summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2023-02-06 16:11:34 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2023-02-06 16:11:34 +0000
commitd079b656b4719739b2247dcd9d46e9bec793095a (patch)
treed2c950c70a776bcf697c963151c5bd959f8a9f03
parentReleasing debian version 1.37.1-2. (diff)
downloadnetdata-d079b656b4719739b2247dcd9d46e9bec793095a.tar.xz
netdata-d079b656b4719739b2247dcd9d46e9bec793095a.zip
Merging upstream version 1.38.0.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
-rw-r--r--.csslintrc2
-rw-r--r--.github/CODEOWNERS49
-rw-r--r--.github/data/distros.yml31
-rw-r--r--.github/labeler.yml6
-rwxr-xr-x.github/scripts/docker-test.sh4
-rwxr-xr-x.github/scripts/gen-docker-tags.py13
-rwxr-xr-x.github/scripts/gen-matrix-build.py34
-rwxr-xr-x.github/scripts/gen-matrix-packaging.py36
-rwxr-xr-x.github/scripts/gen-matrix-repoconfig.py27
-rwxr-xr-x.github/scripts/get-static-cache-key.sh2
-rwxr-xr-x.github/scripts/package-upload.sh2
-rwxr-xr-x.github/scripts/prepare-release-base.sh58
-rwxr-xr-x.github/scripts/run-updater-check.sh21
-rwxr-xr-x.github/scripts/run_install_with_dist_file.sh2
-rw-r--r--.github/workflows/build.yml100
-rw-r--r--.github/workflows/checks.yml2
-rw-r--r--.github/workflows/cloud_regression.yml25
-rw-r--r--.github/workflows/codeql.yml20
-rw-r--r--.github/workflows/docker.yml60
-rw-r--r--.github/workflows/labeler.yml21
-rw-r--r--.github/workflows/packaging.yml66
-rw-r--r--.github/workflows/repoconfig-packages.yml55
-rw-r--r--.github/workflows/review.yml30
-rw-r--r--.gitignore2
-rw-r--r--.lgtm.yml24
-rw-r--r--.remarkignore1
-rw-r--r--.remarkrc.js111
-rw-r--r--.squash.yml7
-rw-r--r--.travis.yml68
-rw-r--r--.travis/README.md149
-rwxr-xr-x.travis/check_changelog_last_modification.sh38
-rwxr-xr-x.travis/create_artifacts.sh77
-rwxr-xr-x.travis/create_changelog.sh47
-rw-r--r--.travis/current_build_status1
-rwxr-xr-x.travis/draft_release.sh65
-rw-r--r--.travis/gcs-credentials.json.encbin2320 -> 0 bytes
-rwxr-xr-x.travis/generate_changelog_and_tag_release.sh64
-rwxr-xr-x.travis/generate_changelog_for_nightlies.sh50
-rwxr-xr-x.travis/nightlies.sh51
-rwxr-xr-x.travis/tagger.sh61
-rwxr-xr-x.travis/trigger_artifact_build.sh20
-rwxr-xr-x.travis/trigger_docker_build.sh19
-rwxr-xr-x.travis/trigger_package_build.sh20
-rw-r--r--.travis/utils.sh29
-rw-r--r--BREAKING_CHANGES.md11
-rw-r--r--BUILD.md365
-rw-r--r--CHANGELOG.md369
-rw-r--r--CMakeLists.txt29
-rw-r--r--Makefile.am46
-rw-r--r--README.md91
-rw-r--r--REDISTRIBUTED.md4
-rw-r--r--aclk/README.md28
-rw-r--r--aclk/aclk.c133
-rw-r--r--aclk/aclk.h2
-rw-r--r--aclk/aclk_capas.c2
-rw-r--r--aclk/aclk_otp.c33
-rw-r--r--aclk/aclk_query.c28
-rw-r--r--aclk/aclk_query_queue.c4
-rw-r--r--aclk/aclk_rx_msgs.c4
-rw-r--r--aclk/aclk_stats.c78
-rw-r--r--aclk/aclk_stats.h4
-rw-r--r--aclk/aclk_util.c120
-rw-r--r--aclk/aclk_util.h8
-rw-r--r--aclk/https_client.c26
-rw-r--r--aclk/https_client.h2
-rwxr-xr-xbuild-artifacts.sh27
-rw-r--r--build_external/scenarios/aclk-testing/agent_netdata.conf5
-rw-r--r--build_external/scenarios/gaps_lo/mostly_off.conf1
-rw-r--r--claim/README.md64
-rw-r--r--claim/claim.c2
-rw-r--r--cli/README.md8
-rw-r--r--collectors/COLLECTORS.md550
-rw-r--r--collectors/Makefile.am1
-rw-r--r--collectors/README.md44
-rw-r--r--collectors/REFERENCE.md20
-rw-r--r--collectors/all.h25
-rw-r--r--collectors/apps.plugin/README.md19
-rw-r--r--collectors/apps.plugin/apps_groups.conf3
-rw-r--r--collectors/apps.plugin/apps_plugin.c224
-rw-r--r--collectors/cgroups.plugin/README.md12
-rw-r--r--collectors/cgroups.plugin/cgroup-network.c81
-rw-r--r--collectors/cgroups.plugin/sys_fs_cgroup.c184
-rw-r--r--collectors/charts.d.plugin/README.md16
-rw-r--r--collectors/charts.d.plugin/ap/README.md7
-rw-r--r--collectors/charts.d.plugin/apcupsd/README.md7
-rw-r--r--collectors/charts.d.plugin/example/README.md6
-rw-r--r--collectors/charts.d.plugin/libreswan/README.md7
-rw-r--r--collectors/charts.d.plugin/nut/README.md7
-rw-r--r--collectors/charts.d.plugin/nut/nut.chart.sh36
-rw-r--r--collectors/charts.d.plugin/opensips/README.md7
-rw-r--r--collectors/charts.d.plugin/sensors/README.md10
-rw-r--r--collectors/checks.plugin/README.md12
-rw-r--r--collectors/cups.plugin/README.md10
-rw-r--r--collectors/cups.plugin/cups_plugin.c1
-rw-r--r--collectors/diskspace.plugin/README.md10
-rw-r--r--collectors/diskspace.plugin/plugin_diskspace.c77
-rw-r--r--collectors/ebpf.plugin/README.md39
-rw-r--r--collectors/ebpf.plugin/ebpf.c98
-rw-r--r--collectors/ebpf.plugin/ebpf.d.conf12
-rw-r--r--collectors/ebpf.plugin/ebpf.h3
-rw-r--r--collectors/ebpf.plugin/ebpf_cachestat.c139
-rw-r--r--collectors/ebpf.plugin/ebpf_cachestat.h6
-rw-r--r--collectors/ebpf.plugin/ebpf_dcstat.c78
-rw-r--r--collectors/ebpf.plugin/ebpf_dcstat.h2
-rw-r--r--collectors/ebpf.plugin/ebpf_disk.c76
-rw-r--r--collectors/ebpf.plugin/ebpf_disk.h2
-rw-r--r--collectors/ebpf.plugin/ebpf_fd.c145
-rw-r--r--collectors/ebpf.plugin/ebpf_fd.h13
-rw-r--r--collectors/ebpf.plugin/ebpf_filesystem.c75
-rw-r--r--collectors/ebpf.plugin/ebpf_filesystem.h1
-rw-r--r--collectors/ebpf.plugin/ebpf_hardirq.c77
-rw-r--r--collectors/ebpf.plugin/ebpf_hardirq.h1
-rw-r--r--collectors/ebpf.plugin/ebpf_mdflush.c84
-rw-r--r--collectors/ebpf.plugin/ebpf_mdflush.h2
-rw-r--r--collectors/ebpf.plugin/ebpf_mount.c85
-rw-r--r--collectors/ebpf.plugin/ebpf_mount.h2
-rw-r--r--collectors/ebpf.plugin/ebpf_oomkill.c11
-rw-r--r--collectors/ebpf.plugin/ebpf_oomkill.h1
-rw-r--r--collectors/ebpf.plugin/ebpf_process.c72
-rw-r--r--collectors/ebpf.plugin/ebpf_process.h2
-rw-r--r--collectors/ebpf.plugin/ebpf_shm.c82
-rw-r--r--collectors/ebpf.plugin/ebpf_shm.h2
-rw-r--r--collectors/ebpf.plugin/ebpf_socket.c247
-rw-r--r--collectors/ebpf.plugin/ebpf_socket.h3
-rw-r--r--collectors/ebpf.plugin/ebpf_softirq.c79
-rw-r--r--collectors/ebpf.plugin/ebpf_softirq.h1
-rw-r--r--collectors/ebpf.plugin/ebpf_swap.c79
-rw-r--r--collectors/ebpf.plugin/ebpf_sync.c81
-rw-r--r--collectors/ebpf.plugin/ebpf_vfs.c81
-rw-r--r--collectors/ebpf.plugin/ebpf_vfs.h2
-rw-r--r--collectors/fping.plugin/Makefile.am24
-rw-r--r--collectors/fping.plugin/README.md110
-rw-r--r--collectors/fping.plugin/fping.conf44
-rwxr-xr-xcollectors/fping.plugin/fping.plugin.in202
-rw-r--r--collectors/freebsd.plugin/README.md8
-rw-r--r--collectors/freebsd.plugin/freebsd_devstat.c24
-rw-r--r--collectors/freebsd.plugin/freebsd_getifaddrs.c26
-rw-r--r--collectors/freebsd.plugin/freebsd_getmntinfo.c12
-rw-r--r--collectors/freebsd.plugin/freebsd_ipfw.c20
-rw-r--r--collectors/freebsd.plugin/freebsd_kstat_zfs.c6
-rw-r--r--collectors/freebsd.plugin/freebsd_sysctl.c248
-rw-r--r--collectors/freebsd.plugin/plugin_freebsd.c2
-rw-r--r--collectors/freeipmi.plugin/README.md6
-rw-r--r--collectors/freeipmi.plugin/freeipmi_plugin.c168
-rw-r--r--collectors/idlejitter.plugin/README.md6
-rw-r--r--collectors/idlejitter.plugin/plugin_idlejitter.c4
-rw-r--r--collectors/ioping.plugin/README.md8
-rw-r--r--collectors/macos.plugin/README.md6
-rw-r--r--collectors/macos.plugin/macos_fw.c22
-rw-r--r--collectors/macos.plugin/macos_mach_smi.c16
-rw-r--r--collectors/macos.plugin/macos_sysctl.c56
-rw-r--r--collectors/macos.plugin/plugin_macos.c2
-rw-r--r--collectors/nfacct.plugin/README.md8
-rw-r--r--collectors/nfacct.plugin/plugin_nfacct.c39
-rw-r--r--collectors/perf.plugin/README.md8
-rw-r--r--collectors/perf.plugin/perf_plugin.c21
-rw-r--r--collectors/plugins.d/README.md34
-rw-r--r--collectors/plugins.d/plugins_d.c133
-rw-r--r--collectors/plugins.d/plugins_d.h13
-rw-r--r--collectors/plugins.d/pluginsd_parser.c63
-rw-r--r--collectors/proc.plugin/README.md10
-rw-r--r--collectors/proc.plugin/ipc.c36
-rw-r--r--collectors/proc.plugin/plugin_proc.c45
-rw-r--r--collectors/proc.plugin/plugin_proc.h3
-rw-r--r--collectors/proc.plugin/proc_diskstats.c62
-rw-r--r--collectors/proc.plugin/proc_interrupts.c4
-rw-r--r--collectors/proc.plugin/proc_loadavg.c4
-rw-r--r--collectors/proc.plugin/proc_mdstat.c12
-rw-r--r--collectors/proc.plugin/proc_meminfo.c8
-rw-r--r--collectors/proc.plugin/proc_net_dev.c33
-rw-r--r--collectors/proc.plugin/proc_net_netstat.c2441
-rw-r--r--collectors/proc.plugin/proc_net_rpc_nfs.c10
-rw-r--r--collectors/proc.plugin/proc_net_rpc_nfsd.c20
-rw-r--r--collectors/proc.plugin/proc_net_sctp_snmp.c2
-rw-r--r--collectors/proc.plugin/proc_net_softnet_stat.c2
-rw-r--r--collectors/proc.plugin/proc_net_stat_conntrack.c2
-rw-r--r--collectors/proc.plugin/proc_net_stat_synproxy.c2
-rw-r--r--collectors/proc.plugin/proc_pagetypeinfo.c14
-rw-r--r--collectors/proc.plugin/proc_pressure.c4
-rw-r--r--collectors/proc.plugin/proc_self_mountinfo.c9
-rw-r--r--collectors/proc.plugin/proc_softirqs.c4
-rw-r--r--collectors/proc.plugin/proc_spl_kstat_zfs.c14
-rw-r--r--collectors/proc.plugin/proc_stat.c54
-rw-r--r--collectors/proc.plugin/proc_vmstat.c2
-rw-r--r--collectors/proc.plugin/sys_block_zram.c12
-rw-r--r--collectors/proc.plugin/sys_class_infiniband.c21
-rw-r--r--collectors/proc.plugin/sys_class_power_supply.c14
-rw-r--r--collectors/proc.plugin/sys_devices_system_edac_mc.c2
-rw-r--r--collectors/proc.plugin/sys_devices_system_node.c4
-rw-r--r--collectors/proc.plugin/sys_fs_btrfs.c40
-rw-r--r--collectors/python.d.plugin/Makefile.am4
-rw-r--r--collectors/python.d.plugin/README.md12
-rw-r--r--collectors/python.d.plugin/adaptec_raid/README.md11
-rw-r--r--collectors/python.d.plugin/alarms/README.md9
-rw-r--r--collectors/python.d.plugin/am2320/README.md7
-rw-r--r--collectors/python.d.plugin/anomalies/README.md16
-rw-r--r--collectors/python.d.plugin/beanstalk/README.md7
-rw-r--r--collectors/python.d.plugin/bind_rndc/README.md7
-rw-r--r--collectors/python.d.plugin/boinc/README.md7
-rw-r--r--collectors/python.d.plugin/ceph/README.md7
-rw-r--r--collectors/python.d.plugin/changefinder/README.md8
-rw-r--r--collectors/python.d.plugin/dockerd/Makefile.inc13
-rw-r--r--collectors/python.d.plugin/dockerd/README.md46
-rw-r--r--collectors/python.d.plugin/dockerd/dockerd.chart.py86
-rw-r--r--collectors/python.d.plugin/dockerd/dockerd.conf77
-rw-r--r--collectors/python.d.plugin/dovecot/README.md7
-rw-r--r--collectors/python.d.plugin/example/README.md10
-rw-r--r--collectors/python.d.plugin/exim/README.md5
-rw-r--r--collectors/python.d.plugin/fail2ban/README.md7
-rw-r--r--collectors/python.d.plugin/gearman/README.md7
-rw-r--r--collectors/python.d.plugin/go_expvar/README.md15
-rw-r--r--collectors/python.d.plugin/haproxy/README.md9
-rw-r--r--collectors/python.d.plugin/hddtemp/README.md7
-rw-r--r--collectors/python.d.plugin/hpssa/README.md13
-rw-r--r--collectors/python.d.plugin/icecast/README.md7
-rw-r--r--collectors/python.d.plugin/ipfs/README.md7
-rw-r--r--collectors/python.d.plugin/litespeed/README.md7
-rw-r--r--collectors/python.d.plugin/logind/Makefile.inc13
-rw-r--r--collectors/python.d.plugin/logind/README.md86
-rw-r--r--collectors/python.d.plugin/logind/logind.chart.py85
-rw-r--r--collectors/python.d.plugin/logind/logind.conf60
-rw-r--r--collectors/python.d.plugin/megacli/README.md11
-rw-r--r--collectors/python.d.plugin/memcached/README.md7
-rw-r--r--collectors/python.d.plugin/mongodb/Makefile.inc13
-rw-r--r--collectors/python.d.plugin/mongodb/README.md210
-rw-r--r--collectors/python.d.plugin/mongodb/mongodb.chart.py786
-rw-r--r--collectors/python.d.plugin/mongodb/mongodb.conf102
-rw-r--r--collectors/python.d.plugin/monit/README.md44
-rw-r--r--collectors/python.d.plugin/nsd/README.md5
-rw-r--r--collectors/python.d.plugin/ntpd/README.md90
-rw-r--r--collectors/python.d.plugin/ntpd/ntpd.chart.py2
-rw-r--r--collectors/python.d.plugin/nvidia_smi/README.md11
-rw-r--r--collectors/python.d.plugin/nvidia_smi/nvidia_smi.chart.py53
-rw-r--r--collectors/python.d.plugin/openldap/README.md7
-rw-r--r--collectors/python.d.plugin/oracledb/README.md7
-rw-r--r--collectors/python.d.plugin/postfix/README.md5
-rw-r--r--collectors/python.d.plugin/proxysql/README.md108
-rw-r--r--collectors/python.d.plugin/proxysql/proxysql.chart.py2
-rw-r--r--collectors/python.d.plugin/puppet/README.md7
-rw-r--r--collectors/python.d.plugin/python.d.conf3
-rw-r--r--collectors/python.d.plugin/rabbitmq/README.md9
-rw-r--r--collectors/python.d.plugin/rethinkdbs/README.md38
-rw-r--r--collectors/python.d.plugin/retroshare/README.md7
-rw-r--r--collectors/python.d.plugin/riakkv/README.md7
-rw-r--r--collectors/python.d.plugin/samba/README.md11
-rw-r--r--collectors/python.d.plugin/sensors/README.md11
-rw-r--r--collectors/python.d.plugin/smartd_log/README.md7
-rw-r--r--collectors/python.d.plugin/spigotmc/README.md7
-rw-r--r--collectors/python.d.plugin/springboot/Makefile.inc13
-rw-r--r--collectors/python.d.plugin/springboot/README.md145
-rw-r--r--collectors/python.d.plugin/springboot/springboot.chart.py160
-rw-r--r--collectors/python.d.plugin/springboot/springboot.conf118
-rw-r--r--collectors/python.d.plugin/squid/README.md7
-rw-r--r--collectors/python.d.plugin/tomcat/README.md7
-rw-r--r--collectors/python.d.plugin/tor/README.md7
-rw-r--r--collectors/python.d.plugin/traefik/README.md62
-rw-r--r--collectors/python.d.plugin/uwsgi/README.md7
-rw-r--r--collectors/python.d.plugin/varnish/README.md7
-rw-r--r--collectors/python.d.plugin/w1sensor/README.md7
-rw-r--r--collectors/python.d.plugin/w1sensor/w1sensor.chart.py2
-rw-r--r--collectors/python.d.plugin/zscores/README.md8
-rw-r--r--collectors/slabinfo.plugin/README.md6
-rw-r--r--collectors/slabinfo.plugin/slabinfo.c9
-rw-r--r--collectors/statsd.plugin/README.md20
-rw-r--r--collectors/statsd.plugin/asterisk.md6
-rw-r--r--collectors/statsd.plugin/k6.md6
-rw-r--r--collectors/statsd.plugin/statsd.c93
-rw-r--r--collectors/tc.plugin/README.md8
-rw-r--r--collectors/tc.plugin/plugin_tc.c29
-rw-r--r--collectors/timex.plugin/README.md8
-rw-r--r--collectors/timex.plugin/plugin_timex.c2
-rw-r--r--collectors/xenstat.plugin/README.md6
-rw-r--r--collectors/xenstat.plugin/xenstat_plugin.c1
-rw-r--r--configure.ac37
-rw-r--r--contrib/debian/netdata.postinst7
-rwxr-xr-xcontrib/debian/rules1
-rw-r--r--contribution-guidelines.md817
-rw-r--r--daemon/README.md43
-rw-r--r--daemon/analytics.c24
-rw-r--r--daemon/commands.c28
-rw-r--r--daemon/commands.h1
-rw-r--r--daemon/common.c35
-rw-r--r--daemon/common.h3
-rw-r--r--daemon/config/README.md70
-rw-r--r--daemon/event_loop.c59
-rw-r--r--daemon/event_loop.h53
-rw-r--r--daemon/global_statistics.c1946
-rw-r--r--daemon/global_statistics.h29
-rw-r--r--daemon/main.c608
-rw-r--r--daemon/main.h31
-rw-r--r--daemon/service.c38
-rw-r--r--daemon/static_threads.c51
-rw-r--r--daemon/static_threads_freebsd.c2
-rw-r--r--daemon/static_threads_linux.c8
-rw-r--r--daemon/static_threads_macos.c2
-rwxr-xr-xdaemon/system-info.sh5
-rw-r--r--daemon/unit_test.c96
-rw-r--r--database/README.md18
-rw-r--r--database/engine/README.md303
-rw-r--r--database/engine/cache.c2737
-rw-r--r--database/engine/cache.h249
-rw-r--r--database/engine/datafile.c415
-rw-r--r--database/engine/datafile.h76
-rw-r--r--database/engine/datafile.ksy74
-rw-r--r--database/engine/dbengine-diagram.xml1
-rw-r--r--database/engine/journalfile.c1445
-rw-r--r--database/engine/journalfile.h150
-rw-r--r--database/engine/journalfile.ksy144
-rw-r--r--database/engine/metric.c875
-rw-r--r--database/engine/metric.h79
-rw-r--r--database/engine/pagecache.c2054
-rw-r--r--database/engine/pagecache.h242
-rw-r--r--database/engine/pdc.c1282
-rw-r--r--database/engine/pdc.h67
-rw-r--r--database/engine/rrdengine.c2634
-rw-r--r--database/engine/rrdengine.h569
-rwxr-xr-xdatabase/engine/rrdengineapi.c1683
-rw-r--r--database/engine/rrdengineapi.h153
-rw-r--r--database/engine/rrdenginelib.c208
-rw-r--r--database/engine/rrdenginelib.h4
-rw-r--r--database/engine/rrdenglocking.c241
-rw-r--r--database/engine/rrdenglocking.h17
-rw-r--r--database/ram/rrddim_mem.c352
-rw-r--r--database/ram/rrddim_mem.h15
-rw-r--r--database/rrd.c2
-rw-r--r--database/rrd.h417
-rw-r--r--database/rrdcalc.c25
-rw-r--r--database/rrdcalc.h4
-rw-r--r--database/rrdcalctemplate.c3
-rw-r--r--database/rrdcontext.c502
-rw-r--r--database/rrdcontext.h35
-rw-r--r--database/rrddim.c157
-rw-r--r--database/rrddimvar.c7
-rw-r--r--database/rrdfamily.c3
-rw-r--r--database/rrdfunctions.c11
-rw-r--r--database/rrdhost.c577
-rw-r--r--database/rrdlabels.c7
-rw-r--r--database/rrdset.c417
-rw-r--r--database/rrdsetvar.c7
-rw-r--r--database/rrdvar.c3
-rw-r--r--database/sqlite/sqlite3.c13954
-rw-r--r--database/sqlite/sqlite3.h160
-rw-r--r--database/sqlite/sqlite_aclk.c336
-rw-r--r--database/sqlite/sqlite_aclk.h5
-rw-r--r--database/sqlite/sqlite_aclk_alert.c102
-rw-r--r--database/sqlite/sqlite_context.c24
-rw-r--r--database/sqlite/sqlite_context.h4
-rw-r--r--database/sqlite/sqlite_functions.c432
-rw-r--r--database/sqlite/sqlite_functions.h7
-rw-r--r--database/sqlite/sqlite_health.c36
-rw-r--r--database/sqlite/sqlite_metadata.c428
-rw-r--r--database/sqlite/sqlite_metadata.h9
-rw-r--r--database/storage_engine.c50
-rw-r--r--diagrams/Makefile.am1
-rw-r--r--diagrams/data_structures/README.md18
-rw-r--r--diagrams/netdata-overview.xml752
-rw-r--r--docs/Add-more-charts-to-netdata.md4
-rw-r--r--docs/Running-behind-apache.md13
-rw-r--r--docs/Running-behind-caddy.md6
-rw-r--r--docs/Running-behind-h2o.md14
-rw-r--r--docs/Running-behind-haproxy.md6
-rw-r--r--docs/Running-behind-lighttpd.md10
-rw-r--r--docs/Running-behind-nginx.md13
-rw-r--r--docs/agent-cloud.md41
-rw-r--r--docs/anonymous-statistics.md8
-rw-r--r--docs/cloud/alerts-notifications/add-discord-notification.md59
-rw-r--r--docs/cloud/alerts-notifications/add-pagerduty-notification-configuration.md60
-rw-r--r--docs/cloud/alerts-notifications/add-slack-notification-configuration.md63
-rw-r--r--docs/cloud/alerts-notifications/add-webhook-notification-configuration.md105
-rw-r--r--docs/cloud/alerts-notifications/manage-notification-methods.md88
-rw-r--r--docs/cloud/alerts-notifications/notifications.mdx155
-rw-r--r--docs/cloud/alerts-notifications/smartboard.mdx46
-rw-r--r--docs/cloud/alerts-notifications/view-active-alerts.mdx76
-rw-r--r--docs/cloud/beta-architecture/new-architecture.md36
-rw-r--r--docs/cloud/cheatsheet.mdx231
-rw-r--r--docs/cloud/cloud.mdx74
-rw-r--r--docs/cloud/data-privacy.mdx39
-rw-r--r--docs/cloud/get-started.mdx133
-rw-r--r--docs/cloud/insights/anomaly-advisor.mdx86
-rw-r--r--docs/cloud/insights/metric-correlations.md87
-rw-r--r--docs/cloud/manage/invite-your-team.md37
-rw-r--r--docs/cloud/manage/sign-in.mdx88
-rw-r--r--docs/cloud/manage/themes.md22
-rw-r--r--docs/cloud/netdata-functions.md65
-rw-r--r--docs/cloud/runtime-troubleshooting-with-functions.md43
-rw-r--r--docs/cloud/spaces.md91
-rw-r--r--docs/cloud/visualize/dashboards.md122
-rw-r--r--docs/cloud/visualize/interact-new-charts.md222
-rw-r--r--docs/cloud/visualize/kubernetes.md154
-rw-r--r--docs/cloud/visualize/nodes.md53
-rw-r--r--docs/cloud/visualize/overview.md250
-rw-r--r--docs/cloud/war-rooms.md162
-rw-r--r--docs/collect/application-metrics.md41
-rw-r--r--docs/collect/container-metrics.md43
-rw-r--r--docs/collect/enable-configure.md26
-rw-r--r--docs/collect/how-collectors-work.md34
-rw-r--r--docs/collect/system-metrics.md31
-rw-r--r--docs/configure/common-changes.md132
-rw-r--r--docs/configure/nodes.md54
-rw-r--r--docs/configure/secure-nodes.md36
-rw-r--r--docs/configure/start-stop-restart.md24
-rw-r--r--docs/contributing/contributing-documentation.md8
-rw-r--r--docs/contributing/style-guide.md101
-rw-r--r--docs/dashboard/customize.mdx32
-rw-r--r--docs/dashboard/dimensions-contexts-families.mdx42
-rw-r--r--docs/dashboard/how-dashboard-works.mdx52
-rw-r--r--docs/dashboard/import-export-print-snapshot.mdx31
-rw-r--r--docs/dashboard/interact-charts.mdx32
-rw-r--r--docs/dashboard/reference-web-server.mdx20
-rw-r--r--docs/dashboard/visualization-date-and-time-controls.mdx32
-rw-r--r--docs/export/enable-connector.md50
-rw-r--r--docs/export/external-databases.md98
-rw-r--r--docs/get-started.mdx116
-rw-r--r--docs/getting-started/integrations.md12
-rw-r--r--docs/getting-started/introduction.md158
-rw-r--r--docs/guidelines.md772
-rw-r--r--docs/guides/collect-apache-nginx-web-logs.md10
-rw-r--r--docs/guides/collect-unbound-metrics.md4
-rw-r--r--docs/guides/configure/performance.md34
-rw-r--r--docs/guides/deploy/ansible.md22
-rw-r--r--docs/guides/export/export-netdata-metrics-graphite.md44
-rw-r--r--docs/guides/monitor-cockroachdb.md43
-rw-r--r--docs/guides/monitor-hadoop-cluster.md8
-rw-r--r--docs/guides/monitor/anomaly-detection-python.md36
-rw-r--r--docs/guides/monitor/anomaly-detection.md18
-rw-r--r--docs/guides/monitor/dimension-templates.md37
-rw-r--r--docs/guides/monitor/kubernetes-k8s-netdata.md28
-rw-r--r--docs/guides/monitor/lamp-stack.md42
-rw-r--r--docs/guides/monitor/pi-hole-raspberry-pi.md26
-rw-r--r--docs/guides/monitor/process.md231
-rw-r--r--docs/guides/monitor/raspberry-pi-anomaly-detection.md22
-rw-r--r--docs/guides/monitor/statsd.md14
-rw-r--r--docs/guides/monitor/stop-notifications-alarms.md12
-rw-r--r--docs/guides/monitor/visualize-monitor-anomalies.md28
-rw-r--r--docs/guides/python-collector.md18
-rw-r--r--docs/guides/step-by-step/step-00.md6
-rw-r--r--docs/guides/step-by-step/step-01.md2
-rw-r--r--docs/guides/step-by-step/step-02.md8
-rw-r--r--docs/guides/step-by-step/step-03.md15
-rw-r--r--docs/guides/step-by-step/step-04.md8
-rw-r--r--docs/guides/step-by-step/step-05.md19
-rw-r--r--docs/guides/step-by-step/step-06.md10
-rw-r--r--docs/guides/step-by-step/step-07.md8
-rw-r--r--docs/guides/step-by-step/step-08.md6
-rw-r--r--docs/guides/step-by-step/step-09.md16
-rw-r--r--docs/guides/step-by-step/step-10.md6
-rw-r--r--docs/guides/troubleshoot/monitor-debug-applications-ebpf.md24
-rw-r--r--docs/guides/troubleshoot/troubleshooting-agent-with-cloud-connection.md4
-rw-r--r--docs/guides/using-host-labels.md28
-rw-r--r--docs/metrics-storage-management/enable-streaming.mdx37
-rw-r--r--docs/metrics-storage-management/how-streaming-works.mdx35
-rw-r--r--docs/metrics-storage-management/reference-streaming.mdx36
-rw-r--r--docs/monitor/configure-alarms.md26
-rw-r--r--docs/monitor/enable-notifications.md78
-rw-r--r--docs/monitor/view-active-alarms.md14
-rw-r--r--docs/netdata-for-IoT.md27
-rw-r--r--docs/netdata-security.md4
-rw-r--r--docs/overview/netdata-monitoring-stack.md8
-rw-r--r--docs/overview/what-is-netdata.md46
-rw-r--r--docs/overview/why-netdata.md2
-rw-r--r--docs/quickstart/infrastructure.md64
-rw-r--r--docs/quickstart/single-node.md24
-rw-r--r--docs/store/change-metrics-storage.md24
-rw-r--r--docs/store/distributed-data-architecture.md20
-rw-r--r--docs/visualize/create-dashboards.md17
-rw-r--r--docs/visualize/interact-dashboards-charts.md38
-rw-r--r--docs/visualize/overview-infrastructure.md32
-rw-r--r--docs/why-netdata/README.md8
-rw-r--r--exporting/README.md36
-rw-r--r--exporting/TIMESCALE.md8
-rw-r--r--exporting/WALKTHROUGH.md11
-rw-r--r--exporting/aws_kinesis/README.md11
-rw-r--r--exporting/aws_kinesis/aws_kinesis.c2
-rw-r--r--exporting/exporting_engine.c2
-rw-r--r--exporting/graphite/README.md20
-rw-r--r--exporting/graphite/graphite.c4
-rw-r--r--exporting/init_connectors.c6
-rw-r--r--exporting/json/README.md14
-rw-r--r--exporting/json/json.c6
-rw-r--r--exporting/mongodb/README.md13
-rw-r--r--exporting/mongodb/mongodb.c2
-rw-r--r--exporting/opentsdb/README.md20
-rw-r--r--exporting/opentsdb/opentsdb.c8
-rw-r--r--exporting/process_data.c16
-rw-r--r--exporting/prometheus/README.md107
-rw-r--r--exporting/prometheus/prometheus.c2
-rw-r--r--exporting/prometheus/remote_write/README.md9
-rw-r--r--exporting/prometheus/remote_write/remote_write.c2
-rw-r--r--exporting/pubsub/README.md8
-rw-r--r--exporting/pubsub/pubsub.c2
-rw-r--r--exporting/send_data.c2
-rw-r--r--exporting/send_internal_metrics.c2
-rw-r--r--exporting/tests/test_exporting_engine.c26
-rw-r--r--exporting/tests/test_exporting_engine.h3
-rw-r--r--health/Makefile.am3
-rw-r--r--health/QUICKSTART.md143
-rw-r--r--health/README.md16
-rw-r--r--health/REFERENCE.md46
-rw-r--r--health/health.c960
-rw-r--r--health/health.d/cgroups.conf8
-rw-r--r--health/health.d/consul.conf159
-rw-r--r--health/health.d/disks.conf8
-rw-r--r--health/health.d/dns_query.conf2
-rw-r--r--health/health.d/elasticsearch.conf73
-rw-r--r--health/health.d/fping.conf64
-rw-r--r--health/health.d/httpcheck.conf62
-rw-r--r--health/health.d/kubelet.conf20
-rw-r--r--health/health.d/load.conf5
-rw-r--r--health/health.d/mdstat.conf4
-rw-r--r--health/health.d/net.conf28
-rw-r--r--health/health.d/nvme.conf2
-rw-r--r--health/health.d/ping.conf6
-rw-r--r--health/health.d/portcheck.conf6
-rw-r--r--health/health.d/postgres.conf26
-rw-r--r--health/health.d/zfs.conf4
-rw-r--r--health/health.h9
-rw-r--r--health/health_config.c105
-rw-r--r--health/health_json.c10
-rw-r--r--health/health_log.c426
-rw-r--r--health/notifications/README.md6
-rwxr-xr-xhealth/notifications/alarm-notify.sh.in130
-rw-r--r--health/notifications/alerta/README.md7
-rw-r--r--health/notifications/awssns/README.md7
-rw-r--r--health/notifications/custom/README.md11
-rw-r--r--health/notifications/discord/README.md21
-rw-r--r--health/notifications/dynatrace/README.md7
-rw-r--r--health/notifications/email/README.md7
-rw-r--r--health/notifications/flock/README.md7
-rw-r--r--health/notifications/gotify/README.md6
-rw-r--r--health/notifications/hangouts/README.md6
-rwxr-xr-xhealth/notifications/health_alarm_notify.conf17
-rw-r--r--health/notifications/irc/README.md7
-rw-r--r--health/notifications/kavenegar/README.md7
-rw-r--r--health/notifications/matrix/README.md6
-rw-r--r--health/notifications/messagebird/README.md7
-rw-r--r--health/notifications/msteams/README.md7
-rw-r--r--health/notifications/opsgenie/README.md16
-rw-r--r--health/notifications/pagerduty/README.md16
-rw-r--r--health/notifications/prowl/README.md7
-rw-r--r--health/notifications/pushbullet/README.md7
-rw-r--r--health/notifications/pushover/README.md7
-rw-r--r--health/notifications/rocketchat/README.md7
-rw-r--r--health/notifications/slack/README.md7
-rw-r--r--health/notifications/smstools3/README.md7
-rw-r--r--health/notifications/stackpulse/README.md8
-rw-r--r--health/notifications/syslog/README.md7
-rw-r--r--health/notifications/telegram/README.md7
-rw-r--r--health/notifications/twilio/README.md7
-rw-r--r--health/notifications/web/README.md11
-rw-r--r--libnetdata/Makefile.am3
-rw-r--r--libnetdata/README.md4
-rw-r--r--libnetdata/adaptive_resortable_list/README.md4
-rw-r--r--libnetdata/aral/Makefile.am (renamed from libnetdata/arrayalloc/Makefile.am)0
-rw-r--r--libnetdata/aral/README.md173
-rw-r--r--libnetdata/aral/aral.c1081
-rw-r--r--libnetdata/aral/aral.h69
-rw-r--r--libnetdata/arrayalloc/README.md7
-rw-r--r--libnetdata/arrayalloc/arrayalloc.c489
-rw-r--r--libnetdata/arrayalloc/arrayalloc.h48
-rw-r--r--libnetdata/avl/README.md4
-rw-r--r--libnetdata/buffer/README.md4
-rw-r--r--libnetdata/buffer/buffer.c60
-rw-r--r--libnetdata/buffer/buffer.h3
-rw-r--r--libnetdata/circular_buffer/README.md6
-rw-r--r--libnetdata/circular_buffer/circular_buffer.c30
-rw-r--r--libnetdata/circular_buffer/circular_buffer.h3
-rw-r--r--libnetdata/clocks/clocks.c52
-rw-r--r--libnetdata/clocks/clocks.h3
-rw-r--r--libnetdata/completion/completion.c30
-rw-r--r--libnetdata/completion/completion.h5
-rw-r--r--libnetdata/config/README.md4
-rw-r--r--libnetdata/dictionary/README.md4
-rw-r--r--libnetdata/dictionary/dictionary.c130
-rw-r--r--libnetdata/dictionary/dictionary.h13
-rw-r--r--libnetdata/ebpf/README.md8
-rw-r--r--libnetdata/ebpf/ebpf.c27
-rw-r--r--libnetdata/ebpf/ebpf.h2
-rw-r--r--libnetdata/eval/eval.c4
-rw-r--r--libnetdata/json/README.md4
-rw-r--r--libnetdata/json/json.c2
-rw-r--r--libnetdata/july/Makefile.am8
-rw-r--r--libnetdata/july/README.md14
-rw-r--r--libnetdata/july/july.c453
-rw-r--r--libnetdata/july/july.h40
-rw-r--r--libnetdata/libnetdata.c215
-rw-r--r--libnetdata/libnetdata.h185
-rw-r--r--libnetdata/locks/README.md5
-rw-r--r--libnetdata/locks/locks.c441
-rw-r--r--libnetdata/locks/locks.h31
-rw-r--r--libnetdata/log/README.md10
-rw-r--r--libnetdata/log/log.c110
-rw-r--r--libnetdata/log/log.h19
-rw-r--r--libnetdata/onewayalloc/README.md4
-rw-r--r--libnetdata/onewayalloc/onewayalloc.c13
-rw-r--r--libnetdata/onewayalloc/onewayalloc.h2
-rw-r--r--libnetdata/os.c128
-rw-r--r--libnetdata/os.h6
-rw-r--r--libnetdata/popen/README.md10
-rw-r--r--libnetdata/popen/popen.c18
-rw-r--r--libnetdata/procfile/README.md6
-rw-r--r--libnetdata/procfile/procfile.c9
-rw-r--r--libnetdata/procfile/procfile.h5
-rw-r--r--libnetdata/required_dummies.h1
-rw-r--r--libnetdata/simple_pattern/README.md8
-rw-r--r--libnetdata/socket/security.c2
-rw-r--r--libnetdata/socket/socket.c37
-rw-r--r--libnetdata/socket/socket.h25
-rw-r--r--libnetdata/statistical/README.md7
-rw-r--r--libnetdata/storage_number/README.md4
-rw-r--r--libnetdata/string/README.md7
-rw-r--r--libnetdata/string/string.c35
-rw-r--r--libnetdata/threads/README.md7
-rw-r--r--libnetdata/threads/threads.c39
-rw-r--r--libnetdata/url/README.md9
-rw-r--r--libnetdata/worker_utilization/worker_utilization.c25
-rw-r--r--libnetdata/worker_utilization/worker_utilization.h1
-rw-r--r--ml/ADCharts.cc543
-rw-r--r--ml/ADCharts.h10
-rw-r--r--ml/Chart.cc0
-rw-r--r--ml/Chart.h128
-rw-r--r--ml/Config.cc6
-rw-r--r--ml/Config.h1
-rw-r--r--ml/Dimension.cc309
-rw-r--r--ml/Dimension.h178
-rw-r--r--ml/Host.cc448
-rw-r--r--ml/Host.h106
-rw-r--r--ml/Mutex.h36
-rw-r--r--ml/Query.h10
-rw-r--r--ml/Queue.h66
-rw-r--r--ml/README.md98
-rw-r--r--ml/SamplesBuffer.cc63
-rw-r--r--ml/SamplesBuffer.h7
-rw-r--r--ml/SamplesBufferTests.cc146
-rw-r--r--ml/Stats.h46
-rw-r--r--ml/ml-dummy.c51
-rw-r--r--ml/ml-private.h13
-rw-r--r--ml/ml.cc119
-rw-r--r--ml/ml.h29
-rw-r--r--ml/notebooks/netdata_anomaly_detection_deepdive.ipynb97
-rwxr-xr-xnetdata-installer.sh24
-rw-r--r--netdata.spec.in7
-rw-r--r--packaging/PLATFORM_SUPPORT.md123
-rw-r--r--packaging/building-native-packages-locally.md1
-rw-r--r--packaging/current_libbpf.checksums2
-rw-r--r--packaging/current_libbpf.version2
-rw-r--r--packaging/docker/Dockerfile15
-rw-r--r--packaging/docker/README.md93
-rwxr-xr-xpackaging/docker/run.sh8
-rw-r--r--packaging/ebpf-co-re.checksums2
-rw-r--r--packaging/ebpf-co-re.version2
-rw-r--r--packaging/ebpf.checksums6
-rw-r--r--packaging/ebpf.version2
-rw-r--r--packaging/go.d.checksums34
-rw-r--r--packaging/go.d.version2
-rw-r--r--packaging/installer/README.md26
-rw-r--r--packaging/installer/REINSTALL.md16
-rw-r--r--packaging/installer/UNINSTALL.md10
-rw-r--r--packaging/installer/UPDATE.md22
-rw-r--r--packaging/installer/functions.sh14
-rwxr-xr-xpackaging/installer/install-required-packages.sh57
-rwxr-xr-xpackaging/installer/kickstart.sh126
-rw-r--r--packaging/installer/methods/cloud-providers.md12
-rw-r--r--packaging/installer/methods/freebsd.md6
-rw-r--r--packaging/installer/methods/kickstart.md29
-rw-r--r--packaging/installer/methods/kubernetes.md16
-rw-r--r--packaging/installer/methods/macos.md34
-rw-r--r--packaging/installer/methods/manual.md29
-rw-r--r--packaging/installer/methods/offline.md16
-rw-r--r--packaging/installer/methods/packages.md89
-rw-r--r--packaging/installer/methods/source.md8
-rw-r--r--packaging/installer/methods/synology.md2
-rwxr-xr-xpackaging/installer/netdata-uninstaller.sh2
-rwxr-xr-xpackaging/installer/netdata-updater.sh38
-rw-r--r--packaging/libbpf.checksums2
-rw-r--r--packaging/libbpf.version2
-rwxr-xr-xpackaging/makeself/install-or-update.sh13
-rwxr-xr-xpackaging/makeself/jobs/50-fping-5.1.install.sh45
-rwxr-xr-xpackaging/makeself/jobs/70-netdata-git.install.sh2
-rw-r--r--packaging/repoconfig/Makefile22
-rw-r--r--packaging/repoconfig/debian/changelog7
-rw-r--r--packaging/repoconfig/debian/control4
-rw-r--r--packaging/repoconfig/debian/copyright2
-rw-r--r--packaging/repoconfig/netdata-edge.repo.centos12
-rw-r--r--packaging/repoconfig/netdata-edge.repo.fedora12
-rw-r--r--packaging/repoconfig/netdata-edge.repo.ol12
-rw-r--r--packaging/repoconfig/netdata-edge.repo.suse12
-rw-r--r--packaging/repoconfig/netdata-repo.spec6
-rw-r--r--packaging/repoconfig/netdata.list.in4
-rw-r--r--packaging/repoconfig/netdata.repo.centos12
-rw-r--r--packaging/repoconfig/netdata.repo.fedora12
-rw-r--r--packaging/repoconfig/netdata.repo.ol12
-rw-r--r--packaging/repoconfig/netdata.repo.suse12
-rw-r--r--packaging/version2
-rw-r--r--parser/README.md25
-rw-r--r--parser/parser.c2
-rw-r--r--registry/README.md14
-rw-r--r--spawn/spawn_server.c5
-rw-r--r--streaming/README.md14
-rw-r--r--streaming/compression.c2
-rw-r--r--streaming/receiver.c627
-rw-r--r--streaming/replication.c1104
-rw-r--r--streaming/replication.h5
-rw-r--r--streaming/rrdpush.c619
-rw-r--r--streaming/rrdpush.h66
-rw-r--r--streaming/sender.c275
-rw-r--r--system/Makefile.am2
-rwxr-xr-xsystem/edit-config309
-rwxr-xr-xsystem/edit-config.in83
-rw-r--r--system/netdata.logrotate.in2
-rw-r--r--system/netdata.service.in6
-rw-r--r--tests/alarm_repetition/netdata.conf_with_repetition1
-rw-r--r--tests/alarm_repetition/netdata.conf_without_repetition1
-rw-r--r--tests/health_mgmtapi/README.md2
-rwxr-xr-xtests/installer/checksums.sh53
-rwxr-xr-xtests/installer/slack.sh65
-rwxr-xr-xtests/lifecycle.bats60
-rwxr-xr-xtests/run-unit-tests.sh2
-rwxr-xr-xtests/updater_checks.bats65
-rwxr-xr-xtests/updater_checks.sh71
-rw-r--r--web/README.md18
-rw-r--r--web/api/README.md6
-rw-r--r--web/api/badges/README.md2
-rw-r--r--web/api/badges/web_buffer_svg.c8
-rw-r--r--web/api/exporters/prometheus/README.md2
-rw-r--r--web/api/exporters/shell/allmetrics_shell.c2
-rw-r--r--web/api/formatters/README.md24
-rw-r--r--web/api/formatters/charts2json.c10
-rw-r--r--web/api/formatters/csv/README.md2
-rw-r--r--web/api/formatters/json/README.md2
-rw-r--r--web/api/formatters/json_wrapper.c60
-rw-r--r--web/api/formatters/rrd2json.c4
-rw-r--r--web/api/formatters/rrd2json.h1
-rw-r--r--web/api/formatters/rrdset2json.c6
-rw-r--r--web/api/formatters/ssv/README.md2
-rw-r--r--web/api/formatters/value/README.md6
-rw-r--r--web/api/formatters/value/value.c3
-rw-r--r--web/api/formatters/value/value.h2
-rw-r--r--web/api/health/README.md6
-rw-r--r--web/api/health/health_cmdapi.c2
-rw-r--r--web/api/queries/README.md2
-rw-r--r--web/api/queries/query.c712
-rw-r--r--web/api/queries/rrdr.h6
-rw-r--r--web/api/queries/weights.c18
-rw-r--r--web/api/web_api_v1.c53
-rw-r--r--web/api/web_api_v1.h2
-rw-r--r--web/gui/README.md22
-rw-r--r--web/gui/confluence/README.md2
-rw-r--r--web/gui/custom/README.md17
-rw-r--r--web/gui/dashboard_info.js394
-rw-r--r--web/gui/main.js3
-rw-r--r--web/server/README.md20
-rw-r--r--web/server/static/static-threaded.c69
-rw-r--r--web/server/web_client.c68
-rw-r--r--web/server/web_client.h7
-rw-r--r--web/server/web_client_cache.c19
-rw-r--r--web/server/web_server.c2
756 files changed, 46230 insertions, 27578 deletions
diff --git a/.csslintrc b/.csslintrc
deleted file mode 100644
index aacba956..00000000
--- a/.csslintrc
+++ /dev/null
@@ -1,2 +0,0 @@
---exclude-exts=.min.css
---ignore=adjoining-classes,box-model,ids,order-alphabetical,unqualified-attributes
diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS
index c513b71d..34c93455 100644
--- a/.github/CODEOWNERS
+++ b/.github/CODEOWNERS
@@ -5,53 +5,50 @@
* @Ferroin
# Ownership by directory structure
-.travis/ @Ferroin
-.github/ @Ferroin
+.github/ @Ferroin @tkatsoulas
aclk/ @stelfrag @underhood
-build/ @Ferroin
-contrib/debian @Ferroin
+build/ @Ferroin @tkatsoulas
+contrib/debian @Ferroin @tkatsoulas
collectors/ @thiagoftsm
collectors/ebpf.plugin/ @thiagoftsm
collectors/charts.d.plugin/ @ilyam8 @Ferroin
collectors/freebsd.plugin/ @thiagoftsm
collectors/macos.plugin/ @thiagoftsm
collectors/python.d.plugin/ @ilyam8
-collectors/cups.plugin/ @simonnagl @thiagoftsm
+collectors/cups.plugin/ @thiagoftsm
exporting/ @thiagoftsm
daemon/ @thiagoftsm @vkalintiris
database/ @thiagoftsm @vkalintiris
-docs/ @DShreve2
+docs/ @tkatsoulas
health/ @thiagoftsm @vkalintiris @MrZammler
health/health.d/ @thiagoftsm @MrZammler
health/notifications/ @Ferroin @thiagoftsm @MrZammler
ml/ @andrewm4894 @vkalintiris
libnetdata/ @thiagoftsm @vkalintiris
-packaging/ @Ferroin
+packaging/ @Ferroin @tkatsoulas
registry/ @jacekkolasa
streaming/ @thiagoftsm
-system/ @Ferroin
-tests/ @Ferroin @vkalintiris
+system/ @Ferroin @tkatsoulas
+tests/ @Ferroin @vkalintiris @tkatsoulas
web/ @thiagoftsm @vkalintiris
web/gui/ @jacekkolasa
# Ownership by filetype (overwrites ownership by directory)
-*.am @Ferroin
-*.md @DShreve2
-Dockerfile* @Ferroin
+*.am @Ferroin @tkatsoulas
+*.md @tkatsoulas
+Dockerfile* @Ferroin @tkatsoulas
# Ownership of specific files
-.gitignore @Ferroin @vkalintiris
-.travis.yml @Ferroin
-.lgtm.yml @Ferroin
-.eslintrc @Ferroin
-.eslintignore @Ferroin
-.csslintrc @Ferroin
-.codeclimate.yml @Ferroin
-.codacy.yml @Ferroin
-.yamllint.yml @Ferroin
-netdata.spec.in @Ferroin
-netdata-installer.sh @Ferroin
-packaging/version @netdatabot @Ferroin
+.gitignore @Ferroin @tkatsoulas @vkalintiris
+.eslintrc @Ferroin @tkatsoulas
+.eslintignore @Ferroin @tkatsoulas
+.csslintrc @Ferroin @tkatsoulas
+.codeclimate.yml @Ferroin @tkatsoulas
+.codacy.yml @Ferroin @tkatsoulas
+.yamllint.yml @Ferroin @tkatsoulas
+netdata.spec.in @Ferroin @tkatsoulas
+netdata-installer.sh @Ferroin @tkatsoulas
+packaging/version @netdatabot @Ferroin @tkatsoulas
-LICENSE.md @DShreve2 @Ferroin @vkalintiris
-CHANGELOG.md @netdatabot @Ferroin
+LICENSE.md @Ferroin @tkatsoulas @vkalintiris
+CHANGELOG.md @netdatabot @Ferroin @tkatsoulas
diff --git a/.github/data/distros.yml b/.github/data/distros.yml
index cc527529..452170c0 100644
--- a/.github/data/distros.yml
+++ b/.github/data/distros.yml
@@ -51,6 +51,9 @@ include:
packages: &alma_packages
type: rpm
repo_distro: el/9
+ alt_links:
+ - el/9Server
+ - el/9Client
arches:
- x86_64
- aarch64
@@ -61,12 +64,18 @@ include:
packages:
<<: *alma_packages
repo_distro: el/8
+ alt_links:
+ - el/8Server
+ - el/8Client
- distro: centos
version: "7"
packages:
type: rpm
repo_distro: el/7
+ alt_links:
+ - el/7Server
+ - el/7Client
arches:
- x86_64
test:
@@ -115,21 +124,6 @@ include:
packages:
<<: *fedora_packages
repo_distro: fedora/36
- arches:
- - x86_64
- - armhfp
- - aarch64
- test:
- ebpf-core: true
- - <<: *fedora
- version: "35"
- packages:
- <<: *fedora_packages
- repo_distro: fedora/35
- arches:
- - x86_64
- - armhfp
- - aarch64
test:
ebpf-core: true
@@ -147,13 +141,6 @@ include:
- aarch64
test:
ebpf-core: true
- - <<: *opensuse
- version: "15.3"
- packages:
- <<: *opensuse_packages
- repo_distro: opensuse/15.3
- test:
- ebpf-core: false
- &oracle
distro: oraclelinux
diff --git a/.github/labeler.yml b/.github/labeler.yml
index c7232507..4d3a614d 100644
--- a/.github/labeler.yml
+++ b/.github/labeler.yml
@@ -34,8 +34,6 @@ area/build:
- "**/Makefile.am"
area/ci:
- - .travis/*
- - .travis/**/*
- .github/*
- .github/**/*
@@ -88,10 +86,6 @@ collectors/ebpf:
- collectors/ebpf.plugin/*
- collectors/ebpf.plugin/**/*
-collectors/fping:
- - collectors/fping.plugin/*
- - collectors/fping.plugin/**/*
-
collectors/freebsd:
- collectors/freebsd.plugin/*
- collectors/freebsd.plugin/**/*
diff --git a/.github/scripts/docker-test.sh b/.github/scripts/docker-test.sh
index 22821d17..0f5fa469 100755
--- a/.github/scripts/docker-test.sh
+++ b/.github/scripts/docker-test.sh
@@ -26,6 +26,10 @@ wait_for() {
sleep 1
if [ "$i" -gt "$timeout" ]; then
printf "Timed out!\n"
+ docker ps -a
+ echo "::group::Netdata container logs"
+ docker logs netdata 2>&1
+ echo "::endgroup::"
return 1
fi
i="$((i + 1))"
diff --git a/.github/scripts/gen-docker-tags.py b/.github/scripts/gen-docker-tags.py
index df4dc026..8c88d3b5 100755
--- a/.github/scripts/gen-docker-tags.py
+++ b/.github/scripts/gen-docker-tags.py
@@ -6,9 +6,14 @@ version = sys.argv[1].split('.')
suffix = sys.argv[2]
REPO = f'netdata/netdata{suffix}'
+GHCR = f'ghcr.io/{REPO}'
+QUAY = f'quay.io/{REPO}'
-MAJOR = ':'.join([REPO, version[0]])
-MINOR = ':'.join([REPO, '.'.join(version[0:2])])
-PATCH = ':'.join([REPO, '.'.join(version[0:3])])
+tags = []
-print(','.join([MAJOR, MINOR, PATCH]))
+for repo in [REPO, GHCR, QUAY]:
+ tags.append(':'.join([repo, version[0]]))
+ tags.append(':'.join([repo, '.'.join(version[0:2])]))
+ tags.append(':'.join([repo, '.'.join(version[0:3])]))
+
+print(','.join(tags))
diff --git a/.github/scripts/gen-matrix-build.py b/.github/scripts/gen-matrix-build.py
new file mode 100755
index 00000000..28406470
--- /dev/null
+++ b/.github/scripts/gen-matrix-build.py
@@ -0,0 +1,34 @@
+#!/usr/bin/env python3
+
+import json
+
+from ruamel.yaml import YAML
+
+yaml = YAML(typ='safe')
+entries = []
+
+with open('.github/data/distros.yml') as f:
+ data = yaml.load(f)
+
+for i, v in enumerate(data['include']):
+ e = {
+ 'artifact_key': v['distro'] + str(v['version']).replace('.', ''),
+ 'version': v['version'],
+ }
+
+ if 'base_image' in v:
+ e['distro'] = ':'.join([v['base_image'], str(v['version'])])
+ else:
+ e['distro'] = ':'.join([v['distro'], str(v['version'])])
+
+ if 'env_prep' in v:
+ e['env_prep'] = v['env_prep']
+
+ if 'jsonc_removal' in v:
+ e['jsonc_removal'] = v['jsonc_removal']
+
+ entries.append(e)
+
+entries.sort(key=lambda k: k['distro'])
+matrix = json.dumps({'include': entries}, sort_keys=True)
+print(matrix)
diff --git a/.github/scripts/gen-matrix-packaging.py b/.github/scripts/gen-matrix-packaging.py
new file mode 100755
index 00000000..01e9ec79
--- /dev/null
+++ b/.github/scripts/gen-matrix-packaging.py
@@ -0,0 +1,36 @@
+#!/usr/bin/env python3
+
+import json
+import sys
+
+from ruamel.yaml import YAML
+
+ALWAYS_RUN_ARCHES = ["amd64", "x86_64"]
+SHORT_RUN = sys.argv[1]
+yaml = YAML(typ='safe')
+entries = list()
+run_limited = False
+
+with open('.github/data/distros.yml') as f:
+ data = yaml.load(f)
+
+if bool(int(SHORT_RUN)):
+ run_limited = True
+
+for i, v in enumerate(data['include']):
+ if 'packages' in data['include'][i]:
+ for arch in data['include'][i]['packages']['arches']:
+ if arch in ALWAYS_RUN_ARCHES or not run_limited:
+ entries.append({
+ 'distro': data['include'][i]['distro'],
+ 'version': data['include'][i]['version'],
+ 'repo_distro': data['include'][i]['packages']['repo_distro'],
+ 'format': data['include'][i]['packages']['type'],
+ 'base_image': data['include'][i]['base_image'] if 'base_image' in data['include'][i] else data['include'][i]['distro'],
+ 'platform': data['platform_map'][arch],
+ 'arch': arch
+ })
+
+entries.sort(key=lambda k: (data['arch_order'].index(k['arch']), k['distro'], k['version']))
+matrix = json.dumps({'include': entries}, sort_keys=True)
+print(matrix)
diff --git a/.github/scripts/gen-matrix-repoconfig.py b/.github/scripts/gen-matrix-repoconfig.py
new file mode 100755
index 00000000..46f67169
--- /dev/null
+++ b/.github/scripts/gen-matrix-repoconfig.py
@@ -0,0 +1,27 @@
+#!/usr/bin/env python3
+
+import json
+
+from ruamel.yaml import YAML
+
+yaml = YAML(typ='safe')
+entries = list()
+
+with open('.github/data/distros.yml') as f:
+ data = yaml.load(f)
+
+for i, v in enumerate(data['include']):
+ if 'packages' in data['include'][i]:
+ entries.append({
+ 'distro': data['include'][i]['distro'],
+ 'version': data['include'][i]['version'],
+ 'pkgclouddistro': data['include'][i]['packages']['repo_distro'],
+ 'format': data['include'][i]['packages']['type'],
+ 'base_image': data['include'][i]['base_image'] if 'base_image' in data['include'][i] else data['include'][i]['distro'],
+ 'platform': data['platform_map']['amd64'],
+ 'arches': ' '.join(['"' + x + '"' for x in data['include'][i]['packages']['arches']])
+ })
+
+entries.sort(key=lambda k: (k['distro'], k['version']))
+matrix = json.dumps({'include': entries}, sort_keys=True)
+print(matrix)
diff --git a/.github/scripts/get-static-cache-key.sh b/.github/scripts/get-static-cache-key.sh
index d9fa2859..3b07088f 100755
--- a/.github/scripts/get-static-cache-key.sh
+++ b/.github/scripts/get-static-cache-key.sh
@@ -12,4 +12,4 @@ docker run -it --rm --platform "${platform}" netdata/static-builder sh -c 'apk l
h="$(sha256sum /tmp/static-cache-key-data | cut -f 1 -d ' ')"
-echo "::set-output name=key::static-${arch}-${h}"
+echo "key=static-${arch}-${h}" >> "${GITHUB_OUTPUT}"
diff --git a/.github/scripts/package-upload.sh b/.github/scripts/package-upload.sh
index fd8a8cda..13d63b4a 100755
--- a/.github/scripts/package-upload.sh
+++ b/.github/scripts/package-upload.sh
@@ -19,7 +19,7 @@ mkdir -p "${staging}"
case "${format}" in
deb)
- src="${staging}/$(echo "${distro}" | cut -f 1 -d '/')/pool/"
+ src="${staging}/${distro}"
mkdir -p "${src}"
for pkg in ${packages}; do
diff --git a/.github/scripts/prepare-release-base.sh b/.github/scripts/prepare-release-base.sh
index 7c24f6b6..06a2da16 100755
--- a/.github/scripts/prepare-release-base.sh
+++ b/.github/scripts/prepare-release-base.sh
@@ -97,7 +97,7 @@ git config user.email "bot@netdata.cloud"
if [ "${REPO}" != "netdata/netdata" ] && [ -z "${RELEASE_TEST}" ]; then
echo "::notice::Not running in the netdata/netdata repository, not queueing a release build."
- echo "::set-output name=run::false"
+ echo "run=false" >> "${GITHUB_OUTPUT}"
elif [ "${EVENT_NAME}" = 'schedule' ] || [ "${EVENT_TYPE}" = 'nightly' ]; then
echo "::notice::Preparing a nightly release build."
LAST_TAG=$(git describe --abbrev=0 --tags)
@@ -107,15 +107,16 @@ elif [ "${EVENT_NAME}" = 'schedule' ] || [ "${EVENT_TYPE}" = 'nightly' ]; then
HEAD_COMMIT="$(git rev-parse HEAD)"
if [ "${EVENT_NAME}" = 'schedule' ] && [ "${LAST_VERSION_COMMIT}" = "${HEAD_COMMIT}" ] && grep -qE '.*-nightly$' packaging/version; then
echo "::notice::No commits since last nightly build, not publishing a new nightly build."
- echo "::set-output name=run::false"
+ echo "run=false" >> "${GITHUB_OUTPUT}"
else
echo "${NEW_VERSION}" > packaging/version || exit 1
- echo "::set-output name=run::true"
- echo "::set-output name=message::Update changelog and version for nightly build: ${NEW_VERSION}."
- echo "::set-output name=ref::master"
- echo "::set-output name=type::nightly"
- echo "::set-output name=branch::master"
- echo "::set-output name=version::nightly"
+ # shellcheck disable=SC2129
+ echo "run=true" >> "${GITHUB_OUTPUT}"
+ echo "message=Update changelog and version for nightly build: ${NEW_VERSION}." >> "${GITHUB_OUTPUT}"
+ echo "ref=master" >> "${GITHUB_OUTPUT}"
+ echo "type=nightly" >> "${GITHUB_OUTPUT}"
+ echo "branch=master" >> "${GITHUB_OUTPUT}"
+ echo "version=nightly" >> "${GITHUB_OUTPUT}"
fi
elif [ "${EVENT_TYPE}" = 'patch' ] && [ "${EVENT_VERSION}" != "nightly" ]; then
echo "::notice::Preparing a patch release build."
@@ -130,12 +131,13 @@ elif [ "${EVENT_TYPE}" = 'patch' ] && [ "${EVENT_VERSION}" != "nightly" ]; then
major_matches || exit 1
check_newer_patch_version || exit 1
echo "${EVENT_VERSION}" > packaging/version || exit 1
- echo "::set-output name=run::true"
- echo "::set-output name=message::Patch release ${EVENT_VERSION}."
- echo "::set-output name=ref::${EVENT_VERSION}"
- echo "::set-output name=type::release"
- echo "::set-output name=branch::${branch_name}"
- echo "::set-output name=version::$(tr -d 'v' < packaging/version)"
+ # shellcheck disable=SC2129
+ echo "run=true" >> "${GITHUB_OUTPUT}"
+ echo "message=Patch release ${EVENT_VERSION}." >> "${GITHUB_OUTPUT}"
+ echo "ref=${EVENT_VERSION}" >> "${GITHUB_OUTPUT}"
+ echo "type=release" >> "${GITHUB_OUTPUT}"
+ echo "branch=${branch_name}" >> "${GITHUB_OUTPUT}"
+ echo "version=$(tr -d 'v' < packaging/version)" >> "${GITHUB_OUTPUT}"
elif [ "${EVENT_TYPE}" = 'minor' ] && [ "${EVENT_VERSION}" != "nightly" ]; then
echo "::notice::Preparing a minor release build."
check_version_format || exit 1
@@ -149,13 +151,14 @@ elif [ "${EVENT_TYPE}" = 'minor' ] && [ "${EVENT_VERSION}" != "nightly" ]; then
exit 1
fi
echo "${EVENT_VERSION}" > packaging/version || exit 1
- echo "::set-output name=run::true"
- echo "::set-output name=message::Minor release ${EVENT_VERSION}."
- echo "::set-output name=ref::${EVENT_VERSION}"
- echo "::set-output name=type::release"
- echo "::set-output name=branch::master"
- echo "::set-output name=new-branch::${branch_name}"
- echo "::set-output name=version::$(tr -d 'v' < packaging/version)"
+ # shellcheck disable=SC2129
+ echo "run=true" >> "${GITHUB_OUTPUT}"
+ echo "message=Minor release ${EVENT_VERSION}." >> "${GITHUB_OUTPUT}"
+ echo "ref=${EVENT_VERSION}" >> "${GITHUB_OUTPUT}"
+ echo "type=release" >> "${GITHUB_OUTPUT}"
+ echo "branch=master" >> "${GITHUB_OUTPUT}"
+ echo "new-branch=${branch_name}" >> "${GITHUB_OUTPUT}"
+ echo "version=$(tr -d 'v' < packaging/version)" >> "${GITHUB_OUTPUT}"
elif [ "${EVENT_TYPE}" = 'major' ] && [ "${EVENT_VERSION}" != "nightly" ]; then
echo "::notice::Preparing a major release build."
check_version_format || exit 1
@@ -164,12 +167,13 @@ elif [ "${EVENT_TYPE}" = 'major' ] && [ "${EVENT_VERSION}" != "nightly" ]; then
check_newer_major_version || exit 1
check_for_existing_tag || exit 1
echo "${EVENT_VERSION}" > packaging/version || exit 1
- echo "::set-output name=run::true"
- echo "::set-output name=message::Major release ${EVENT_VERSION}"
- echo "::set-output name=ref::${EVENT_VERSION}"
- echo "::set-output name=type::release"
- echo "::set-output name=branch::master"
- echo "::set-output name=version::$(tr -d 'v' < packaging/version)"
+ # shellcheck disable=SC2129
+ echo "run=true" >> "${GITHUB_OUTPUT}"
+ echo "message=Major release ${EVENT_VERSION}" >> "${GITHUB_OUTPUT}"
+ echo "ref=${EVENT_VERSION}" >> "${GITHUB_OUTPUT}"
+ echo "type=release" >> "${GITHUB_OUTPUT}"
+ echo "branch=master" >> "${GITHUB_OUTPUT}"
+ echo "version=$(tr -d 'v' < packaging/version)" >> "${GITHUB_OUTPUT}"
else
echo '::error::Unrecognized release type or invalid version.'
exit 1
diff --git a/.github/scripts/run-updater-check.sh b/.github/scripts/run-updater-check.sh
index 31ab71de..a96a1d6e 100755
--- a/.github/scripts/run-updater-check.sh
+++ b/.github/scripts/run-updater-check.sh
@@ -4,11 +4,26 @@ echo ">>> Installing CI support packages..."
/netdata/.github/scripts/ci-support-pkgs.sh
echo ">>> Installing Netdata..."
/netdata/packaging/installer/kickstart.sh --dont-wait --build-only --disable-telemetry || exit 1
-echo "::group::Environment File Contents"
+echo "::group::>>> Pre-Update Environment File Contents"
cat /etc/netdata/.environment
echo "::endgroup::"
+echo "::group::>>> Pre-Update Netdata Build Info"
+netdata -W buildinfo
+echo "::endgroup::"
echo ">>> Updating Netdata..."
-export NETDATA_NIGHTLIES_BASEURL="http://localhost:8080/artifacts/" # Pull the tarball from the local web server.
-/netdata/packaging/installer/netdata-updater.sh --not-running-from-cron --no-updater-self-update || exit 1
+export NETDATA_BASE_URL="http://localhost:8080/artifacts/" # Pull the tarball from the local web server.
+timeout 3600 /netdata/packaging/installer/netdata-updater.sh --not-running-from-cron --no-updater-self-update
+
+case "$?" in
+ 124) echo "!!! Updater timed out." ; exit 1 ;;
+ 0) ;;
+ *) echo "!!! Updater failed." ; exit 1 ;;
+esac
+echo "::group::>>> Post-Update Environment File Contents"
+cat /etc/netdata/.environment
+echo "::endgroup::"
+echo "::group::>>> Post-Update Netdata Build Info"
+netdata -W buildinfo
+echo "::endgroup::"
echo ">>> Checking if update was successful..."
/netdata/.github/scripts/check-updater.sh || exit 1
diff --git a/.github/scripts/run_install_with_dist_file.sh b/.github/scripts/run_install_with_dist_file.sh
index d59e8b13..74652efd 100755
--- a/.github/scripts/run_install_with_dist_file.sh
+++ b/.github/scripts/run_install_with_dist_file.sh
@@ -33,7 +33,7 @@ docker run \
-v "${PWD}:/netdata" \
-w /netdata \
"ubuntu:latest" \
- /bin/bash -c "./install-required-packages.sh --dont-wait --non-interactive netdata && apt install wget && ./netdata-installer.sh --dont-wait --require-cloud --disable-telemetry --install /tmp --one-time-build && echo \"Validating netdata instance is running\" && wget -O - 'http://127.0.0.1:19999/api/v1/info' | grep version"
+ /bin/bash -c "./install-required-packages.sh --dont-wait --non-interactive netdata && apt install wget && ./netdata-installer.sh --dont-wait --require-cloud --disable-telemetry --install-prefix /tmp --one-time-build && echo \"Validating netdata instance is running\" && wget -O - 'http://127.0.0.1:19999/api/v1/info' | grep version"
popd || exit 1
echo "All Done!"
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 53f1590f..c3924fb0 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -57,7 +57,7 @@ jobs:
--with-math \
--with-user=netdata
make dist
- echo "::set-output name=distfile::$(find . -name 'netdata-*.tar.gz')"
+ echo "distfile=$(find . -name 'netdata-*.tar.gz')" >> "${GITHUB_OUTPUT}"
cp netdata-*.tar.gz artifacts/
- name: Store
id: store
@@ -171,6 +171,7 @@ jobs:
matrix: # Generate the shared build matrix for our build tests.
name: Prepare Build Matrix
runs-on: ubuntu-latest
+ if: github.event_name != 'workflow_dispatch'
outputs:
matrix: ${{ steps.set-matrix.outputs.matrix }}
steps:
@@ -183,39 +184,10 @@ jobs:
sudo apt-get update && sudo apt-get install -y python3-ruamel.yaml
- name: Read build matrix
id: set-matrix
- shell: python3 {0}
run: |
- from ruamel.yaml import YAML
- import json
- yaml = YAML(typ='safe')
- entries = list()
-
- with open('.github/data/distros.yml') as f:
- data = yaml.load(f)
-
- for i, v in enumerate(data['include']):
- e = {
- 'artifact_key': v['distro'] + str(v['version']).replace('.', ''),
- 'version': v['version'],
- }
-
- if 'base_image' in v:
- e['distro'] = ':'.join([v['base_image'], str(v['version'])])
- else:
- e['distro'] = ':'.join([v['distro'], str(v['version'])])
-
- if 'env_prep' in v:
- e['env_prep'] = v['env_prep']
-
- if 'jsonc_removal' in v:
- e['jsonc_removal'] = v['jsonc_removal']
-
- entries.append(e)
-
- entries.sort(key=lambda k: k['distro'])
- matrix = json.dumps({'include': entries}, sort_keys=True)
- print('Generated Matrix: ' + matrix)
- print('::set-output name=matrix::' + matrix)
+ matrix="$(.github/scripts/gen-matrix-build.py)"
+ echo "Generated matrix: ${matrix}"
+ echo "matrix=${matrix}" >> "${GITHUB_OUTPUT}"
- name: Failure Notification
uses: rtCamp/action-slack-notify@v2
env:
@@ -241,12 +213,13 @@ jobs:
prepare-test-images: # Prepare the test environments for our build checks. This also checks dependency handling code for each tested environment.
name: Prepare Test Environments
runs-on: ubuntu-latest
+ if: github.event_name != 'workflow_dispatch'
needs:
- matrix
env:
RETRY_DELAY: 300
strategy:
- # Unlike the actal build tests, this completes _very_ fast (average of about 3 minutes for each job), so we
+ # Unlike the actual build tests, this completes _very_ fast (average of about 3 minutes for each job), so we
# just run everything in parallel instead lof limiting job concurrency.
fail-fast: false
matrix: ${{ fromJson(needs.matrix.outputs.matrix) }}
@@ -269,7 +242,7 @@ jobs:
BASE=${{ matrix.distro }}
PRE=${{ matrix.env_prep }}
RMJSONC=${{ matrix.jsonc_removal }}
- outputs: type=oci,dest=/tmp/image.tar
+ outputs: type=docker,dest=/tmp/image.tar
tags: test:${{ matrix.artifact_key }}
- name: Retry delay
if: ${{ steps.build1.outcome == 'failure' }}
@@ -287,7 +260,7 @@ jobs:
BASE=${{ matrix.distro }}
PRE=${{ matrix.env_prep }}
RMJSONC=${{ matrix.jsonc_removal }}
- outputs: type=oci,dest=/tmp/image.tar
+ outputs: type=docker,dest=/tmp/image.tar
tags: test:${{ matrix.artifact_key }}
- name: Retry delay
if: ${{ steps.build1.outcome == 'failure' && steps.build2.outcome == 'failure' }}
@@ -304,7 +277,7 @@ jobs:
BASE=${{ matrix.distro }}
PRE=${{ matrix.env_prep }}
RMJSONC=${{ matrix.jsonc_removal }}
- outputs: type=oci,dest=/tmp/image.tar
+ outputs: type=docker,dest=/tmp/image.tar
tags: test:${{ matrix.artifact_key }}
- name: Upload image artifact
id: upload
@@ -341,6 +314,7 @@ jobs:
source-build: # Test various source build arrangements.
name: Test Source Build
runs-on: ubuntu-latest
+ if: github.event_name != 'workflow_dispatch'
needs:
- matrix
- prepare-test-images
@@ -361,29 +335,27 @@ jobs:
name: ${{ matrix.artifact_key }}-test-env
- name: Load test environment
id: load
- run: |
- docker load --input image.tar | tee image-info.txt
- echo "::set-output name=image::$(cut -d ':' -f 3 image-info.txt)"
+ run: docker load --input image.tar
- name: Regular build on ${{ matrix.distro }}
id: build-basic
run: |
- docker run --security-opt seccomp=unconfined -w /netdata sha256:${{ steps.load.outputs.image }} \
+ docker run --security-opt seccomp=unconfined -w /netdata test:${{ matrix.artifact_key }} \
/bin/sh -c 'autoreconf -ivf && ./configure --disable-dependency-tracking && make -j2'
- name: netdata-installer on ${{ matrix.distro }}, disable cloud
id: build-no-cloud
run: |
- docker run --security-opt seccomp=unconfined -w /netdata sha256:${{ steps.load.outputs.image }} \
+ docker run --security-opt seccomp=unconfined -w /netdata test:${{ matrix.artifact_key }} \
/bin/sh -c './netdata-installer.sh --dont-wait --dont-start-it --disable-cloud --one-time-build'
- name: netdata-installer on ${{ matrix.distro }}, require cloud
id: build-cloud
run: |
- docker run --security-opt seccomp=unconfined -w /netdata sha256:${{ steps.load.outputs.image }} \
+ docker run --security-opt seccomp=unconfined -w /netdata test:${{ matrix.artifact_key }} \
/bin/sh -c './netdata-installer.sh --dont-wait --dont-start-it --require-cloud --one-time-build'
- name: netdata-installer on ${{ matrix.distro }}, require cloud, no JSON-C
id: build-no-jsonc
if: matrix.jsonc_removal != ''
run: |
- docker run --security-opt seccomp=unconfined -w /netdata sha256:${{ steps.load.outputs.image }} \
+ docker run --security-opt seccomp=unconfined -w /netdata test:${{ matrix.artifact_key }} \
/bin/sh -c '/rmjsonc.sh && ./netdata-installer.sh --dont-wait --dont-start-it --require-cloud --one-time-build'
- name: Failure Notification
uses: rtCamp/action-slack-notify@v2
@@ -414,6 +386,7 @@ jobs:
updater-check: # Test the generated dist archive using the updater code.
name: Test Generated Distfile and Updater Code
runs-on: ubuntu-latest
+ if: github.event_name != 'workflow_dispatch'
needs:
- build-dist
- matrix
@@ -442,10 +415,10 @@ jobs:
- name: Prepare artifact directory
id: prepare
run: |
- mkdir -p artifacts || exit 1
- echo "9999.0.0-0" > artifacts/latest-version.txt || exit 1
- cp dist-tarball/* artifacts || exit 1
- cd artifacts || exit 1
+ mkdir -p artifacts/download/latest || exit 1
+ echo "9999.0.0-0" > artifacts/download/latest/latest-version.txt || exit 1
+ cp dist-tarball/* artifacts/download/latest || exit 1
+ cd artifacts/download/latest || exit 1
ln -s ${{ needs.build-dist.outputs.distfile }} netdata-latest.tar.gz || exit 1
sha256sum -b ./* > "sha256sums.txt" || exit 1
cat sha256sums.txt
@@ -456,13 +429,11 @@ jobs:
name: ${{ matrix.artifact_key }}-test-env
- name: Load test environment
id: load
- run: |
- docker load --input image.tar | tee image-info.txt
- echo "::set-output name=image::$(cut -d ':' -f 3 image-info.txt)"
+ run: docker load --input image.tar
- name: Install netdata and run the updater on ${{ matrix.distro }}
id: updater-check
run: |
- docker run --security-opt seccomp=unconfined -e DISABLE_TELEMETRY=1 --network host -w /netdata sha256:${{ steps.load.outputs.image }} \
+ docker run --security-opt seccomp=unconfined -e DISABLE_TELEMETRY=1 --network host -w /netdata test:${{ matrix.artifact_key }} \
/netdata/.github/scripts/run-updater-check.sh
- name: Failure Notification
uses: rtCamp/action-slack-notify@v2
@@ -578,10 +549,15 @@ jobs:
with:
name: final-artifacts
path: artifacts
+ - name: Prepare artifacts directory
+ id: prepare
+ run: |
+ mkdir -p download/latest
+ mv artifacts/* download/latest
- name: Verify that artifacts work with installer
id: verify
env:
- NETDATA_TARBALL_BASEURL: http://localhost:8080/artifacts
+ NETDATA_TARBALL_BASEURL: http://localhost:8080/
run: packaging/installer/kickstart.sh --build-only --dont-start-it --disable-telemetry --dont-wait
- name: Failure Notification
uses: rtCamp/action-slack-notify@v2
@@ -627,10 +603,15 @@ jobs:
with:
name: final-artifacts
path: artifacts
+ - name: Prepare artifacts directory
+ id: prepare
+ run: |
+ mkdir -p download/latest
+ mv artifacts/* download/latest
- name: Verify that artifacts work with installer
id: verify
env:
- NETDATA_TARBALL_BASEURL: http://localhost:8080/artifacts
+ NETDATA_TARBALL_BASEURL: http://localhost:8080/
run: packaging/installer/kickstart.sh --static-only --dont-start-it --disable-telemetry
- name: Failure Notification
uses: rtCamp/action-slack-notify@v2
@@ -659,8 +640,6 @@ jobs:
runs-on: ubuntu-latest
if: github.event_name == 'workflow_dispatch' && github.event.inputs.type == 'nightly' && github.repository == 'netdata/netdata'
needs:
- - updater-check
- - source-build
- artifact-verification-dist
- artifact-verification-static
steps:
@@ -714,8 +693,6 @@ jobs:
runs-on: ubuntu-latest
if: github.event_name == 'workflow_dispatch' && github.event.inputs.type == 'nightly' && github.repository == 'netdata/netdata'
needs:
- - updater-check
- - source-build
- artifact-verification-dist
- artifact-verification-static
steps:
@@ -755,6 +732,7 @@ jobs:
repo: netdata-nightlies
body: Netdata nightly build for ${{ steps.version.outputs.date }}.
commit: ${{ steps.version.outputs.commit }}
+ makeLatest: true
tag: ${{ steps.version.outputs.version }}
token: ${{ secrets.NETDATABOT_GITHUB_TOKEN }}
- name: Failure Notification
@@ -790,9 +768,9 @@ jobs:
id: tag
run: |
if echo ${{ github.event.inputs.version }} | grep -qE '^[[:digit:]]+\.[[:digit:]]+\.[[:digit:]]+$'; then
- echo "::set-output name=tag::v${{ github.event.inputs.version }}"
+ echo "tag=v${{ github.event.inputs.version }}" >> "${GITHUB_OUTPUT}"
else
- echo "::set-output name=tag::${{ github.event.inputs.version }}"
+ echo "tag=${{ github.event.inputs.version }}" >> "${GITHUB_OUTPUT}"
fi
upload-release: # Create the draft release and upload the build artifacts.
@@ -800,8 +778,6 @@ jobs:
runs-on: ubuntu-latest
if: github.event_name == 'workflow_dispatch' && github.event.inputs.type == 'release' && github.repository == 'netdata/netdata'
needs:
- - updater-check
- - source-build
- artifact-verification-dist
- artifact-verification-static
- normalize-tag
diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml
index 65ad6acb..799f8d99 100644
--- a/.github/workflows/checks.yml
+++ b/.github/workflows/checks.yml
@@ -51,7 +51,7 @@ jobs:
- name: Prepare environment
run: ./packaging/installer/install-required-packages.sh --dont-wait --non-interactive netdata
- name: Build netdata
- run: ./netdata-installer.sh --dont-start-it --disable-telemetry --dont-wait --install /tmp/install --one-time-build
+ run: ./netdata-installer.sh --dont-start-it --disable-telemetry --dont-wait --install-prefix /tmp/install --one-time-build
- name: Check that repo is clean
run: |
git status --porcelain=v1 > /tmp/porcelain
diff --git a/.github/workflows/cloud_regression.yml b/.github/workflows/cloud_regression.yml
index b6e321fe..01fcdca4 100644
--- a/.github/workflows/cloud_regression.yml
+++ b/.github/workflows/cloud_regression.yml
@@ -33,12 +33,12 @@ jobs:
NETDATA_CUSTOM_PR_NUMBER=""
NETDATA_CUSTOM_COMMIT_HASH="${{ github.sha }}"
fi
- echo "::set-output name=netdata_repo::${NETDATA_CUSTOM_REPO}"
- echo "::set-output name=netdata_branch::${NETDATA_CUSTOM_BRANCH}"
- echo "::set-output name=netdata_pr_number::${NETDATA_CUSTOM_PR_NUMBER}"
- echo "::set-output name=netdata_commit_hash::${NETDATA_CUSTOM_COMMIT_HASH}"
+ echo "netdata_repo=${NETDATA_CUSTOM_REPO}" >> $GITHUB_OUTPUT
+ echo "netdata_branch=${NETDATA_CUSTOM_BRANCH}" >> $GITHUB_OUTPUT
+ echo "netdata_pr_number=${NETDATA_CUSTOM_PR_NUMBER}" >> $GITHUB_OUTPUT
+ echo "netdata_commit_hash=${NETDATA_CUSTOM_COMMIT_HASH}" >> $GITHUB_OUTPUT
- - name: Trigger Cloud Regression
+ - name: Trigger Full Cloud Regression
uses: aurelien-baudet/workflow-dispatch@v2
with:
repo: netdata/test-automation
@@ -52,3 +52,18 @@ jobs:
"custom_netdata_image": "true"
}'
wait-for-completion: false
+
+ - name: Trigger Agent Parent/Child with Cloud Integration tests
+ uses: aurelien-baudet/workflow-dispatch@v2
+ with:
+ repo: netdata/test-automation
+ ref: refs/heads/master
+ workflow: agent_smoke_tests.yml
+ token: ${{ secrets.NETDATABOT_GITHUB_TOKEN }}
+ inputs: '{ "netdata_branch": "${{ steps.output-workflow-dispatch-params.outputs.netdata_branch }}",
+ "netdata_repo": "${{ steps.output-workflow-dispatch-params.outputs.netdata_repo }}",
+ "netdata_pr_number": "${{ steps.output-workflow-dispatch-params.outputs.netdata_pr_number }}",
+ "netdata_branch_commit_hash": "${{ steps.output-workflow-dispatch-params.outputs.netdata_commit_hash }}",
+ "custom_netdata_image": "true"
+ }'
+ wait-for-completion: true
diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml
index 021376a2..b2af615e 100644
--- a/.github/workflows/codeql.yml
+++ b/.github/workflows/codeql.yml
@@ -32,39 +32,39 @@ jobs:
run: |
if [ "${{ github.event_name }}" = "pull_request" ]; then
if [ "${{ contains(github.event.pull_request.labels.*.name, 'run-ci/codeql') }}" = "true" ]; then
- echo '::set-output name=run::true'
+ echo "run=true" >> "${GITHUB_OUTPUT}"
echo '::notice::Found ci/codeql label, unconditionally running all CodeQL checks.'
else
- echo '::set-output name=run::false'
+ echo "run=false" >> "${GITHUB_OUTPUT}"
fi
else
- echo '::set-output name=run::true'
+ echo "run=true" >> "${GITHUB_OUTPUT}"
fi
- name: Check for C/C++ changes
id: cpp
run: |
if [ "${{ steps.always.outputs.run }}" = "false" ]; then
if git diff --name-only origin/${{ github.base_ref }} HEAD | grep -Eq '.*\.[ch](xx|\+\+)?' ; then
- echo '::set-output name=run::true'
+ echo "run=true" >> "${GITHUB_OUTPUT}"
echo '::notice::C/C++ code has changed, need to run CodeQL.'
else
- echo '::set-output name=run::false'
+ echo "run=false" >> "${GITHUB_OUTPUT}"
fi
else
- echo '::set-output name=run::true'
+ echo "run=true" >> "${GITHUB_OUTPUT}"
fi
- name: Check for python changes
id: python
run: |
if [ "${{ steps.always.outputs.run }}" = "false" ]; then
if git diff --name-only origin/${{ github.base_ref }} HEAD | grep -Eq 'collectors/python.d.plugin/.*\.py' ; then
- echo '::set-output name=run::true'
+ echo "run=true" >> "${GITHUB_OUTPUT}"
echo '::notice::Python code has changed, need to run CodeQL.'
else
- echo '::set-output name=run::false'
+ echo "run=false" >> "${GITHUB_OUTPUT}"
fi
else
- echo '::set-output name=run::true'
+ echo "run=true" >> "${GITHUB_OUTPUT}"
fi
analyze-cpp:
@@ -87,7 +87,7 @@ jobs:
- name: Prepare environment
run: ./packaging/installer/install-required-packages.sh --dont-wait --non-interactive netdata
- name: Build netdata
- run: ./netdata-installer.sh --dont-start-it --disable-telemetry --dont-wait --install /tmp/install --one-time-build
+ run: ./netdata-installer.sh --dont-start-it --disable-telemetry --dont-wait --install-prefix /tmp/install --one-time-build
- name: Run CodeQL
uses: github/codeql-action/analyze@v2
with:
diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml
index b7eb53c8..78a39d5a 100644
--- a/.github/workflows/docker.yml
+++ b/.github/workflows/docker.yml
@@ -129,9 +129,9 @@ jobs:
id: tag
run: |
if echo ${{ github.event.inputs.version }} | grep -qE '^[[:digit:]]+\.[[:digit:]]+\.[[:digit:]]+$'; then
- echo "::set-output name=tag::v${{ github.event.inputs.version }}"
+ echo "tag=v${{ github.event.inputs.version }}" >> "${GITHUB_OUTPUT}"
else
- echo "::set-output name=tag::${{ github.event.inputs.version }}"
+ echo "tag=${{ github.event.inputs.version }}" >> "${GITHUB_OUTPUT}"
fi
docker-publish:
@@ -151,13 +151,13 @@ jobs:
id: release-tags
if: github.event.inputs.version != 'nightly'
run: |
- echo "tags=netdata/netdata:latest,netdata/netdata:stable,$(.github/scripts/gen-docker-tags.py ${{ needs.normalize-tag.outputs.tag }} '')" \
+ echo "tags=netdata/netdata:latest,netdata/netdata:stable,ghcr.io/netdata/netdata:latest,ghcr.io/netdata/netdata:stable,quay.io/netdata/netdata:latest,quay.io/netdata/netdata:stable,$(.github/scripts/gen-docker-tags.py ${{ needs.normalize-tag.outputs.tag }} '')" \
>> "${GITHUB_ENV}"
- name: Determine which tags to use
id: nightly-tags
if: github.event.inputs.version == 'nightly'
run: |
- echo "tags=netdata/netdata:latest,netdata/netdata:edge" >> "${GITHUB_ENV}"
+ echo "tags=netdata/netdata:latest,netdata/netdata:edge,ghcr.io/netdata/netdata:latest,ghcr.io/netdata/netdata:edge,quay.io/netdata/netdata:latest,quay.io/netdata/netdata:edge" >> "${GITHUB_ENV}"
- name: Mark image as official
id: env
if: github.repository == 'netdata/netdata'
@@ -169,12 +169,28 @@ jobs:
id: buildx
uses: docker/setup-buildx-action@v2
- name: Docker Hub Login
- id: login
+ id: docker-hub-login
if: github.repository == 'netdata/netdata'
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKER_HUB_USERNAME }}
password: ${{ secrets.DOCKER_HUB_PASSWORD }}
+ - name: GitHub Container Registry Login
+ id: ghcr-login
+ if: github.repository == 'netdata/netdata'
+ uses: docker/login-action@v2
+ with:
+ registry: ghcr.io
+ username: ${{ github.repository_owner }}
+ password: ${{ secrets.GITHUB_TOKEN }}
+ - name: Quay.io Login
+ id: quay-login
+ if: github.repository == 'netdata/netdata'
+ uses: docker/login-action@v2
+ with:
+ registry: quay.io
+ username: ${{ secrets.NETDATABOT_QUAY_USERNAME }}
+ password: ${{ secrets.NETDATABOT_QUAY_TOKEN }}
- name: Docker Build
id: build
uses: docker/build-push-action@v3
@@ -199,7 +215,9 @@ jobs:
Setup environment: ${{ steps.env.outcome }}
Setup QEMU: ${{ steps.qemu.outcome }}
Setup buildx: ${{ steps.buildx.outcome }}
- Authenticate against DockerHub: ${{ steps.login.outcome }}
+ Login to DockerHub: ${{ steps.docker-hub-login.outcome }}
+ Login to GHCR: ${{ steps.ghcr-login.outcome }}
+ Login to Quay: ${{ steps.quay-login.outcome }}
Build and publish images: ${{ steps.build.outcome }}
SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK_URL }}
if: >-
@@ -221,7 +239,7 @@ jobs:
docker-dbg-publish:
if: github.event_name == 'workflow_dispatch'
- name: Docker Build and Publish (Debuging Image)
+ name: Docker Build and Publish (Debugging Image)
needs:
- docker-test
- normalize-tag
@@ -236,13 +254,13 @@ jobs:
id: release-tags
if: github.event.inputs.version != 'nightly'
run: |
- echo "tags=netdata/netdata-debug:latest,netdata/netdata-debug:stable,$(.github/scripts/gen-docker-tags.py ${{ needs.normalize-tag.outputs.tag }} '-debug')" \
+ echo "tags=netdata/netdata-debug:latest,netdata/netdata-debug:stable,ghcr.io/netdata/netdata-debug:latest,ghcr.io/netdata/netdata-debug:stable,quay.io/netdata/netdata-debug:latest,quay.io/netdata/netdata-debug:stable,$(.github/scripts/gen-docker-tags.py ${{ needs.normalize-tag.outputs.tag }} '-debug')" \
>> "${GITHUB_ENV}"
- name: Determine which tags to use
id: nightly-tags
if: github.event.inputs.version == 'nightly'
run: |
- echo "tags=netdata/netdata-debug:latest,netdata/netdata-debug:edge" >> "${GITHUB_ENV}"
+ echo "tags=netdata/netdata-debug:latest,netdata/netdata-debug:edge,ghcr.io/netdata/netdata-debug:latest,ghcr.io/netdata/netdata-debug:edge,quay.io/netdata/netdata-debug:latest,quay.io/netdata/netdata-debug:edge" >> "${GITHUB_ENV}"
- name: Mark image as official
id: env
if: github.repository == 'netdata/netdata'
@@ -254,12 +272,28 @@ jobs:
id: buildx
uses: docker/setup-buildx-action@v2
- name: Docker Hub Login
- id: login
+ id: docker-hub-login
if: github.repository == 'netdata/netdata'
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKER_HUB_USERNAME }}
password: ${{ secrets.DOCKER_HUB_PASSWORD }}
+ - name: GitHub Container Registry Login
+ id: ghcr-login
+ if: github.repository == 'netdata/netdata'
+ uses: docker/login-action@v2
+ with:
+ registry: ghcr.io
+ username: ${{ github.repository_owner }}
+ password: ${{ secrets.GITHUB_TOKEN }}
+ - name: Quay.io Login
+ id: quay-login
+ if: github.repository == 'netdata/netdata'
+ uses: docker/login-action@v2
+ with:
+ registry: quay.io
+ username: ${{ secrets.NETDATABOT_QUAY_USERNAME }}
+ password: ${{ secrets.NETDATABOT_QUAY_TOKEN }}
- name: Docker Build
id: build
uses: docker/build-push-action@v3
@@ -280,13 +314,15 @@ jobs:
SLACK_USERNAME: 'GitHub Actions'
SLACK_MESSAGE: |-
${{ github.repository }}: Failed to build or publish Docker debug images.
- CHeckout: ${{ steps.checkout.outcome }}
+ Checkout: ${{ steps.checkout.outcome }}
Generate release tags: ${{ steps.release-tags.outcome }}
Generate nightly tags: ${{ steps.nightly-tags.outcome }}
Setup environment: ${{ steps.env.outcome }}
Setup QEMU: ${{ steps.qemu.outcome }}
Setup buildx: ${{ steps.buildx.outcome }}
- Authenticate against DockerHub: ${{ steps.login.outcome }}
+ Login to DockerHub: ${{ steps.docker-hub-login.outcome }}
+ Login to GHCR: ${{ steps.ghcr-login.outcome }}
+ Login to Quay: ${{ steps.quay-login.outcome }}
Build and publish images: ${{ steps.build.outcome }}
SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK_URL }}
if: >-
diff --git a/.github/workflows/labeler.yml b/.github/workflows/labeler.yml
index 0854080a..2b8b41fc 100644
--- a/.github/workflows/labeler.yml
+++ b/.github/workflows/labeler.yml
@@ -2,17 +2,20 @@
# Handles labelling of PR's.
name: Pull Request Labeler
on:
- schedule:
- - cron: '*/10 * * * *'
-env:
- DISABLE_TELEMETRY: 1
+ pull_request_target: null
+concurrency:
+ group: pr-label-${{ github.ref }}
+ cancel-in-progress: true
jobs:
labeler:
+ name: Apply PR Labels
runs-on: ubuntu-latest
+ permissions:
+ contents: read
+ pull-requests: write
steps:
- - uses: docker://docker.io/ilyam8/periodic-pr-labeler:v0.1.0
+ - uses: actions/labeler@v4
if: github.repository == 'netdata/netdata'
- env:
- GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- GITHUB_REPOSITORY: ${{ github.repository }}
- LABEL_MAPPINGS_FILE: .github/labeler.yml
+ with:
+ repo-token: "${{ secrets.GITHUB_TOKEN }}"
+ sync-labels: true
diff --git a/.github/workflows/packaging.yml b/.github/workflows/packaging.yml
index ddd8356e..c99f535a 100644
--- a/.github/workflows/packaging.yml
+++ b/.github/workflows/packaging.yml
@@ -44,41 +44,15 @@ jobs:
sudo apt-get update && sudo apt-get install -y python3-ruamel.yaml
- name: Read build matrix
id: set-matrix
- shell: python3 {0}
run: |
- from ruamel.yaml import YAML
- import json
- import re
- import os
- ALWAYS_RUN_ARCHES = ["amd64", "x86_64"]
- yaml = YAML(typ='safe')
- entries = list()
- run_limited = False
-
- with open('.github/data/distros.yml') as f:
- data = yaml.load(f)
-
- if "${{ github.event_name }}" == "pull_request" and "${{ !contains(github.event.pull_request.labels.*.name, 'run-ci/packaging') }}":
- run_limited = True
-
- for i, v in enumerate(data['include']):
- if 'packages' in data['include'][i]:
- for arch in data['include'][i]['packages']['arches']:
- if arch in ALWAYS_RUN_ARCHES or not run_limited:
- entries.append({
- 'distro': data['include'][i]['distro'],
- 'version': data['include'][i]['version'],
- 'repo_distro': data['include'][i]['packages']['repo_distro'],
- 'format': data['include'][i]['packages']['type'],
- 'base_image': data['include'][i]['base_image'] if 'base_image' in data['include'][i] else data['include'][i]['distro'],
- 'platform': data['platform_map'][arch],
- 'arch': arch
- })
-
- entries.sort(key=lambda k: (data['arch_order'].index(k['arch']), k['distro'], k['version']))
- matrix = json.dumps({'include': entries}, sort_keys=True)
- print('Generated Matrix: ' + matrix)
- print('::set-output name=matrix::' + matrix)
+ if [ "${{ github.event_name }}" = "pull_request" ] && \
+ [ "${{ !contains(github.event.pull_request.labels.*.name, 'run-ci/packaging') }}" = "true" ]; then
+ matrix="$(.github/scripts/gen-matrix-packaging.py 1)"
+ else
+ matrix="$(.github/scripts/gen-matrix-packaging.py 0)"
+ fi
+ echo "Generated matrix: ${matrix}"
+ echo "matrix=${matrix}" >> "${GITHUB_OUTPUT}"
- name: Failure Notification
uses: rtCamp/action-slack-notify@v2
env:
@@ -117,24 +91,24 @@ jobs:
if [ "${{ github.event_name }}" == "workflow_dispatch" ]; then
case "${{ github.event.inputs.type }}" in
"release")
- echo "::set-output name=repo::${REPO_PREFIX}"
- echo "::set-output name=version::${{ github.event.inputs.version }}"
- echo "::set-output name=retention::365"
+ echo "repo=${REPO_PREFIX}" >> "${GITHUB_OUTPUT}"
+ echo "version=${{ github.event.inputs.version }}" >> "${GITHUB_OUTPUT}"
+ echo "retention=365" >> "${GITHUB_OUTPUT}"
;;
"nightly")
- echo "::set-output name=repo::${REPO_PREFIX}-edge"
- echo "::set-output name=version::$(tr -d 'v' < packaging/version)"
- echo "::set-output name=retention::30"
+ echo "repo=${REPO_PREFIX}-edge" >> "${GITHUB_OUTPUT}"
+ echo "version=$(tr -d 'v' < packaging/version)" >> "${GITHUB_OUTPUT}"
+ echo "retention=30" >> "${GITHUB_OUTPUT}"
;;
*)
- echo "::set-output name=repo::${REPO_PREFIX}-devel"
- echo "::set-output name=version::0.${GITHUB_SHA}"
- echo "::set-output name=retention::30"
+ echo "repo=${REPO_PREFIX}-devel" >> "${GITHUB_OUTPUT}"
+ echo "version=0.${GITHUB_SHA}" >> "${GITHUB_OUTPUT}"
+ echo "retention=30" >> "${GITHUB_OUTPUT}"
;;
esac
else
- echo "::set-output name=version::$(cut -d'-' -f 1 packaging/version | tr -d 'v')"
- echo "::set-output name=retention::0"
+ echo "version=$(cut -d'-' -f 1 packaging/version | tr -d 'v')" >> "${GITHUB_OUTPUT}"
+ echo "retention=0" >> "${GITHUB_OUTPUT}"
fi
- name: Failure Notification
uses: rtCamp/action-slack-notify@v2
@@ -186,7 +160,7 @@ jobs:
id: docker-config
shell: bash
run: |
- echo '{"cgroup-parent": "/actions_job", "experimental": true}' | sudo tee /etc/docker/daemon.json 2>/dev/null
+ echo '{"cgroup-parent": "actions-job.slice", "experimental": true}' | sudo tee /etc/docker/daemon.json 2>/dev/null
sudo service docker restart
- name: Fetch images
id: fetch-images
diff --git a/.github/workflows/repoconfig-packages.yml b/.github/workflows/repoconfig-packages.yml
index 824ddd34..f8a3dc40 100644
--- a/.github/workflows/repoconfig-packages.yml
+++ b/.github/workflows/repoconfig-packages.yml
@@ -34,31 +34,10 @@ jobs:
sudo apt-get update && sudo apt-get install -y python3-ruamel.yaml
- name: Read build matrix
id: set-matrix
- shell: python3 {0}
run: |
- from ruamel.yaml import YAML
- import json
- yaml = YAML(typ='safe')
- entries = list()
-
- with open('.github/data/distros.yml') as f:
- data = yaml.load(f)
-
- for i, v in enumerate(data['include']):
- if 'packages' in data['include'][i]:
- entries.append({
- 'distro': data['include'][i]['distro'],
- 'version': data['include'][i]['version'],
- 'pkgclouddistro': data['include'][i]['packages']['repo_distro'],
- 'format': data['include'][i]['packages']['type'],
- 'base_image': data['include'][i]['base_image'] if 'base_image' in data['include'][i] else data['include'][i]['distro'],
- 'platform': data['platform_map']['amd64']
- })
-
- entries.sort(key=lambda k: (k['distro'], k['version']))
- matrix = json.dumps({'include': entries}, sort_keys=True)
- print('Generated Matrix: ' + matrix)
- print('::set-output name=matrix::' + matrix)
+ matrix="$(.github/scripts/gen-matrix-repoconfig.py)"
+ echo "Generated matrix: ${matrix}"
+ echo "matrix=${matrix}" >> "${GITHUB_OUTPUT}"
- name: Failure Notification
uses: rtCamp/action-slack-notify@v2
env:
@@ -117,7 +96,7 @@ jobs:
/netdata/packaging/repoconfig/build-${{ matrix.format }}.sh
- name: SSH setup
id: ssh-setup
- if: github.event_name == 'workflow_dispatch'
+ if: github.event_name != 'pull_request' && github.repository == 'netdata/netdata'
continue-on-error: true
uses: shimataro/ssh-key-action@v2
with:
@@ -127,23 +106,17 @@ jobs:
- name: Upload to packages.netdata.cloud
id: package-upload
continue-on-error: true
- if: github.event_name == 'workflow_dispatch'
+ if: github.event_name != 'pull_request' && github.repository == 'netdata/netdata'
run: |
- .github/scripts/package-upload.sh \
- ${{ matrix.repo_distro }} \
- ${{ matrix.arch }} \
- ${{ matrix.format }} \
- netdata/netdata
- .github/scripts/package-upload.sh \
- ${{ matrix.repo_distro }} \
- ${{ matrix.arch }} \
- ${{ matrix.format }} \
- netdata/netdata-edge
- .github/scripts/package-upload.sh \
- ${{ matrix.repo_distro }} \
- ${{ matrix.arch }} \
- ${{ matrix.format }} \
- netdata/netdata-repoconfig
+ for arch in ${{ matrix.arches }}; do
+ for suffix in '' -edge -repoconfig ; do
+ .github/scripts/package-upload.sh \
+ ${{ matrix.pkgclouddistro }} \
+ ${arch} \
+ ${{ matrix.format }} \
+ netdata/netdata${suffix}
+ done
+ done
- name: Upload Packages
id: publish
if: github.event_name != 'pull_request' && github.repository == 'netdata/netdata'
diff --git a/.github/workflows/review.yml b/.github/workflows/review.yml
index 5679b246..7f12aeec 100644
--- a/.github/workflows/review.yml
+++ b/.github/workflows/review.yml
@@ -29,56 +29,56 @@ jobs:
id: actionlint
run: |
if [ "${{ contains(github.event.pull_request.labels.*.name, 'run-ci/actionlint') }}" = "true" ]; then
- echo '::set-output name=run::true'
+ echo "run=true" >> "${GITHUB_OUTPUT}"
elif git diff --name-only origin/${{ github.base_ref }} HEAD | grep -Eq '\.github/workflows/.*' ; then
- echo '::set-output name=run::true'
+ echo "run=true" >> "${GITHUB_OUTPUT}"
echo 'GitHub Actions workflows have changed, need to run actionlint.'
else
- echo '::set-output name=run::false'
+ echo "run=false" >> "${GITHUB_OUTPUT}"
fi
- name: Check files for eslint
id: eslint
run: |
if [ "${{ contains(github.event.pull_request.labels.*.name, 'run-ci/eslint') }}" = "true" ]; then
- echo '::set-output name=run::true'
+ echo "run=true" >> "${GITHUB_OUTPUT}"
elif git diff --name-only origin/${{ github.base_ref }} HEAD | grep -v "web/gui/dashboard" | grep -Eq '.*\.js|node\.d\.plugin\.in' ; then
- echo '::set-output name=run::true'
+ echo "run=true" >> "${GITHUB_OUTPUT}"
echo 'JS files have changed, need to run ESLint.'
else
- echo '::set-output name=run::false'
+ echo "run=false" >> "${GITHUB_OUTPUT}"
fi
- name: Check files for hadolint
id: hadolint
run: |
if [ "${{ contains(github.event.pull_request.labels.*.name, 'run-ci/hadolint') }}" = "true" ]; then
- echo '::set-output name=run::true'
+ echo "run=true" >> "${GITHUB_OUTPUT}"
elif git diff --name-only origin/${{ github.base_ref }} HEAD | grep -Eq '.*Dockerfile.*' ; then
- echo '::set-output name=run::true'
+ echo "run=true" >> "${GITHUB_OUTPUT}"
echo 'Dockerfiles have changed, need to run Hadolint.'
else
- echo '::set-output name=run::false'
+ echo "run=false" >> "${GITHUB_OUTPUT}"
fi
- name: Check files for shellcheck
id: shellcheck
run: |
if [ "${{ contains(github.event.pull_request.labels.*.name, 'run-ci/shellcheck') }}" = "true" ]; then
- echo '::set-output name=run::true'
+ echo "run=true" >> "${GITHUB_OUTPUT}"
elif git diff --name-only origin/${{ github.base_ref }} HEAD | grep -Eq '.*\.sh.*' ; then
- echo '::set-output name=run::true'
+ echo "run=true" >> "${GITHUB_OUTPUT}"
echo 'Shell scripts have changed, need to run shellcheck.'
else
- echo '::set-output name=run::false'
+ echo "run=false" >> "${GITHUB_OUTPUT}"
fi
- name: Check files for yamllint
id: yamllint
run: |
if [ "${{ contains(github.event.pull_request.labels.*.name, 'run-ci/yamllint') }}" = "true" ]; then
- echo '::set-output name=run::true'
+ echo "run=true" >> "${GITHUB_OUTPUT}"
elif git diff --name-only origin/${{ github.base_ref }} HEAD | grep -Eq '.*\.ya?ml|python\.d/.*\.conf' ; then
- echo '::set-output name=run::true'
+ echo "run=true" >> "${GITHUB_OUTPUT}"
echo 'YAML files have changed, need to run yamllint.'
else
- echo '::set-output name=run::false'
+ echo "run=false" >> "${GITHUB_OUTPUT}"
fi
actionlint:
diff --git a/.gitignore b/.gitignore
index b90428a1..821d72d9 100644
--- a/.gitignore
+++ b/.gitignore
@@ -126,7 +126,6 @@ system/netdata-updater.service
!system/netdata.service.*.in
system/netdata.plist
system/netdata-freebsd
-system/edit-config
system/netdata.crontab
system/install-service.sh
@@ -138,7 +137,6 @@ claim/netdata-claim.sh
collectors/tc.plugin/tc-qos-helper.sh
collectors/charts.d.plugin/charts.d.plugin
collectors/python.d.plugin/python.d.plugin
-collectors/fping.plugin/fping.plugin
collectors/ioping.plugin/ioping.plugin
collectors/go.d.plugin
web/netdata-switch-dashboard.sh
diff --git a/.lgtm.yml b/.lgtm.yml
deleted file mode 100644
index 6fdab0e8..00000000
--- a/.lgtm.yml
+++ /dev/null
@@ -1,24 +0,0 @@
----
-# LGTM does a good job at classifying files, but sometimes it needs some help.
-# To classify files which shouldn't be checked we need to define where such
-# files are located and manually assign them one of possible categories:
-# docs, generated, library, template, test
-# More information can be found in lgtm documentation:
-# https://help.semmle.com/lgtm-enterprise/user/help/file-classification.html#built-in-tags
-# https://lgtm.com/help/lgtm/lgtm.yml-configuration-file
-path_classifiers:
- library:
- - collectors/python.d.plugin/python_modules/third_party/
- - collectors/python.d.plugin/python_modules/urllib3/
- - collectors/python.d.plugin/python_modules/pyyaml2/
- - collectors/python.d.plugin/python_modules/pyyaml3/
- - ml/kmeans/dlib/
- - ml/dlib/dlib/
- - ml/json/
- - web/gui/lib/
- - web/gui/src/
- - web/gui/css/
- - web/gui/dashboard/lib/
- - libnetdata/libjudy/
- test:
- - tests/
diff --git a/.remarkignore b/.remarkignore
deleted file mode 100644
index 83b69470..00000000
--- a/.remarkignore
+++ /dev/null
@@ -1 +0,0 @@
-CHANGELOG.md \ No newline at end of file
diff --git a/.remarkrc.js b/.remarkrc.js
deleted file mode 100644
index 55793e43..00000000
--- a/.remarkrc.js
+++ /dev/null
@@ -1,111 +0,0 @@
-// Source: https://github.com/codacy/codacy-remark-lint/raw/master/.remarkrc.js
-
-exports.settings = {
- gfm: true,
- commonmark: true,
- looseTable: false,
- spacedTable: false,
- paddedTable: false,
- fences: true,
- rule: "-",
- ruleRepetition: 3,
- emphasis: "*",
- strong: "*",
- bullet: "-",
- listItemIndent: "1",
- incrementListMarker: true
-};
-
-const remarkPresetLintMarkdownStyleGuide = {
- plugins: require("remark-preset-lint-markdown-style-guide").plugins.filter(
- function(elem) {
- return elem != require("remark-lint-no-duplicate-headings");
- }
- )
-};
-
-exports.plugins = [
- require("remark-preset-lint-consistent"),
- require("remark-preset-lint-recommended"),
- remarkPresetLintMarkdownStyleGuide,
- [require("remark-lint-no-dead-urls"), { skipOffline: true }],
- require("remark-lint-heading-whitespace"),
- [require("remark-lint-maximum-line-length"), 120],
- [require("remark-lint-maximum-heading-length"), 120],
- [require("remark-lint-list-item-indent"), "tab-size"],
- [require("remark-lint-list-item-spacing"), false],
- [require("remark-lint-strong-marker"), "*"],
- [require("remark-lint-emphasis-marker"), "_"],
- [require("remark-lint-unordered-list-marker-style"), "-"],
- [require("remark-lint-ordered-list-marker-style"), "."],
- [require("remark-lint-ordered-list-marker-value"), "ordered"],
- /*[
- require("remark-lint-write-good"),
- [
- "warn",
- {
- passive: false,
- illusion: true,
- so: true,
- thereIs: true,
- weasel: true,
- adverb: true,
- tooWordy: true,
- cliches: true,
- eprime: false
- }
- ]
- ],*/
- require("remark-validate-links"),
- require("remark-frontmatter"),
- /*[
- require("remark-retext"),
- require("unified")().use({
- plugins: [
- require("retext-english"),
- require("retext-syntax-urls"),
- [
- require("retext-spell"),
- {
- ignoreLiteral: true,
- dictionary: require("dictionary-en-us"),
- ...personalDictionary
- }
- ],
- [
- require("retext-sentence-spacing"),
- {
- preferred: 1
- }
- ],
- require("retext-repeated-words"),
- require("retext-usage"),
- require("retext-indefinite-article"),
- require("retext-redundant-acronyms"),
- [
- require("retext-contractions"),
- {
- straight: true,
- allowLiteral: true
- }
- ],
- require("retext-diacritics"),
- [
- require("retext-quotes"),
- {
- preferred: "straight"
- }
- ],
- require("retext-equality"),
- require("retext-passive"),
- require("retext-profanities"),
- [
- require("retext-readability"),
- {
- age: 20
- }
- ]
- ]
- })
- ]*/
-];
diff --git a/.squash.yml b/.squash.yml
deleted file mode 100644
index 58e59103..00000000
--- a/.squash.yml
+++ /dev/null
@@ -1,7 +0,0 @@
-deployments:
- netdata:
- auto_deploy_on_commits: true
- filename: ./packaging/docker/Dockerfile
- context_path: ./
- port_forwarding: 80:19999
- run_options: -v /proc:/host/proc:ro -v /sys:/host/sys:ro -v /var/run/docker.sock:/var/run/docker.sock:ro --cap-add SYS_PTRACE --security-opt apparmor=unconfined
diff --git a/.travis.yml b/.travis.yml
deleted file mode 100644
index e729815a..00000000
--- a/.travis.yml
+++ /dev/null
@@ -1,68 +0,0 @@
----
-dist: focal
-language: c
-
-addons:
- apt:
- packages: ['moreutils']
-
-env:
- global:
- - RELEASE_CHANNEL=nightly
-
-before_install:
- - exec > >(ts -s '%H:%M:%.S ') 2>&1
- - source .travis/utils.sh
-
-# Install dependencies for all, once
-#
-install:
- - sudo apt-get install -y libuv1-dev liblz4-dev libcap2-bin zlib1g-dev uuid-dev fakeroot libipmimonitoring-dev libmnl-dev libnetfilter-acct-dev gnupg python3-pip
- - sudo pip3 install git-semver==0.3.2 # 11/Sep/2019: git-semver tip was broken, so we had to force last good run of it
- - source tests/installer/slack.sh
- - export NOTIF_CHANNEL="automation-beta"
- - if [ "${TRAVIS_REPO_SLUG}" = "netdata/netdata" ]; then export NOTIF_CHANNEL="automation"; fi;
- - export BUILD_VERSION="$(cat packaging/version | cut -d'-' -f1)"
- - export LATEST_RELEASE_VERSION="$(cat packaging/version | cut -d'-' -f1)"
- - export LATEST_RELEASE_DATE="$(git log -1 --format=%aD "${LATEST_RELEASE_VERSION}" | cat)"
- - if [[ "${TRAVIS_COMMIT_MESSAGE}" = *"[Build latest]"* ]]; then export BUILD_VERSION="$(cat packaging/version | cut -d'-' -f1,2 | sed -e 's/-/./g').latest"; fi;
- - export DEPLOY_REPO="netdata" # Default production packaging repository
- - export PACKAGING_USER="netdata" # Standard package cloud account
- - if [[ "${TRAVIS_COMMIT_MESSAGE}" = *"[Build latest]"* ]]; then export DEPLOY_REPO="netdata-edge"; fi;
- - export PACKAGE_CLOUD_RETENTION_DAYS=30
- - if [ ! "${TRAVIS_REPO_SLUG}" = "netdata/netdata" ]; then export DEPLOY_REPO="netdata-devel"; fi;
- # These are release-related artifacts and have to be evaluated before we start doing conditional checks inside stages
- - source ".travis/tagger.sh"
- - export GIT_TAG="$(git tag --points-at)"
- - git submodule update --init --recursive
-
-
-# Setup notification system
-#
-notifications:
- webhooks:
- urls:
- - https://app.fossa.io/hooks/travisci
-
-# Define the stage sequence and conditionals
-#
-stages:
- # Mandatory runs, we always want these executed
- - name: Build process
- if: commit_message =~ /^((?!\[Package (amd64|arm64|i386) (DEB|RPM)( .*)?\]).)*$/
-
-
-# Define stage implementation details
-#
-jobs:
- # This is a hook to help us introduce "soft" errors on our process
- allow_failures:
- - env: ALLOW_SOFT_FAILURE_HERE=true
- include:
- # Ensure netdata code builds successfully
- - stage: Build process
-
- name: Standard netdata build
- script: fakeroot ./netdata-installer.sh --install $HOME --dont-wait --dont-start-it --enable-plugin-nfacct --enable-plugin-freeipmi --disable-lto
- env: CFLAGS='-O1 -Wall -Wextra -Wformat-signedness -fstack-protector-all -fno-common -DNETDATA_INTERNAL_CHECKS=1 -D_FORTIFY_SOURCE=2 -DNETDATA_VERIFY_LOCKS=1'
- after_failure: post_message "TRAVIS_MESSAGE" "<!here> standard netdata build is failing (Still dont know which one, will improve soon)"
diff --git a/.travis/README.md b/.travis/README.md
deleted file mode 100644
index 8927dd4c..00000000
--- a/.travis/README.md
+++ /dev/null
@@ -1,149 +0,0 @@
-<!--
----
-title: "Description of CI build configuration"
-custom_edit_url: https://github.com/netdata/netdata/edit/master/.travis/README.md
----
--->
-
-# Description of CI build configuration
-
-## Variables needed by travis
-
-- GITHUB_TOKEN - GitHub token with push access to repository
-- DOCKER_USERNAME - Username (netdatabot) with write access to docker hub repository
-- DOCKER_PWD - Password to docker hub
-- encrypted_8daf19481253_key - key needed by openssl to decrypt GCS credentials file
-- encrypted_8daf19481253_iv - IV needed by openssl to decrypt GCS credentials file
-- COVERITY_SCAN_TOKEN - Token to allow coverity test analysis uploads
-- SLACK_USERNAME - This is required for the slack notifications triggered by travis pipeline
-- SLACK_CHANNEL - This is the channel that Travis will be posting messages
-- SLACK_NOTIFY_WEBHOOK_URL - This is the incoming URL webhook as provided by slack integration. Visit Apps integration in slack to generate the required hook
-- SLACK_BOT_NAME - This is the name your bot will appear with on slack
-
-## CI workflow details
-Our CI pipeline is designed to help us identify and mitigate risks at all stages of implementation.
-To accommodate this need, we used [Travis CI](http://www.travis-ci.com) as our CI/CD tool.
-Our main areas of concern are:
-1) Only push code that is working. That means fail fast so that we can improve before we reach the public
-
-2) Reduce the time to market to minimum, by streamlining the release process.
- That means a lot of testing, a lot of consistency checks, a lot of validations
-
-3) Generated artifacts consistency. We should not allow broken software to reach the public.
- When this happens, it's embarrassing and we struggle to eliminate it.
-
-4) We are an innovative company, so we love to automate :)
-
-
-Having said that, here's a brief introduction to Netdata's improved CI/CD pipeline with Travis.
-Our CI/CD lifecycle contains three different execution entry points:
-1) A user opens a pull request to netdata/master: Travis will run a pipeline on the branch under that PR
-2) A merge or commit happens on netdata/master. This will trigger travis to run, but we have two distinct cases in this scenario:
- a) A user merges a pull request to netdata/master: Travis will run on master, after the merge.
- b) A user runs a commit/merge with a special keyword (mentioned later).
- This triggers a release for either minor, major or release candidate versions, depending the keyword
-3) A scheduled job runs on master once per day: Travis will run on master at the scheduled interval
-
-To accommodate all three entry points our CI/CD workflow has a set of steps that run on all three entry points.
-Once all these steps are successful, then our pipeline executes another subset of steps for entry points 2 and 3.
-In travis terms the "steps" are "Stages" and within each stage we execute a set of activities called "jobs" in travis.
-
-### Always run: Stages that running on all three execution entry points
-
-## Code quality, linting, syntax, code style
-At this early stage we iterate through a set of basic quality control checks:
-- Shell checking: Run linters for our various BASH scripts
-- Checksum validators: Run validators to ensure our installers and documentation are in sync
-- Dashboard validator: We provide a pre-generated dashboard.js script file that we need to make sure its up to date. We validate that.
-
-## Build process
-At this stage, basically, we build :-)
-We do a baseline check of our build artifacts to guarantee they are not broken
-Briefly our activities include:
-- Verify docker builds successfully
-- Run the standard Netdata installer, to make sure we build & run properly
-- Do the same through 'make dist', as this is our stable channel for our kickstart files
-
-## Artifacts validation
-At this point we know our software is building, we need to go through the a set of checks, to guarantee
-that our product meets certain expectations. At the current stage, we are focusing on basic capabilities
-like installing in different distributions, running the full lifecycle of install-run-update-install and so on.
-We are still working on enriching this with more and more use cases, to get us closer to achieving full stability of our software.
-Briefly we currently evaluate the following activities:
-- Basic software unit testing (only run when changes happen that require it)
-- Non containerized build and install on ubuntu 14.04
-- Non containerized build and install on ubuntu 18.04
-- Running the full Netdata lifecycle (install, update, uninstall) on ubuntu 18.04
-- Build and install on CentOS 7
-(More to come)
-
-### Nightly operations: Stages that run daily under cronjob
-The nightly stages are related to the daily nightly activities, that produce our daily latest releases.
-We also maintain a couple of cronjobs that run during the night to provide us with deeper insights,
-like for example coverity scanning or extended kickstart checksum checks
-
-## Nightly operations
-At this stage we run scheduled jobs and execute the nightly changelog generator, coverity scans,
-labeler for our issues and extended kickstart files checksum validations.
-
-## Nightly release
-During this stage we are building and publishing latest docker images, prepare the nightly artifacts
-and deploy them (the artifacts) to our google cloud service provider.
-
-
-### Publishing
-Publishing is responsible for executing the major/minor/patch releases and is separated
-in two stages: packaging preparation process and publishing.
-
-## Packaging for release
-During packaging we are preparing the release changelog information and run the labeler.
-
-## Publish for release
-The publishing stage is the most complex part in publishing. This is the stage were we generate and publish docker images,
-prepare the release artifacts and get ready with the release draft.
-
-### Package Management workflows
-As part of our goal to provide the best support to our customers, we have created a set of CI workflows to automatically produce
-DEB and RPM for multiple distributions. These workflows are implemented under the templated stages '_DEB_TEMPLATE' and '_RPM_TEMPLATE'.
-We currently plan to actively support the following Operating Systems, with a plan to further expand this list following our users needs.
-
-### Operating systems supported
-The following distributions are supported
-- Debian versions
- - Buster (TBD - not released yet, check [debian releases](https://www.debian.org/releases/) for details)
- - Stretch
- - Jessie
- - Wheezy
-
-- Ubuntu versions
- - Disco
- - Cosmic
- - Bionic
- - artful
-
-- Enterprise Linux versions (Covers Red Hat, CentOS, and Amazon Linux with version 6)
- - Version 8 (TBD)
- - Version 7
- - Version 6
-
-- Fedora versions
- - Version 31 (TBD)
- - Version 30
- - Version 29
- - Version 28
-
-- openSUSE versions
- - 15.1
- - 15.0
-
-- Gentoo distributions
- - TBD
-
-### Architectures supported
-We plan to support amd64, x86 and arm64 architectures. As of June 2019 only amd64 and x86 will become available, as we are still working on solving issues with the architecture.
-
-The Package deployment can be triggered manually by executing an empty commit with the following message pattern: `[Package PACKAGE_TYPE PACKAGE_ARCH] DESCRIBE_THE_REASONING_HERE`.
-Travis Yaml configuration allows the user to combine package type and architecture as necessary to regenerate the current stable release (For example tag v1.15.0 as of 4th of May 2019)
-Sample patterns to trigger building of packages for all amd64 supported architecture:
-- '[Package amd64 RPM]': Build & publish all amd64 available RPM packages
-- '[Package amd64 DEB]': Build & publish all amd64 available DEB packages
diff --git a/.travis/check_changelog_last_modification.sh b/.travis/check_changelog_last_modification.sh
deleted file mode 100755
index ae0da877..00000000
--- a/.travis/check_changelog_last_modification.sh
+++ /dev/null
@@ -1,38 +0,0 @@
-#!/usr/bin/env bash
-#
-# This scriptlet validates nightlies age and notifies is if it gets too old
-#
-# Copyright: SPDX-License-Identifier: GPL-3.0-or-later
-#
-# Author : Pavlos Emm. Katsoulakis (paul@netdata.cloud)
-
-set -e
-
-# If we are not in netdata git repo, at the top level directory, fail
-TOP_LEVEL=$(basename "$(git rev-parse --show-toplevel)")
-CWD=$(git rev-parse --show-cdup || echo "")
-if [ -n "${CWD}" ] || [ ! "${TOP_LEVEL}" == "netdata" ]; then
- echo "Run as .travis/$(basename "$0") from top level directory of netdata git repository"
- echo "Changelog age checker exited abnormally"
- exit 1
-fi
-
-source tests/installer/slack.sh || echo "I could not load slack library"
-
-LAST_MODIFICATION="$(git log -1 --pretty="format:%at" CHANGELOG.md)"
-CURRENT_TIME="$(date +"%s")"
-TWO_DAYS_IN_SECONDS=172800
-
-DIFF=$((CURRENT_TIME - LAST_MODIFICATION))
-
-echo "Checking CHANGELOG.md last modification time on GIT.."
-echo "CHANGELOG.md timestamp: ${LAST_MODIFICATION}"
-echo "Current timestamp: ${CURRENT_TIME}"
-echo "Diff: ${DIFF}"
-
-if [ ${DIFF} -gt ${TWO_DAYS_IN_SECONDS} ]; then
- echo "CHANGELOG.md is more than two days old!"
- post_message "TRAVIS_MESSAGE" "Hi <!here>, CHANGELOG.md was found more than two days old (Diff: ${DIFF} seconds)" "${NOTIF_CHANNEL}"
-else
- echo "CHANGELOG.md is less than two days old, fine"
-fi
diff --git a/.travis/create_artifacts.sh b/.travis/create_artifacts.sh
deleted file mode 100755
index 27428913..00000000
--- a/.travis/create_artifacts.sh
+++ /dev/null
@@ -1,77 +0,0 @@
-#!/usr/bin/env bash
-#
-# Artifacts creation script.
-# This script generates two things:
-# 1) The static binary that can run on all linux distros (built-in dependencies etc)
-# 2) The distribution source tarball
-#
-# Copyright: SPDX-License-Identifier: GPL-3.0-or-later
-#
-# Author: Paul Emm. Katsoulakis <paul@netdata.cloud>
-#
-# shellcheck disable=SC2230
-
-set -e
-
-# If we are not in netdata git repo, at the top level directory, fail
-TOP_LEVEL=$(basename "$(git rev-parse --show-toplevel)")
-CWD=$(git rev-parse --show-cdup || echo "")
-if [ -n "${CWD}" ] || [ ! "${TOP_LEVEL}" == "netdata" ]; then
- echo "Run as .travis/$(basename "$0") from top level directory of netdata git repository"
- exit 1
-fi
-
-if [ ! "${TRAVIS_REPO_SLUG}" == "netdata/netdata" ]; then
- echo "Beta mode on ${TRAVIS_REPO_SLUG}, not running anything here"
- exit 0
-fi
-
-echo "--- Initialize git configuration ---"
-git checkout "${1-master}"
-git pull
-
-if [ "${RELEASE_CHANNEL}" == stable ]; then
- echo "--- Set default release channel to stable ---"
- sed -i 's/^RELEASE_CHANNEL="nightly" *#/RELEASE_CHANNEL="stable" #/' \
- netdata-installer.sh \
- packaging/makeself/install-or-update.sh
-fi
-
-# Everything from this directory will be uploaded to GCS
-mkdir -p artifacts
-BASENAME="netdata-$(git describe)"
-
-# Make sure stdout is in blocking mode. If we don't, then conda create will barf during downloads.
-# See https://github.com/travis-ci/travis-ci/issues/4704#issuecomment-348435959 for details.
-python -c 'import os,sys,fcntl; flags = fcntl.fcntl(sys.stdout, fcntl.F_GETFL); fcntl.fcntl(sys.stdout, fcntl.F_SETFL, flags&~os.O_NONBLOCK);'
-echo "--- Create tarball ---"
-command -v git > /dev/null && [ -d .git ] && git clean -d -f
-autoreconf -ivf
-./configure --prefix=/usr --sysconfdir=/etc --localstatedir=/var --libexecdir=/usr/libexec --with-zlib --with-math --with-user=netdata CFLAGS=-O2
-make dist
-mv "${BASENAME}.tar.gz" artifacts/
-
-echo "--- Create self-extractor ---"
-sxarches="x86_64 armv7l aarch64"
-for arch in ${sxarches}; do
- git clean -d -f
- rm -rf packating/makeself/tmp
- ./packaging/makeself/build-static.sh ${arch}
-done
-
-# Needed for GCS
-echo "--- Copy artifacts to separate directory ---"
-#shellcheck disable=SC2164
-cp packaging/version artifacts/latest-version.txt
-cd artifacts
-ln -s "${BASENAME}.tar.gz" netdata-latest.tar.gz
-
-for arch in ${sxarches}; do
- ln -s "netdata-${arch}-$(git describe).gz.run" netdata-${arch}-latest.gz.run
-done
-
-ln -s "${BASENAME}.gz.run" netdata-latest.gz.run
-
-sha256sum -b ./* > "sha256sums.txt"
-echo "checksums:"
-cat sha256sums.txt
diff --git a/.travis/create_changelog.sh b/.travis/create_changelog.sh
deleted file mode 100755
index 83584aa6..00000000
--- a/.travis/create_changelog.sh
+++ /dev/null
@@ -1,47 +0,0 @@
-#!/usr/bin/env bash
-#
-# Copyright: SPDX-License-Identifier: GPL-3.0-or-later
-#
-# Author : Pavlos Emm. Katsoulakis (paul@netdata.cloud)
-set -e
-
-# If we are not in netdata git repo, at the top level directory, fail
-TOP_LEVEL=$(basename "$(git rev-parse --show-toplevel)")
-CWD=$(git rev-parse --show-cdup || echo "")
-if [ -n "$CWD" ] || [ ! "${TOP_LEVEL}" == "netdata" ]; then
- echo "Run as .travis/$(basename "$0") from top level directory of netdata git repository"
- echo "Changelog creation aborted"
- exit 1
-fi
-
-ORGANIZATION=$(echo "$TRAVIS_REPO_SLUG" | awk -F '/' '{print $1}')
-PROJECT=$(echo "$TRAVIS_REPO_SLUG" | awk -F '/' '{print $2}')
-GIT_MAIL=${GIT_MAIL:-"bot@netdata.cloud"}
-GIT_USER=${GIT_USER:-"netdatabot"}
-
-if [ -z ${GIT_TAG+x} ]; then
- OPTS=""
-else
- OPTS="--future-release ${GIT_TAG}"
-fi
-
-if [ ! "${TRAVIS_REPO_SLUG}" == "netdata/netdata" ]; then
- echo "Beta mode on ${TRAVIS_REPO_SLUG}, nothing else to do here"
- exit 0
-fi
-
-echo "--- Creating changelog ---"
-git checkout master
-git pull
-
-docker login -u "${DOCKER_USERNAME}" -p "${DOCKER_PWD}"
-docker run -it -v "$(pwd)":/project markmandel/github-changelog-generator:latest \
- --user "${ORGANIZATION}" \
- --project "${PROJECT}" \
- --token "${GITHUB_TOKEN}" \
- --since-tag "v1.10.0" \
- --unreleased-label "**Next release**" \
- --no-issues \
- --exclude-labels "stale,duplicate,question,invalid,wontfix,discussion,no changelog" \
- --max-issues 500 \
- --bug-labels IGNOREBUGS ${OPTS}
diff --git a/.travis/current_build_status b/.travis/current_build_status
deleted file mode 100644
index 11a6d0a5..00000000
--- a/.travis/current_build_status
+++ /dev/null
@@ -1 +0,0 @@
-changes-#18220
diff --git a/.travis/draft_release.sh b/.travis/draft_release.sh
deleted file mode 100755
index ddc0f9ad..00000000
--- a/.travis/draft_release.sh
+++ /dev/null
@@ -1,65 +0,0 @@
-#!/bin/bash
-#
-# Draft release generator.
-# This utility is responsible for submitting a draft release to github repo
-# It is agnostic of other processes, when executed it will draft a release,
-# based on the most recent reachable tag.
-#
-# Requirements:
-# - GITHUB_TOKEN variable set with GitHub token. Access level: repo.public_repo
-# - artifacts directory in place
-# - The directory is created by create_artifacts.sh mechanism
-# - The artifacts need to be created with the same tag, obviously
-#
-# Copyright: SPDX-License-Identifier: GPL-3.0-or-later
-#
-# Author: Pavlos Emm. Katsoulakis <paul@netdata.cloud>
-
-set -e
-
-if [ ! -f .gitignore ]; then
- echo "Run as ./travis/$(basename "$0") from top level directory of git repository"
- exit 1
-fi
-
-echo "--- Initialize git configuration ---"
-git checkout master
-git pull
-
-
-if [[ $(git describe) =~ -rc* ]]; then
- echo "This is a release candidate tag, we do not generate a release draft"
- exit 0
-fi
-
-# Load the tag, if any
-GIT_TAG=$(git describe)
-
-if [ ! "${TRAVIS_REPO_SLUG}" == "netdata/netdata" ]; then
- echo "Beta mode on ${TRAVIS_REPO_SLUG}, i was about to run for release (${GIT_TAG}), but i am emulating, so bye"
- exit 0
-fi;
-
-echo "---- CREATING RELEASE DRAFT WITH ASSETS -----"
-# Download hub
-HUB_VERSION=${HUB_VERSION:-"2.5.1"}
-wget "https://github.com/github/hub/releases/download/v${HUB_VERSION}/hub-linux-amd64-${HUB_VERSION}.tgz" -O "/tmp/hub-linux-amd64-${HUB_VERSION}.tgz"
-tar -C /tmp -xvf "/tmp/hub-linux-amd64-${HUB_VERSION}.tgz"
-export PATH=$PATH:"/tmp/hub-linux-amd64-${HUB_VERSION}/bin"
-
-# Create a release draft
-if [ -z ${GIT_TAG+x} ]; then
- echo "Variable GIT_TAG is not set. Something went terribly wrong! Exiting."
- exit 1
-fi
-if [ "${GIT_TAG}" != "$(git tag --points-at)" ]; then
- echo "ERROR! Current commit is not tagged. Stopping release creation."
- exit 1
-fi
-until hub release create --draft \
- -a "artifacts/netdata-${GIT_TAG}.tar.gz" \
- -a "artifacts/netdata-${GIT_TAG}.gz.run" \
- -a "artifacts/sha256sums.txt" \
- -m "${GIT_TAG}" "${GIT_TAG}"; do
- sleep 5
-done
diff --git a/.travis/gcs-credentials.json.enc b/.travis/gcs-credentials.json.enc
deleted file mode 100644
index 5d1e7b2d..00000000
--- a/.travis/gcs-credentials.json.enc
+++ /dev/null
Binary files differ
diff --git a/.travis/generate_changelog_and_tag_release.sh b/.travis/generate_changelog_and_tag_release.sh
deleted file mode 100755
index bf5555b4..00000000
--- a/.travis/generate_changelog_and_tag_release.sh
+++ /dev/null
@@ -1,64 +0,0 @@
-#!/bin/bash
-#
-# Script to automatically do a couple of things:
-# - generate a new tag according to semver (https://semver.org/)
-# - generate CHANGELOG.md by using https://github.com/skywinder/github-changelog-generator
-#
-# Tags are generated by searching for a keyword in last commit message. Keywords are:
-# - [patch] or [fix] to bump patch number
-# - [minor], [feature] or [feat] to bump minor number
-# - [major] or [breaking change] to bump major number
-# All keywords MUST be surrounded with square braces.
-#
-# Script uses git mechanisms for locking, so it can be used in parallel builds
-#
-# Requirements:
-# - GITHUB_TOKEN variable set with GitHub token. Access level: repo.public_repo
-# - docker
-#
-# This is a modified version of:
-# https://github.com/paulfantom/travis-helper/blob/master/releasing/releaser.sh
-#
-# Copyright: SPDX-License-Identifier: GPL-3.0-or-later
-#
-# Author: Pavlos Emm. Katsoulakis <paul@netdata.cloud>
-# Author: Pawel Krupa (@paulfantom)
-set -e
-
-if [ ! -f .gitignore ]; then
- echo "Run as ./travis/$(basename "$0") from top level directory of git repository"
- exit 1
-fi
-
-echo "--- Changelog generator script starting ---"
-# If we dont have a produced TAG there is nothing to do, so bail out happy
-if [ -z "${GIT_TAG}" ]; then
- echo "GIT_TAG is empty, that is not suppose to happen (Value: $GIT_TAG)"
- exit 1
-fi
-
-if [ ! "${TRAVIS_REPO_SLUG}" == "netdata/netdata" ]; then
- echo "Beta mode on ${TRAVIS_REPO_SLUG}, nothing to do on the changelog generator and tagging script for (${GIT_TAG}), bye"
- exit 0
-fi
-
-echo "--- Initialize git configuration ---"
-export GIT_MAIL="bot@netdata.cloud"
-export GIT_USER="netdatabot"
-git checkout master
-git pull
-
-echo "---- UPDATE VERSION FILE ----"
-echo "$GIT_TAG" >packaging/version
-git add packaging/version
-
-echo "---- Create CHANGELOG -----"
-./.travis/create_changelog.sh
-git add CHANGELOG.md
-
-echo "---- COMMIT AND PUSH CHANGES ----"
-git commit -m "[ci skip] release $GIT_TAG" --author "${GIT_USER} <${GIT_MAIL}>"
-git tag "$GIT_TAG" -a -m "Automatic tag generation for travis build no. $TRAVIS_BUILD_NUMBER"
-git push "https://${GITHUB_TOKEN}:@$(git config --get remote.origin.url | sed -e 's/^https:\/\///')"
-git push "https://${GITHUB_TOKEN}:@$(git config --get remote.origin.url | sed -e 's/^https:\/\///')" --tags
-# After those operations output of command `git describe` should be identical with a value of GIT_TAG
diff --git a/.travis/generate_changelog_for_nightlies.sh b/.travis/generate_changelog_for_nightlies.sh
deleted file mode 100755
index 59173af3..00000000
--- a/.travis/generate_changelog_for_nightlies.sh
+++ /dev/null
@@ -1,50 +0,0 @@
-#!/usr/bin/env bash
-#
-# Changelog generation scriptlet, for nightlies
-#
-# Copyright: SPDX-License-Identifier: GPL-3.0-or-later
-#
-# Author : Pawel Krupa (paulfantom)
-# Author : Pavlos Emm. Katsoulakis (paul@netdata.cloud)
-set -e
-
-# If we are not in netdata git repo, at the top level directory, fail
-TOP_LEVEL=$(basename "$(git rev-parse --show-toplevel)")
-CWD=$(git rev-parse --show-cdup || echo "")
-if [ -n "$CWD" ] || [ ! "${TOP_LEVEL}" == "netdata" ]; then
- echo "Run as .travis/$(basename "$0") from top level directory of netdata git repository"
- echo "Changelog generation process aborted"
- exit 1
-fi
-
-LAST_TAG="$1"
-COMMITS_SINCE_RELEASE="$2"
-NEW_VERSION="${LAST_TAG}-$((COMMITS_SINCE_RELEASE + 1))-nightly"
-GIT_MAIL=${GIT_MAIL:-"bot@netdata.cloud"}
-GIT_USER=${GIT_USER:-"netdatabot"}
-PUSH_URL=$(git config --get remote.origin.url | sed -e 's/^https:\/\///')
-FAIL=0
-
-if [ ! "${TRAVIS_REPO_SLUG}" == "netdata/netdata" ]; then
- echo "Beta mode on ${TRAVIS_REPO_SLUG}, nothing else to do here"
- exit 0
-fi
-
-echo "Running changelog creation mechanism"
-.travis/create_changelog.sh
-
-echo "Changelog created! Adding packaging/version(${NEW_VERSION}) and CHANGELOG.md to the repository"
-echo "${NEW_VERSION}" > packaging/version
-git add packaging/version && echo "1) Added packaging/version to repository" || FAIL=1
-git add CHANGELOG.md && echo "2) Added changelog file to repository" || FAIL=1
-git commit -m '[ci skip] create nightly packages and update changelog' --author "${GIT_USER} <${GIT_MAIL}>" && echo "3) Committed changes to repository" || FAIL=1
-git push "https://${GITHUB_TOKEN}:@${PUSH_URL}" && echo "4) Pushed changes to remote ${PUSH_URL}" || FAIL=1
-
-# In case of a failure, wrap it up and bail out cleanly
-if [ $FAIL -eq 1 ]; then
- git clean -xfd
- echo "Changelog generation failed during github UPDATE!"
- exit 1
-fi
-
-echo "Changelog generation completed successfully!"
diff --git a/.travis/nightlies.sh b/.travis/nightlies.sh
deleted file mode 100755
index 7240a784..00000000
--- a/.travis/nightlies.sh
+++ /dev/null
@@ -1,51 +0,0 @@
-#!/usr/bin/env bash
-#
-# This is the nightly changelog generation script
-# It is responsible for two major activities:
-# 1) Update packaging/version with the current nightly version
-# 2) Generate the changelog for the mentioned version
-#
-# Copyright: SPDX-License-Identifier: GPL-3.0-or-later
-#
-# Author : Pawel Krupa (paulfantom)
-# Author : Pavlos Emm. Katsoulakis (paul@netdata.cloud)
-set -e
-FAIL=0
-
-source tests/installer/slack.sh || echo "Could not load slack library"
-
-# If we are not in netdata git repo, at the top level directory, fail
-TOP_LEVEL=$(basename "$(git rev-parse --show-toplevel)")
-CWD=$(git rev-parse --show-cdup || echo "")
-if [ -n "${CWD}" ] || [ ! "${TOP_LEVEL}" == "netdata" ]; then
- echo "Run as .travis/$(basename "$0") from top level directory of netdata git repository"
- echo "Changelog generation process aborted"
- exit 1
-fi
-
-LAST_TAG=$(git describe --abbrev=0 --tags)
-COMMITS_SINCE_RELEASE=$(git rev-list "${LAST_TAG}"..HEAD --count)
-PREVIOUS_NIGHTLY_COUNT="$(rev <packaging/version | cut -d- -f 2 | rev)"
-
-# If no commits since release, just stop
-if [ "${COMMITS_SINCE_RELEASE}" == "${PREVIOUS_NIGHTLY_COUNT}" ]; then
- echo "No changes since last nightly release, nothing else to do"
- exit 0
-fi
-
-if [ ! "${TRAVIS_REPO_SLUG}" == "netdata/netdata" ]; then
- echo "Beta mode -- nothing to do for ${TRAVIS_REPO_SLUG} on the nightlies script, bye"
- exit 0
-fi
-
-echo "--- Running Changelog generation ---"
-echo "We got $COMMITS_SINCE_RELEASE changes since $LAST_TAG, re-generating changelog"
-NIGHTLIES_CHANGELOG_FAILED=0
-.travis/generate_changelog_for_nightlies.sh "${LAST_TAG}" "${COMMITS_SINCE_RELEASE}" || NIGHTLIES_CHANGELOG_FAILED=1
-
-if [ ${NIGHTLIES_CHANGELOG_FAILED} -eq 1 ]; then
- echo "Changelog generation has failed, this is a soft error, process continues"
- post_message "TRAVIS_MESSAGE" "Changelog generation job for nightlies failed, possibly due to github issues" "${NOTIF_CHANNEL}" || echo "Slack notification failed"
-fi
-
-exit "${FAIL}"
diff --git a/.travis/tagger.sh b/.travis/tagger.sh
deleted file mode 100755
index a775a826..00000000
--- a/.travis/tagger.sh
+++ /dev/null
@@ -1,61 +0,0 @@
-# #BASH library
-#
-# Tags are generated by searching for a keyword in last commit message. Keywords are:
-# - [patch] or [fix] to bump patch number
-# - [minor], [feature] or [feat] to bump minor number
-# - [major] or [breaking change] to bump major number
-# All keywords MUST be surrounded with square braces.
-#
-# Requirements:
-# - GITHUB_TOKEN variable set with GitHub token. Access level: repo.public_repo
-# - git-semver python package (pip install git-semver)
-#
-# Original script is available at https://github.com/paulfantom/travis-helper/blob/master/releasing/releaser.sh
-#
-# Copyright: SPDX-License-Identifier: GPL-3.0-or-later
-#
-# Author : Pawel Krupa (paulfantom)
-# Author : Pavlos Emm. Katsoulakis (paul@netdata.cloud)
-
-# Figure out what will be new release candidate tag based only on previous ones.
-# This assumes that RELEASES are in format of "v0.1.2" and prereleases (RCs) are using "v0.1.2-rc0"
-function set_tag_release_candidate() {
- LAST_TAG=$(git semver)
- echo "${0}: Last tag found is: ${LAST_TAG}"
-
- if [[ $LAST_TAG =~ -rc* ]]; then
- VERSION=$(echo "$LAST_TAG" | cut -d'-' -f 1)
- LAST_RC=$(echo "$LAST_TAG" | cut -d'c' -f 2)
- RC=$((LAST_RC + 1))
- else
- VERSION="$(git semver --next-minor)"
- RC=0
- echo "${0}: Warning: Will set version to ${VERSION} (Last tag: ${LAST_TAG}) while tagged for release candidate generation"
- fi
-
- GIT_TAG="v${VERSION}-rc${RC}"
- echo "${0}: Generated a new tag, set to: (${GIT_TAG})"
-}
-
-function set_tag_for_release() {
- echo "${0}: Checking for tag existence"
- if [ -z "${GIT_TAG}" ]; then
- echo "${0}: No tag was found, generating a new tag"
- git semver
-
- echo "${0}: Last commit message: ${TRAVIS_COMMIT_MESSAGE}"
-
- # Figure out next tag based on commit message
- case "${TRAVIS_COMMIT_MESSAGE}" in
- *"[netdata patch release]"*) GIT_TAG="v$(git semver --next-patch)" ;;
- *"[netdata minor release]"*) GIT_TAG="v$(git semver --next-minor)" ;;
- *"[netdata major release]"*) GIT_TAG="v$(git semver --next-major)" ;;
- *"[netdata release candidate]"*) set_tag_release_candidate ;;
- *)
- echo "${0}: Keyword not detected. Nothing to set for GIT_TAG"
- ;;
- esac
- else
- echo "${0}: We seem to already have a GIT_TAG set to (${GIT_TAG})"
- fi
-}
diff --git a/.travis/trigger_artifact_build.sh b/.travis/trigger_artifact_build.sh
deleted file mode 100755
index 3ec5d02d..00000000
--- a/.travis/trigger_artifact_build.sh
+++ /dev/null
@@ -1,20 +0,0 @@
-#!/bin/sh
-
-token="${1}"
-version="${2}"
-type="${3}"
-
-resp="$(curl -X POST \
- -H 'Accept: application/vnd.github.v3+json' \
- -H "Authorization: Bearer ${token}" \
- "https://api.github.com/repos/netdata/netdata/actions/workflows/build.yml/dispatches" \
- -d "{\"ref\": \"master\", \"inputs\": {\"version\": \"${version}\", \"type\": \"${type}\"}}")"
-
-if [ -z "${resp}" ]; then
- echo "Successfully triggered release artifact build."
- exit 0
-else
- echo "Failed to trigger release artifact build. Output:"
- echo "${resp}"
- exit 1
-fi
diff --git a/.travis/trigger_docker_build.sh b/.travis/trigger_docker_build.sh
deleted file mode 100755
index 7a3dd508..00000000
--- a/.travis/trigger_docker_build.sh
+++ /dev/null
@@ -1,19 +0,0 @@
-#!/bin/sh
-
-token="${1}"
-version="${2}"
-
-resp="$(curl -X POST \
- -H 'Accept: application/vnd.github.v3+json' \
- -H "Authorization: Bearer ${token}" \
- "https://api.github.com/repos/netdata/netdata/actions/workflows/docker.yml/dispatches" \
- -d "{\"ref\": \"master\", \"inputs\": {\"version\": \"${version}\"}}")"
-
-if [ -z "${resp}" ]; then
- echo "Successfully triggered Docker image build."
- exit 0
-else
- echo "Failed to trigger Docker image build. Output:"
- echo "${resp}"
- exit 1
-fi
diff --git a/.travis/trigger_package_build.sh b/.travis/trigger_package_build.sh
deleted file mode 100755
index d1ecd48c..00000000
--- a/.travis/trigger_package_build.sh
+++ /dev/null
@@ -1,20 +0,0 @@
-#!/bin/sh
-
-token="${1}"
-version="${2}"
-pkgtype="${3}"
-
-resp="$(curl -X POST \
- -H 'Accept: application/vnd.github.v3+json' \
- -H "Authorization: Bearer ${token}" \
- "https://api.github.com/repos/netdata/netdata/actions/workflows/packaging.yml/dispatches" \
- -d "{\"ref\": \"master\", \"inputs\": {\"version\": \"${version}\", \"type\": \"${pkgtype}\"}}")"
-
-if [ -z "${resp}" ]; then
- echo "Successfully triggered binary package builds."
- exit 0
-else
- echo "Failed to trigger binary package builds. Output:"
- echo "${resp}"
- exit 1
-fi
diff --git a/.travis/utils.sh b/.travis/utils.sh
deleted file mode 100644
index d084a7ae..00000000
--- a/.travis/utils.sh
+++ /dev/null
@@ -1,29 +0,0 @@
-#!/usr/bin/env bash
-
-# Prevent travis from timing out after 10 minutes of no output
-tick() {
- (while true; do sleep 300; echo; done) &
- local PID=$!
- disown
-
- "$@"
- local RET=$?
-
- kill $PID
- return $RET
-}
-export -f tick
-
-retry() {
- local tries=$1
- shift
-
- local i=0
- while [ "$i" -lt "$tries" ]; do
- "$@" && return 0
- sleep $((2**((i++))))
- done
-
- return 1
-}
-export -f retry
diff --git a/BREAKING_CHANGES.md b/BREAKING_CHANGES.md
deleted file mode 100644
index 3399cb61..00000000
--- a/BREAKING_CHANGES.md
+++ /dev/null
@@ -1,11 +0,0 @@
-<!--
-title: "Breaking changes"
-description: "On occasion, the Netdata team must make significant changes to the open-source Netdata Agent. We publish those breaking changes here for reference."
-custom_edit_url: https://github.com/netdata/netdata/edit/master/BREAKING_CHANGES.md
--->
-
-# Breaking changes
-
-- remove deprecated bash modules (`apache`, `cpu_apps`, `cpufreq`, `exim`, `hddtemp`, `load_average`, `mem_apps`, `mysql`, `nginx`, `phpfpm`, `postfix`, `squid`, `tomcat`) [[#7962](https://github.com/netdata/netdata/pull/7962)]
-
-
diff --git a/BUILD.md b/BUILD.md
deleted file mode 100644
index 83f7e994..00000000
--- a/BUILD.md
+++ /dev/null
@@ -1,365 +0,0 @@
-<!--
-title: "The build system"
-custom_edit_url: https://github.com/netdata/netdata/edit/master/BUILD.md
--->
-
-# The build system
-
-We are currently migrating from `autotools` to `CMake` as a build-system. This document
-currently describes how we intend to perform this migration, and will be updated after
-the migration to explain how the new `CMake` configuration works.
-
-## Stages during the build
-
-1. The `netdata-installer.sh`, take in arguments and environment settings to control the
- build.
-2. The configure step: `autoreconf -ivf ; ./configure` passing arguments into the configure
- script. This becomes `generation-time` in CMake. This includes package / system detection
- and configuration resulting in the `config.h` in the source root.
-3. The build step: recurse through the generated Makefiles and build the executable.
-4. The first install step: calls `make install` to handle all the install steps put into
- the Makefiles by the configure step (puts binaries / libraries / config into target
- tree structure).
-5. The second install step: the rest of the installer after the make install handles
- system-level configuration (privilege setting, user / groups, fetch/build/install `go.d`
- plugins, telemetry, installing service for startup, uninstaller, auto-updates.
-
-The ideal migration result is to replace all of this with the following steps:
-```
-mkdir build ; cd build ; cmake .. -D... ; cmake --build . --target install
-```
-
-The `-D...` indicates where the command-line arguments for configuration are passed into
-`CMake`.
-
-## CMake generation time
-
-At generation time we need to solve the following issues:
-
-### Feature flags
-
-Every command-line switch on the installer and the configure script needs to becomes an
-argument to the CMake generation, we can do this with variables in the CMake cache:
-
-CMakeLists.txt:
-```
-option(ENABLE_DBENGINE "Enable the dbengine storage" ON)
-...
-if(${ENABLE_DBENGINE})
-...
-endif()
-```
-
-Command-line interface
-```
-cmake -DENABLE_DBENGINE
-```
-
-### Dependency detection
-
-We have a mixture of soft- and hard-dependencies on libraries. For most of these we expect
-`pkg-config` information, for some we manually probe for libraries and include files. We
-should treat all of the external dependencies consistently:
-
-1. Default to autodetect using `pkg-config` (e.g. the standard `jemalloc` drops a `.pc`
- into the system but we do not check for it.
-2. If no `.pc` is found perform a manual search for libraries under known names, and
- check for accessible symbols inside them.
-3. Check that include paths work.
-4. Allow a command-line override (e.g. `-DWITH_JEMALLOC=/...`).
-5. If none of the above work then fail the install if the dependency is hard, otherwise
- indicate it is not present in the `config.h`.
-
-Before doing any dependency detection we need to determine which search paths are
-really in use for the current compiler, after the `project` declaration we can use:
-```
-execute_process(COMMAND ${CMAKE_C_COMPILER} "--print-search-dirs"
- COMMAND grep "^libraries:"
- COMMAND sed "s/^libraries: =//"
- COMMAND tr ":" " "
- COMMAND tr -d "\n"
- OUTPUT_VARIABLE CC_SEARCH_DIRS
- RESULTS_VARIABLE CC_SEARCH_RES)
-string(REGEX MATCH "^[0-9]+" CC_SEARCH_RES ${CC_SEARCH_RES})
-#string(STRIP "${CC_SEARCH_RES}" CC_SEARCH_RES)
-if(0 LESS ${CC_SEARCH_RES})
- message(STATUS "Warning - cannot determine standard compiler library paths")
- # Note: we will probably need a different method for Windows...
-endif()
-
-```
-
-The output format for this switch works on both `Clang` and `gcc`, it also includes
-the include search path, which can be extracted in a similar way. Standard advice here
-is to list the `ldconfig` cache or use the `-V` flag to check, but this does not work
-consistently across platforms - in particular `gcc` will reconfigure `ld` when it is
-called to gcc's internal view of search paths. During experiments each of these
-alternative missed / added unused paths. Dumping the compiler's own estimate of the
-search paths seems to work consistently across clang/gcc/linux/freebsd configurations.
-
-The default behaviour in CMake is to search across predefined paths (e.g. `CMAKE_LIBRARY_PATH`)
-that are based on heuristics about the current platform. Most projects using CMake seem
-to overwrite this with their own estimates.
-
-We can use the extracted paths as a base, add our own heuristics based on OS and then
-`set(CMAKE_LIBRARY_PATH ${OUR_OWN_LIB_SEARCH})` to get the best results. Roughly we do
-the following for each external dependency:
-```
-set(WITH_JSONC "Detect" CACHE STRING "Manually set the path to a json-c installation")
-...
-if(${WITH_JSONC} STREQUAL "Detect")
- pkg_check_modules(JSONC json-c) # Don't set the REQUIRED flag
- if(JSONC_FOUND)
- message(STATUS "libjsonc found through .pc -> ${JSONC_CFLAGS_OTHER} ${JSONC_LIBRARIES}")
- # ... setup using JSONC_CFLAGS_OTHER JSONC_LIBRARIES and JSONC_INCLUDE_DIRS
- else()
- find_library(LIB_JSONC
- NAMES json-c libjson-c
- PATHS ${CMAKE_LIBRARY_PATH}) # Includes our additions by this point
- if(${LIB_JSONC} STREQUAL "LIB_JSONC-NOTFOUND")
- message(STATUS "Library json-c not installed, disabling")
- else()
- check_library_exists(${LIB_JSONC} json_object_get_type "" HAVE_JSONC)
- # ... setup using heuristics for CFLAGS and check include files are available
- endif()
- endif()
-else()
- # ... use explicit path as base to check for library and includes ...
-endif()
-
-```
-
-For checking the include path we have two options, if we overwrite the `CMAKE_`... variables
-to change the internal search path we can use:
-```
-CHECK_INCLUDE_FILE(json/json.h HAVE_JSONC_H)
-```
-Or we can build a custom search path and then use:
-```
-find_file(HAVE_JSONC_H json/json.h PATHS ${OUR_INCLUDE_PATHS})
-```
-
-Note: we may have cases where there is no `.pc` but we have access to a `.cmake` (e.g. AWS SDK, mongodb,cmocka) - these need to be checked / pulled inside the repo while building a prototype.
-
-### Compiler compatibility checks
-
-In CMakeLists.txt:
-
-```
-CHECK_INCLUDE_FILE(sys/prctl.h HAVE_PRCTL_H)
-configure_file(cmake/config.in config.h)
-```
-
-In cmake/config.in:
-
-```
-#cmakedefine HAVE_PRCTL_H 1
-```
-
-If we want to check explicitly if something compiles (e.g. the accept4 check, or the
-`strerror_r` typing issue) then we set the `CMAKE_`... paths and then use:
-```
-check_c_source_compiles(
- "
- #include <string.h>
- int main() { char x = *strerror_r(0, &x, sizeof(x)); return 0; }
- "
- STRERROR_R_CHAR_P)
-
-```
-This produces a bool that we can use inside CMake or propagate into the `config.h`.
-
-We can handle the atomic checks with:
-```
-check_c_source_compiles(
- "
- int main (int argc, char **argv)
- {
- volatile unsigned long ul1 = 1, ul2 = 0, ul3 = 2;
- __atomic_load_n(&ul1, __ATOMIC_SEQ_CST);
- __atomic_compare_exchange(&ul1, &ul2, &ul3, 1, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST);
- __atomic_fetch_add(&ul1, 1, __ATOMIC_SEQ_CST);
- __atomic_fetch_sub(&ul3, 1, __ATOMIC_SEQ_CST);
- __atomic_or_fetch(&ul1, ul2, __ATOMIC_SEQ_CST);
- __atomic_and_fetch(&ul1, ul2, __ATOMIC_SEQ_CST);
- volatile unsigned long long ull1 = 1, ull2 = 0, ull3 = 2;
- __atomic_load_n(&ull1, __ATOMIC_SEQ_CST);
- __atomic_compare_exchange(&ull1, &ull2, &ull3, 1, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST);
- __atomic_fetch_add(&ull1, 1, __ATOMIC_SEQ_CST);
- __atomic_fetch_sub(&ull3, 1, __ATOMIC_SEQ_CST);
- __atomic_or_fetch(&ull1, ull2, __ATOMIC_SEQ_CST);
- __atomic_and_fetch(&ull1, ull2, __ATOMIC_SEQ_CST);
- return 0;
- }
- "
- HAVE_C__ATOMIC)
-```
-
-For the specific problem of getting the correct type signature in log.c for the `strerror_r`
-calls we can replicate what we have now, or we can delete this code completely and use a
-better solution that is documented [here](http://www.club.cc.cmu.edu/~cmccabe/blog_strerror.html).
-To replicate what we have now:
-```
-check_c_source_compiles(
- "
- #include <string.h>
- int main() { char x = *strerror_r(0, &x, sizeof(x)); return 0; }
- "
- STRERROR_R_CHAR_P)
-
-check_c_source_compiles(
- "
- #include <string.h>
- int main() { int x = strerror_r(0, &x, sizeof(x)); return 0; }
- "
- STRERROR_R_INT)
-
-if("${STRERROR_R_CHAR_P}" OR "${STRERROR_R_INT}")
- set(HAVE_DECL_STRERROR_R 1)
-endif()
-message(STATUS "Result was ${HAVE_DECL_STRERROR_R}")
-
-```
-
-Note: I did not find an explicit way to select compiler when both `clang` and `gcc` are
-present. We might have an implicit way (like redirecting `cc`) but we should put one in.
-
-
-
-### Debugging problems in test compilations
-
-Test compilations attempt to feed a test-input into the targeted compiler and result
-in a yes/no decision, this is similar to `AC_LANG_SOURCE(.... if test $ac_...` in .`m4`.
-We have two techniques to use in CMake:
-```
-cmake_minimum_required(VERSION 3.1.0)
-include(CheckCCompilerFlag)
-project(empty C)
-
-check_c_source_compiles(
- "
- #include <string.h>
- int main() { char x = *strerror_r(0, &x, sizeof(x)); return 0; }
- "
- STRERROR_R_CHAR_P)
-
-try_compile(HAVE_JEMALLOC ${CMAKE_CURRENT_BINARY_DIR}
- ${CMAKE_CURRENT_SOURCE_DIR}/quickdemo.c
- LINK_LIBRARIES jemalloc)
-```
-
-The `check_c_source_compiles` is light-weight:
-
-* Inline source for the test, easy to follow.
-* Build errors are reported in `CMakeFiles/CMakeErrors.log`
-
-But we cannot alter the include-paths / library-paths / compiler-flags specifically for
-the test without overwriting the current CMake settings. The alternative approach is
-slightly more heavy-weight:
-
-* Can't inline source for `try_compile` - it requires a `.c` file in the tree.
-* Build errors are not shown, the recovery process for them is somewhat difficult.
-
-```
-rm -rf * && cmake .. --debug-trycompile
-grep jemal CMakeFiles/CMakeTmp/CMakeFiles/*dir/*
-cd CMakeFiles/CMakeTmp/CMakeFiles/cmTC_d6f0e.dir # for example
-cmake --build ../..
-```
-
-This implies that we can do this to diagnose problems / develop test-programs, but we
-have to make them *bullet-proof* as we cannot expose this to end-users. This means that
-the results of the compilation must be *crisp* - exactly yes/no if the feature we are
-testing is supported.
-
-### System configuration checks
-
-For any system configuration checks that fall outside of the above scope (includes, libraries,
-packages, test-compilation checks) we have a fall-back that we can use to glue any holes
-that we need, e.g. to pull out the packaging strings, inside the `CMakeLists.h`:
-```
-execute_process(COMMAND cat ${CMAKE_CURRENT_SOURCE_DIR}/packaging/version
- COMMAND tr -d '\n'
- OUTPUT_VARIABLE VERSION_FROM_FILE)
-message(STATUS "Packaging version ${VERSION_FROM_FILE}")
-```
-and this in the `config.h.in`:
-```
-#define VERSION_FROM_FILE "@VERSION_FROM_FILE@"
-```
-
-## CMake build time
-
-We have a working definition of the targets that is in use with CLion and works on modern
-CMake (3.15). It breaks on older CMake version (e.g. 3.7) with an error message (issue#7091).
-No PoC yet to fix this, but it looks like changing the target properties should do it (in the
-worst case we can drop the separate object completely and merge the sources directly into
-the final target).
-
-Steps needed for building a prototype:
-
-1. Pick a reasonable configuration.
-2. Use the PoC techniques above to do a full generation of `CMAKE_` variables in the cache
- according to the feature options and dependencies.
-3. Push these into the project variables.
-4. Work on it until the build succeeds in at least one known configuration.
-5. Smoke-test that the output is valid (i.e. the executable loads and runs, and we can
- access the dashboard).
-6. Do a full comparison of the `config.h` generated by autotools against the CMake version
- and document / fix any deviations.
-
-## CMake install target
-
-I've only looked at this superficially as we do not have a prototype yet, but each of the
-first-stage install steps (in `make install`) and the second-stage (in `netdata-installer.sh`)
-look feasible.
-
-## General issues
-
-* We need to choose a minimum CMake version that is an available package across all of our
- supported environments. There is currently a build issue #7091 that documents a problem
- in the compilation phase (we cannot link in libnetdata as an object on old CMake versions
- and need to find a different way to express this).
-
-* The default variable-expansion / comparisons in CMake are awkward, we need this to make it
- sane:
- ```
- cmake_policy(SET CMP0054 "NEW")
- ```
-* Default paths for libs / includes are not comprehensive on most environments, we still need
- some heuristics for common locations, e.g. `/usr/local` on FreeBSD.
-
-# Recommendations
-
-We should follow these steps:
-
-1. Build a prototype.
-2. Build a test-environment to check the prototype against environments / configurations that
- the team uses.
-3. Perform an "internal" release - merge the new CMake into master, but not announce it or
- offer to support it.
-4. Check it works for the team internally.
-5. Do a soft-release: offer it externally as a replacement option for autotools.
-6. Gather feedback and usage reports on a wider range of configurations.
-7. Do a hard-release: switch over the preferred build-system in the installation instructions.
-8. Gather feedback and usage reports on a wider range of configurations (again).
-9. Deprecate / remove the autotools build-system completely (so that we can support a single
- build-system).
-
-Some smaller miscellaneous suggestions:
-
-1. Remove the `_Generic` / `strerror_r` config to make the system simpler (use the technique
- on the blog post to make the standard version re-entrant so that it is thread-safe).
-2. Pull in jemalloc by source into the repo if it is our preferred malloc implementation.
-
-# Background
-
-* [Stack overflow starting point](https://stackoverflow.com/questions/7132862/how-do-i-convert-an-autotools-project-to-a-cmake-project#7680240)
-* [CMake wiki including previous autotools conversions](https://gitlab.kitware.com/cmake/community/wikis/Home)
-* [Commands section in old CMake docs](https://cmake.org/cmake/help/v2.8.8/cmake.html#section_Commands)
-* [try_compile in newer CMake docs](https://cmake.org/cmake/help/v3.7/command/try_compile.html)
-* [configure_file in newer CMake docs](https://cmake.org/cmake/help/v3.7/command/configure_file.html?highlight=configure_file)
-* [header checks in CMake](https://stackoverflow.com/questions/647892/how-to-check-header-files-and-library-functions-in-cmake-like-it-is-done-in-auto)
-* [how to write platform checks](https://gitlab.kitware.com/cmake/community/wikis/doc/tutorials/How-To-Write-Platform-Checks)
-
-
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 7619153d..acec2c71 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,11 +1,184 @@
# Changelog
-## [v1.37.1](https://github.com/netdata/netdata/tree/v1.37.1) (2022-12-05)
+## [v1.38.0](https://github.com/netdata/netdata/tree/v1.38.0) (2023-02-06)
-[Full Changelog](https://github.com/netdata/netdata/compare/v1.37.0...v1.37.1)
+[Full Changelog](https://github.com/netdata/netdata/compare/v1.37.1...v1.38.0)
**Merged pull requests:**
+- Updated w1sensor.chart.py [\#14435](https://github.com/netdata/netdata/pull/14435) ([martindue](https://github.com/martindue))
+- replication to streaming transition when there are gaps [\#14434](https://github.com/netdata/netdata/pull/14434) ([ktsaou](https://github.com/ktsaou))
+- turn error\(\) to internal\_error\(\) [\#14428](https://github.com/netdata/netdata/pull/14428) ([ktsaou](https://github.com/ktsaou))
+- Fix typo on the netdata-functions.md [\#14426](https://github.com/netdata/netdata/pull/14426) ([lokerhp](https://github.com/lokerhp))
+- Update screenshot of timezone selector [\#14425](https://github.com/netdata/netdata/pull/14425) ([cakrit](https://github.com/cakrit))
+- Stop training thread from processing training requests once cancelled. [\#14423](https://github.com/netdata/netdata/pull/14423) ([vkalintiris](https://github.com/vkalintiris))
+- Check on parents the microseconds delta sent by agents [\#14422](https://github.com/netdata/netdata/pull/14422) ([ktsaou](https://github.com/ktsaou))
+- better logging of invalid pages detected on dbengine files [\#14420](https://github.com/netdata/netdata/pull/14420) ([ktsaou](https://github.com/ktsaou))
+- fix functions memory leak [\#14419](https://github.com/netdata/netdata/pull/14419) ([ktsaou](https://github.com/ktsaou))
+- Move under Developer in Learn [\#14417](https://github.com/netdata/netdata/pull/14417) ([cakrit](https://github.com/cakrit))
+- Libnetdata readmes learn [\#14416](https://github.com/netdata/netdata/pull/14416) ([cakrit](https://github.com/cakrit))
+- Minor fixes in markdown links [\#14415](https://github.com/netdata/netdata/pull/14415) ([tkatsoulas](https://github.com/tkatsoulas))
+- fix kubelet alarms [\#14414](https://github.com/netdata/netdata/pull/14414) ([ilyam8](https://github.com/ilyam8))
+- DBENGINE v2 - bug fixes [\#14413](https://github.com/netdata/netdata/pull/14413) ([ktsaou](https://github.com/ktsaou))
+- fix\(cgroups.plugin\): fix collecting full pressure stall time [\#14410](https://github.com/netdata/netdata/pull/14410) ([ilyam8](https://github.com/ilyam8))
+- feat\(charts.d\): add load usage \(Watts\) to nuts collector [\#14407](https://github.com/netdata/netdata/pull/14407) ([ilyam8](https://github.com/ilyam8))
+- fix link to ebpf collector [\#14405](https://github.com/netdata/netdata/pull/14405) ([ilyam8](https://github.com/ilyam8))
+- Remove equality when deciding how to use point [\#14402](https://github.com/netdata/netdata/pull/14402) ([MrZammler](https://github.com/MrZammler))
+- add help line to functions response [\#14399](https://github.com/netdata/netdata/pull/14399) ([ktsaou](https://github.com/ktsaou))
+- Fix typo on the page [\#14397](https://github.com/netdata/netdata/pull/14397) ([iorvd](https://github.com/iorvd))
+- Fix kickstart and updater not working with BusyBox wget [\#14392](https://github.com/netdata/netdata/pull/14392) ([Dim-P](https://github.com/Dim-P))
+- Fix publishing Docker Images to secondary registries. [\#14389](https://github.com/netdata/netdata/pull/14389) ([Ferroin](https://github.com/Ferroin))
+- Reduce service exit [\#14381](https://github.com/netdata/netdata/pull/14381) ([thiagoftsm](https://github.com/thiagoftsm))
+- DBENGINE v2 - improvements part 12 [\#14379](https://github.com/netdata/netdata/pull/14379) ([ktsaou](https://github.com/ktsaou))
+- bump go.d.plugin v0.50.0 [\#14378](https://github.com/netdata/netdata/pull/14378) ([ilyam8](https://github.com/ilyam8))
+- Patch master [\#14377](https://github.com/netdata/netdata/pull/14377) ([tkatsoulas](https://github.com/tkatsoulas))
+- Revert "Delete libnetdata readme" [\#14374](https://github.com/netdata/netdata/pull/14374) ([cakrit](https://github.com/cakrit))
+- Revert "Add libnetdata readmes to learn, delete empty" [\#14373](https://github.com/netdata/netdata/pull/14373) ([cakrit](https://github.com/cakrit))
+- Publish Docker images to GHCR.io and Quay.io [\#14372](https://github.com/netdata/netdata/pull/14372) ([Ferroin](https://github.com/Ferroin))
+- Add libnetdata readmes to learn, delete empty [\#14371](https://github.com/netdata/netdata/pull/14371) ([cakrit](https://github.com/cakrit))
+- Add collectors main readme to learn [\#14370](https://github.com/netdata/netdata/pull/14370) ([cakrit](https://github.com/cakrit))
+- Add collectors list to learn temporarily [\#14369](https://github.com/netdata/netdata/pull/14369) ([cakrit](https://github.com/cakrit))
+- Add simple patterns readme to learn [\#14366](https://github.com/netdata/netdata/pull/14366) ([cakrit](https://github.com/cakrit))
+- Add one way allocator readme to learn [\#14365](https://github.com/netdata/netdata/pull/14365) ([cakrit](https://github.com/cakrit))
+- Add July README to learn [\#14364](https://github.com/netdata/netdata/pull/14364) ([cakrit](https://github.com/cakrit))
+- Add ARL readme to learn [\#14363](https://github.com/netdata/netdata/pull/14363) ([cakrit](https://github.com/cakrit))
+- Add BUFFER lib doc to learn [\#14362](https://github.com/netdata/netdata/pull/14362) ([cakrit](https://github.com/cakrit))
+- Add dictionary readme to learn [\#14361](https://github.com/netdata/netdata/pull/14361) ([cakrit](https://github.com/cakrit))
+- Add explanation of config files to learn [\#14360](https://github.com/netdata/netdata/pull/14360) ([cakrit](https://github.com/cakrit))
+- Delete libnetdata readme [\#14357](https://github.com/netdata/netdata/pull/14357) ([cakrit](https://github.com/cakrit))
+- Add main health readme to learn [\#14356](https://github.com/netdata/netdata/pull/14356) ([cakrit](https://github.com/cakrit))
+- Delete QUICKSTART.md [\#14355](https://github.com/netdata/netdata/pull/14355) ([cakrit](https://github.com/cakrit))
+- Delete data structures readme [\#14354](https://github.com/netdata/netdata/pull/14354) ([cakrit](https://github.com/cakrit))
+- Delete BREAKING\_CHANGES.md [\#14353](https://github.com/netdata/netdata/pull/14353) ([cakrit](https://github.com/cakrit))
+- Add redistributed to learn [\#14352](https://github.com/netdata/netdata/pull/14352) ([cakrit](https://github.com/cakrit))
+- Add missing entries in README.md [\#14351](https://github.com/netdata/netdata/pull/14351) ([thiagoftsm](https://github.com/thiagoftsm))
+- Add ansible.md to learn [\#14350](https://github.com/netdata/netdata/pull/14350) ([cakrit](https://github.com/cakrit))
+- Delete BUILD.md [\#14348](https://github.com/netdata/netdata/pull/14348) ([cakrit](https://github.com/cakrit))
+- Patch convert rel links [\#14344](https://github.com/netdata/netdata/pull/14344) ([tkatsoulas](https://github.com/tkatsoulas))
+- Update dashboard [\#14342](https://github.com/netdata/netdata/pull/14342) ([thiagoftsm](https://github.com/thiagoftsm))
+- minor fix on notification doc \(Discord\) [\#14339](https://github.com/netdata/netdata/pull/14339) ([tkatsoulas](https://github.com/tkatsoulas))
+- DBENGINE v2 - improvements part 11 [\#14337](https://github.com/netdata/netdata/pull/14337) ([ktsaou](https://github.com/ktsaou))
+- Update the Get started doc [\#14336](https://github.com/netdata/netdata/pull/14336) ([Ancairon](https://github.com/Ancairon))
+- Notifications integration docs [\#14335](https://github.com/netdata/netdata/pull/14335) ([hugovalente-pm](https://github.com/hugovalente-pm))
+- DBENGINE v2 - improvements part 10 [\#14332](https://github.com/netdata/netdata/pull/14332) ([ktsaou](https://github.com/ktsaou))
+- reviewed the docs functions to fix broken links and other additions [\#14331](https://github.com/netdata/netdata/pull/14331) ([hugovalente-pm](https://github.com/hugovalente-pm))
+- Add |nowarn and |noclear notification modifiers [\#14330](https://github.com/netdata/netdata/pull/14330) ([vobruba-martin](https://github.com/vobruba-martin))
+- Revert "Misc SSL improvements" [\#14327](https://github.com/netdata/netdata/pull/14327) ([MrZammler](https://github.com/MrZammler))
+- DBENGINE v2 - improvements part 9 [\#14326](https://github.com/netdata/netdata/pull/14326) ([ktsaou](https://github.com/ktsaou))
+- Don't send alert variables to the cloud [\#14325](https://github.com/netdata/netdata/pull/14325) ([MrZammler](https://github.com/MrZammler))
+- fix\(proc.plugin\): add "cpu" label to per core util% charts [\#14322](https://github.com/netdata/netdata/pull/14322) ([ilyam8](https://github.com/ilyam8))
+- DBENGINE v2 - improvements part 8 [\#14319](https://github.com/netdata/netdata/pull/14319) ([ktsaou](https://github.com/ktsaou))
+- Misc SSL improvements [\#14317](https://github.com/netdata/netdata/pull/14317) ([MrZammler](https://github.com/MrZammler))
+- Use "getent group" instead of reading "/etc/group" to get group information [\#14316](https://github.com/netdata/netdata/pull/14316) ([Dim-P](https://github.com/Dim-P))
+- Add nvidia smi pci bandwidth percent collector [\#14315](https://github.com/netdata/netdata/pull/14315) ([ghanapunq](https://github.com/ghanapunq))
+- minor - kaitai for netdata datafiles [\#14312](https://github.com/netdata/netdata/pull/14312) ([underhood](https://github.com/underhood))
+- Add Collector log [\#14309](https://github.com/netdata/netdata/pull/14309) ([thiagoftsm](https://github.com/thiagoftsm))
+- DBENGINE v2 - improvements part 7 [\#14307](https://github.com/netdata/netdata/pull/14307) ([ktsaou](https://github.com/ktsaou))
+- Fix Exporiting compilaton error [\#14306](https://github.com/netdata/netdata/pull/14306) ([thiagoftsm](https://github.com/thiagoftsm))
+- bump go.d.plugin to v0.49.2 [\#14305](https://github.com/netdata/netdata/pull/14305) ([ilyam8](https://github.com/ilyam8))
+- Fixes required to make the agent work without crashes on MacOS [\#14304](https://github.com/netdata/netdata/pull/14304) ([vkalintiris](https://github.com/vkalintiris))
+- Update kickstart script to use new DEB infrastructure. [\#14301](https://github.com/netdata/netdata/pull/14301) ([Ferroin](https://github.com/Ferroin))
+- DBENGINE v2 - improvements part 6 [\#14299](https://github.com/netdata/netdata/pull/14299) ([ktsaou](https://github.com/ktsaou))
+- add consul license expiration time alarm [\#14298](https://github.com/netdata/netdata/pull/14298) ([ilyam8](https://github.com/ilyam8))
+- Fix macos struct definition. [\#14297](https://github.com/netdata/netdata/pull/14297) ([vkalintiris](https://github.com/vkalintiris))
+- Remove archivedcharts endpoint, optimize indices [\#14296](https://github.com/netdata/netdata/pull/14296) ([stelfrag](https://github.com/stelfrag))
+- track memory footprint of Netdata [\#14294](https://github.com/netdata/netdata/pull/14294) ([ktsaou](https://github.com/ktsaou))
+- Switch to self-hosted infrastructure for DEB package distribution. [\#14290](https://github.com/netdata/netdata/pull/14290) ([Ferroin](https://github.com/Ferroin))
+- DBENGINE v2 - improvements part 5 [\#14289](https://github.com/netdata/netdata/pull/14289) ([ktsaou](https://github.com/ktsaou))
+- allow multiple local-build/static-install options in kickstart [\#14287](https://github.com/netdata/netdata/pull/14287) ([ilyam8](https://github.com/ilyam8))
+- fix\(alarms\): treat 0 processors as unknown in load\_cpu\_number [\#14286](https://github.com/netdata/netdata/pull/14286) ([ilyam8](https://github.com/ilyam8))
+- DBENGINE v2 - improvements part 4 [\#14285](https://github.com/netdata/netdata/pull/14285) ([ktsaou](https://github.com/ktsaou))
+- fix for dbengine2 improvements part 3 [\#14284](https://github.com/netdata/netdata/pull/14284) ([ktsaou](https://github.com/ktsaou))
+- Make sure variables are streamed after SENDER\_CONNECTED flag is set [\#14283](https://github.com/netdata/netdata/pull/14283) ([MrZammler](https://github.com/MrZammler))
+- Update to SQLITE version 3.40.1 [\#14282](https://github.com/netdata/netdata/pull/14282) ([stelfrag](https://github.com/stelfrag))
+- Check session variable before resuming it [\#14279](https://github.com/netdata/netdata/pull/14279) ([MrZammler](https://github.com/MrZammler))
+- Update infographic image on main README [\#14276](https://github.com/netdata/netdata/pull/14276) ([cakrit](https://github.com/cakrit))
+- bump go.d.plugin to v0.49.1 [\#14275](https://github.com/netdata/netdata/pull/14275) ([ilyam8](https://github.com/ilyam8))
+- Improve ebpf exit [\#14270](https://github.com/netdata/netdata/pull/14270) ([thiagoftsm](https://github.com/thiagoftsm))
+- DBENGINE v2 - improvements part 3 [\#14269](https://github.com/netdata/netdata/pull/14269) ([ktsaou](https://github.com/ktsaou))
+- minor - add kaitaistruct for journal v2 files [\#14267](https://github.com/netdata/netdata/pull/14267) ([underhood](https://github.com/underhood))
+- fix\(health\): don't assume 2 cores if the number is unknown [\#14265](https://github.com/netdata/netdata/pull/14265) ([ilyam8](https://github.com/ilyam8))
+- More 32bit fixes [\#14264](https://github.com/netdata/netdata/pull/14264) ([ktsaou](https://github.com/ktsaou))
+- Store host and claim info in sqlite as soon as possible [\#14263](https://github.com/netdata/netdata/pull/14263) ([MrZammler](https://github.com/MrZammler))
+- Replace individual collector images/links on infographic [\#14262](https://github.com/netdata/netdata/pull/14262) ([cakrit](https://github.com/cakrit))
+- Fix binpkg updates on OpenSUSE [\#14260](https://github.com/netdata/netdata/pull/14260) ([Dim-P](https://github.com/Dim-P))
+- DBENGINE v2 - improvements 2 [\#14257](https://github.com/netdata/netdata/pull/14257) ([ktsaou](https://github.com/ktsaou))
+- fix\(pacakging\): fix cpu/memory metrics when running inside LXC container as systemd service [\#14255](https://github.com/netdata/netdata/pull/14255) ([ilyam8](https://github.com/ilyam8))
+- fix\(proc.plugin\): handle disabled IPv6 [\#14252](https://github.com/netdata/netdata/pull/14252) ([ilyam8](https://github.com/ilyam8))
+- DBENGINE v2 - improvements part 1 [\#14251](https://github.com/netdata/netdata/pull/14251) ([ktsaou](https://github.com/ktsaou))
+- Remove daemon/common.h header from libnetdata [\#14248](https://github.com/netdata/netdata/pull/14248) ([vkalintiris](https://github.com/vkalintiris))
+- allow the cache to grow when huge queries are running that exceed the cache size [\#14247](https://github.com/netdata/netdata/pull/14247) ([ktsaou](https://github.com/ktsaou))
+- Update netdata-overview.xml [\#14245](https://github.com/netdata/netdata/pull/14245) ([andrewm4894](https://github.com/andrewm4894))
+- Revert health to run in a single thread [\#14244](https://github.com/netdata/netdata/pull/14244) ([MrZammler](https://github.com/MrZammler))
+- profile startup and shutdown timings [\#14243](https://github.com/netdata/netdata/pull/14243) ([ktsaou](https://github.com/ktsaou))
+- `ml - machine learning` to just `machine learning` [\#14242](https://github.com/netdata/netdata/pull/14242) ([andrewm4894](https://github.com/andrewm4894))
+- cancel ml threads on shutdown and join them on host free [\#14240](https://github.com/netdata/netdata/pull/14240) ([ktsaou](https://github.com/ktsaou))
+- pre gcc v5 support and allow building without dbengine [\#14239](https://github.com/netdata/netdata/pull/14239) ([ktsaou](https://github.com/ktsaou))
+- Drop ARMv7 native packages for Fedora 36. [\#14233](https://github.com/netdata/netdata/pull/14233) ([Ferroin](https://github.com/Ferroin))
+- fix consul\_raft\_leadership\_transitions alarm units [\#14232](https://github.com/netdata/netdata/pull/14232) ([ilyam8](https://github.com/ilyam8))
+- readme updates [\#14224](https://github.com/netdata/netdata/pull/14224) ([andrewm4894](https://github.com/andrewm4894))
+- bump go.d v0.49.0 [\#14220](https://github.com/netdata/netdata/pull/14220) ([ilyam8](https://github.com/ilyam8))
+- remove lgtm.com [\#14216](https://github.com/netdata/netdata/pull/14216) ([ilyam8](https://github.com/ilyam8))
+- Improve file descriptor closing loops [\#14213](https://github.com/netdata/netdata/pull/14213) ([Dim-P](https://github.com/Dim-P))
+- Remove temporary allocations when preprocessing a samples buffer [\#14208](https://github.com/netdata/netdata/pull/14208) ([vkalintiris](https://github.com/vkalintiris))
+- Create ML charts on child hosts. [\#14207](https://github.com/netdata/netdata/pull/14207) ([vkalintiris](https://github.com/vkalintiris))
+- Use brackets around info variables [\#14206](https://github.com/netdata/netdata/pull/14206) ([MrZammler](https://github.com/MrZammler))
+- Dont call worker\_utilization\_finish\(\) twice [\#14204](https://github.com/netdata/netdata/pull/14204) ([MrZammler](https://github.com/MrZammler))
+- Switch to actions/labeler@v4 for labeling PRs. [\#14203](https://github.com/netdata/netdata/pull/14203) ([Ferroin](https://github.com/Ferroin))
+- Refactor ML code and add support for multiple KMeans models [\#14198](https://github.com/netdata/netdata/pull/14198) ([vkalintiris](https://github.com/vkalintiris))
+- Add few alarms for elasticsearch [\#14197](https://github.com/netdata/netdata/pull/14197) ([ilyam8](https://github.com/ilyam8))
+- chore\(packaging\): remove python-pymongo [\#14196](https://github.com/netdata/netdata/pull/14196) ([ilyam8](https://github.com/ilyam8))
+- bump go.d.plugin to v0.48.0 [\#14195](https://github.com/netdata/netdata/pull/14195) ([ilyam8](https://github.com/ilyam8))
+- Fix typos [\#14194](https://github.com/netdata/netdata/pull/14194) ([rex4539](https://github.com/rex4539))
+- add `telegraf` to `apps_groups.conf` monitoring section [\#14188](https://github.com/netdata/netdata/pull/14188) ([andrewm4894](https://github.com/andrewm4894))
+- bump go.d.plugin to v0.47.0 [\#14182](https://github.com/netdata/netdata/pull/14182) ([ilyam8](https://github.com/ilyam8))
+- remove mqtt-c from websockets [\#14181](https://github.com/netdata/netdata/pull/14181) ([underhood](https://github.com/underhood))
+- fix logrotate postrotate [\#14180](https://github.com/netdata/netdata/pull/14180) ([ilyam8](https://github.com/ilyam8))
+- docs: explicitly set the `nofile` limit for Netdata container and document the reason for this [\#14178](https://github.com/netdata/netdata/pull/14178) ([ilyam8](https://github.com/ilyam8))
+- remove interface name from cgroup net family [\#14174](https://github.com/netdata/netdata/pull/14174) ([ilyam8](https://github.com/ilyam8))
+- use specific charts labels instead of family in alarms [\#14173](https://github.com/netdata/netdata/pull/14173) ([ilyam8](https://github.com/ilyam8))
+- Revert "Refactor ML code and add support for multiple KMeans models. … [\#14172](https://github.com/netdata/netdata/pull/14172) ([vkalintiris](https://github.com/vkalintiris))
+- fix a typo in debian postinst [\#14171](https://github.com/netdata/netdata/pull/14171) ([ilyam8](https://github.com/ilyam8))
+- feat\(packaging\): add netdata to www-data group on Proxmox [\#14168](https://github.com/netdata/netdata/pull/14168) ([ilyam8](https://github.com/ilyam8))
+- minor - fix localhost nodeinstance fnc caps [\#14166](https://github.com/netdata/netdata/pull/14166) ([underhood](https://github.com/underhood))
+- minor - Adds query type "function\[s\]" for aclk chart [\#14165](https://github.com/netdata/netdata/pull/14165) ([underhood](https://github.com/underhood))
+- Fix race on query thread startup [\#14164](https://github.com/netdata/netdata/pull/14164) ([underhood](https://github.com/underhood))
+- add alarms and dashboard info for Consul [\#14163](https://github.com/netdata/netdata/pull/14163) ([ilyam8](https://github.com/ilyam8))
+- Ensure --claim-url for the claim script is a URL [\#14160](https://github.com/netdata/netdata/pull/14160) ([ralphm](https://github.com/ralphm))
+- Finish switch to self-hosted RPM repositories. [\#14158](https://github.com/netdata/netdata/pull/14158) ([Ferroin](https://github.com/Ferroin))
+- fix nodejs app detection [\#14156](https://github.com/netdata/netdata/pull/14156) ([ilyam8](https://github.com/ilyam8))
+- Populate field values in send\_slack\(\) for Mattermost [\#14153](https://github.com/netdata/netdata/pull/14153) ([je2555](https://github.com/je2555))
+- bump go.d.plugin to v0.46.1 [\#14151](https://github.com/netdata/netdata/pull/14151) ([ilyam8](https://github.com/ilyam8))
+- Add a health configuration option of which alarms to load [\#14150](https://github.com/netdata/netdata/pull/14150) ([MrZammler](https://github.com/MrZammler))
+- MQTT5 Topic Alias [\#14148](https://github.com/netdata/netdata/pull/14148) ([underhood](https://github.com/underhood))
+- Disable integration by default \(eBPF \<-\> APPS\) [\#14147](https://github.com/netdata/netdata/pull/14147) ([thiagoftsm](https://github.com/thiagoftsm))
+- Revert "MQTT 5 publish topic alias support" [\#14145](https://github.com/netdata/netdata/pull/14145) ([MrZammler](https://github.com/MrZammler))
+- rename "Pid" to "PID" in functions [\#14144](https://github.com/netdata/netdata/pull/14144) ([andrewm4894](https://github.com/andrewm4894))
+- Document memory mode alloc [\#14142](https://github.com/netdata/netdata/pull/14142) ([cakrit](https://github.com/cakrit))
+- fix\(packaging\): add setuid for cgroup-network and ebpf.plugin in RPM [\#14140](https://github.com/netdata/netdata/pull/14140) ([ilyam8](https://github.com/ilyam8))
+- use chart labels in portcheck alarms [\#14137](https://github.com/netdata/netdata/pull/14137) ([ilyam8](https://github.com/ilyam8))
+- Remove Fedora 35 from the list of supported platforms. [\#14136](https://github.com/netdata/netdata/pull/14136) ([Ferroin](https://github.com/Ferroin))
+- Fix conditions for uploading repoconfig packages to new infra. [\#14134](https://github.com/netdata/netdata/pull/14134) ([Ferroin](https://github.com/Ferroin))
+- fix httpcheck alarms [\#14133](https://github.com/netdata/netdata/pull/14133) ([ilyam8](https://github.com/ilyam8))
+- eBPF \(memory, NV, basis for functions\) [\#14131](https://github.com/netdata/netdata/pull/14131) ([thiagoftsm](https://github.com/thiagoftsm))
+- DBENGINE v2 [\#14125](https://github.com/netdata/netdata/pull/14125) ([ktsaou](https://github.com/ktsaou))
+- bump go.d.plugin to v0.46.0 [\#14124](https://github.com/netdata/netdata/pull/14124) ([ilyam8](https://github.com/ilyam8))
+- heartbeat: don't log every discrepancy [\#14122](https://github.com/netdata/netdata/pull/14122) ([ktsaou](https://github.com/ktsaou))
+- ARAL: add destroy function and optimize ifdefs [\#14121](https://github.com/netdata/netdata/pull/14121) ([ktsaou](https://github.com/ktsaou))
+- Enable retries for SSL\_ERROR\_WANT\_READ [\#14120](https://github.com/netdata/netdata/pull/14120) ([MrZammler](https://github.com/MrZammler))
+- ci: fix cgroup-parent name in packaging [\#14118](https://github.com/netdata/netdata/pull/14118) ([ilyam8](https://github.com/ilyam8))
+- don't log too much about streaming connections [\#14117](https://github.com/netdata/netdata/pull/14117) ([ktsaou](https://github.com/ktsaou))
+- fix get\_system\_cpus\(\) [\#14116](https://github.com/netdata/netdata/pull/14116) ([ktsaou](https://github.com/ktsaou))
+- Fix minor typo. [\#14111](https://github.com/netdata/netdata/pull/14111) ([tkatsoulas](https://github.com/tkatsoulas))
+- expose ACLK SSL KeyLog interface for developers [\#14109](https://github.com/netdata/netdata/pull/14109) ([underhood](https://github.com/underhood))
+- add filtering options to functions table output [\#14108](https://github.com/netdata/netdata/pull/14108) ([ktsaou](https://github.com/ktsaou))
+- fix health emphemerality labels src [\#14105](https://github.com/netdata/netdata/pull/14105) ([ilyam8](https://github.com/ilyam8))
+- fix docker host editable config [\#14104](https://github.com/netdata/netdata/pull/14104) ([ilyam8](https://github.com/ilyam8))
+- Switch to self-hosted infrastructure for RPM package distribution. [\#14100](https://github.com/netdata/netdata/pull/14100) ([Ferroin](https://github.com/Ferroin))
+- Fix missing required package install of tar on FreeBSD [\#14095](https://github.com/netdata/netdata/pull/14095) ([Dim-P](https://github.com/Dim-P))
+- Add version to netdatacli [\#14094](https://github.com/netdata/netdata/pull/14094) ([MrZammler](https://github.com/MrZammler))
+- docs: add a note to set container nofile ulimit for Fedora users [\#14092](https://github.com/netdata/netdata/pull/14092) ([ilyam8](https://github.com/ilyam8))
+- Fix eBPF load on RH 8.x family and improve code. [\#14090](https://github.com/netdata/netdata/pull/14090) ([thiagoftsm](https://github.com/thiagoftsm))
- fix v1.37 dbengine page alignment crashes [\#14086](https://github.com/netdata/netdata/pull/14086) ([ktsaou](https://github.com/ktsaou))
- Fix \_\_atomic\_compare\_exchange\_n\(\) atomics [\#14085](https://github.com/netdata/netdata/pull/14085) ([ktsaou](https://github.com/ktsaou))
- Fix 1.37 crashes [\#14081](https://github.com/netdata/netdata/pull/14081) ([stelfrag](https://github.com/stelfrag))
@@ -15,7 +188,21 @@
- fix SSL related crashes [\#14076](https://github.com/netdata/netdata/pull/14076) ([ktsaou](https://github.com/ktsaou))
- remove python.d/springboot [\#14075](https://github.com/netdata/netdata/pull/14075) ([ilyam8](https://github.com/ilyam8))
- fix backfilling statistics [\#14074](https://github.com/netdata/netdata/pull/14074) ([ktsaou](https://github.com/ktsaou))
+- remove deprecated fping.plugin in accordance with v1.37.0 deprecation notice [\#14073](https://github.com/netdata/netdata/pull/14073) ([ilyam8](https://github.com/ilyam8))
+- remove deprecated python.d collectors announced in v1.37.0 deprecation notice [\#14072](https://github.com/netdata/netdata/pull/14072) ([ilyam8](https://github.com/ilyam8))
- Add workflow dispatch trigger for parent/child with cloud integration smoke tests [\#14070](https://github.com/netdata/netdata/pull/14070) ([dimko](https://github.com/dimko))
+- MQTT 5 publish topic alias support [\#14067](https://github.com/netdata/netdata/pull/14067) ([underhood](https://github.com/underhood))
+- Refactor ML code and add support for multiple KMeans models. [\#14065](https://github.com/netdata/netdata/pull/14065) ([vkalintiris](https://github.com/vkalintiris))
+- Adds some introspection into the MQTT\_WSS [\#14039](https://github.com/netdata/netdata/pull/14039) ([underhood](https://github.com/underhood))
+- add clickhouse third party collector and install instructions [\#14021](https://github.com/netdata/netdata/pull/14021) ([andrewm4894](https://github.com/andrewm4894))
+- Switch nightlies to GitHub releases. [\#14020](https://github.com/netdata/netdata/pull/14020) ([Ferroin](https://github.com/Ferroin))
+- Wmi descriptions [\#14001](https://github.com/netdata/netdata/pull/14001) ([thiagoftsm](https://github.com/thiagoftsm))
+- Introduce the new Structure of the documentation [\#13915](https://github.com/netdata/netdata/pull/13915) ([Ancairon](https://github.com/Ancairon))
+- Finish renaming the `--install` option to `--install-prefix`. [\#13881](https://github.com/netdata/netdata/pull/13881) ([Ferroin](https://github.com/Ferroin))
+
+## [v1.37.1](https://github.com/netdata/netdata/tree/v1.37.1) (2022-12-05)
+
+[Full Changelog](https://github.com/netdata/netdata/compare/v1.37.0...v1.37.1)
## [v1.37.0](https://github.com/netdata/netdata/tree/v1.37.0) (2022-11-30)
@@ -177,144 +364,6 @@
- allow netdata installer to install and run netdata as any user [\#13780](https://github.com/netdata/netdata/pull/13780) ([ktsaou](https://github.com/ktsaou))
- Update libbpf 1.0.1 [\#13778](https://github.com/netdata/netdata/pull/13778) ([thiagoftsm](https://github.com/thiagoftsm))
- Bump websockets submodule [\#13776](https://github.com/netdata/netdata/pull/13776) ([underhood](https://github.com/underhood))
-- Rename variable for old CentOS version [\#13775](https://github.com/netdata/netdata/pull/13775) ([thiagoftsm](https://github.com/thiagoftsm))
-- Further improvements to the new service installation code. [\#13774](https://github.com/netdata/netdata/pull/13774) ([Ferroin](https://github.com/Ferroin))
-- Pandas collector [\#13773](https://github.com/netdata/netdata/pull/13773) ([andrewm4894](https://github.com/andrewm4894))
-- dbengine free from RRDSET and RRDDIM [\#13772](https://github.com/netdata/netdata/pull/13772) ([ktsaou](https://github.com/ktsaou))
-- bump go.d.plugin v0.40.3 [\#13771](https://github.com/netdata/netdata/pull/13771) ([ilyam8](https://github.com/ilyam8))
-- Update fping plugin documentation with better details about the required version. [\#13765](https://github.com/netdata/netdata/pull/13765) ([Ferroin](https://github.com/Ferroin))
-- fix bad merge [\#13764](https://github.com/netdata/netdata/pull/13764) ([ktsaou](https://github.com/ktsaou))
-- Remove anomaly rates chart. [\#13763](https://github.com/netdata/netdata/pull/13763) ([vkalintiris](https://github.com/vkalintiris))
-- add 1m delay for tcp reset alarms [\#13761](https://github.com/netdata/netdata/pull/13761) ([ilyam8](https://github.com/ilyam8))
-- Use /bin/sh instead of ls to detect glibc [\#13758](https://github.com/netdata/netdata/pull/13758) ([MrZammler](https://github.com/MrZammler))
-- Add ZFS rate charts [\#13757](https://github.com/netdata/netdata/pull/13757) ([vlvkobal](https://github.com/vlvkobal))
-- Count currently streaming senders on the localhost [\#13755](https://github.com/netdata/netdata/pull/13755) ([MrZammler](https://github.com/MrZammler))
-- Fix streaming crash when child reconnects and is archived on the parent [\#13754](https://github.com/netdata/netdata/pull/13754) ([stelfrag](https://github.com/stelfrag))
-- Add CloudLinux OS detection to the updater script [\#13752](https://github.com/netdata/netdata/pull/13752) ([Pulseeey](https://github.com/Pulseeey))
-- Add CloudLinux OS detection to kickstart [\#13750](https://github.com/netdata/netdata/pull/13750) ([Pulseeey](https://github.com/Pulseeey))
-- bump go.d v0.40.2 [\#13747](https://github.com/netdata/netdata/pull/13747) ([ilyam8](https://github.com/ilyam8))
-- fix\(python.d\): set correct label source for \_collect\_job label [\#13746](https://github.com/netdata/netdata/pull/13746) ([ilyam8](https://github.com/ilyam8))
-- Provide Details on Label Filtering/Custom Labels [\#13745](https://github.com/netdata/netdata/pull/13745) ([DShreve2](https://github.com/DShreve2))
-- Fix handling of temporary directories in kickstart code. [\#13744](https://github.com/netdata/netdata/pull/13744) ([Ferroin](https://github.com/Ferroin))
-- Dont send NodeInfo during first database cleanup [\#13740](https://github.com/netdata/netdata/pull/13740) ([MrZammler](https://github.com/MrZammler))
-- CMake - add possibility to build without ACLK [\#13736](https://github.com/netdata/netdata/pull/13736) ([underhood](https://github.com/underhood))
-- Change cast to remove coverity warnings [\#13735](https://github.com/netdata/netdata/pull/13735) ([thiagoftsm](https://github.com/thiagoftsm))
-- Do not try to start an archived host in dbengine if dbengine is not compiled [\#13724](https://github.com/netdata/netdata/pull/13724) ([stelfrag](https://github.com/stelfrag))
-- Allow netdata plugins to expose functions for querying more information about specific charts [\#13720](https://github.com/netdata/netdata/pull/13720) ([ktsaou](https://github.com/ktsaou))
-- feat\(health\): add new Redis alarms [\#13715](https://github.com/netdata/netdata/pull/13715) ([ilyam8](https://github.com/ilyam8))
-- Health thread per host [\#13712](https://github.com/netdata/netdata/pull/13712) ([MrZammler](https://github.com/MrZammler))
-- Faster streaming by 25% on the child [\#13708](https://github.com/netdata/netdata/pull/13708) ([ktsaou](https://github.com/ktsaou))
-- Do not create train/predict dimensions meant for tracking anomaly rates. [\#13707](https://github.com/netdata/netdata/pull/13707) ([vkalintiris](https://github.com/vkalintiris))
-- Update exporting unit tests [\#13706](https://github.com/netdata/netdata/pull/13706) ([vlvkobal](https://github.com/vlvkobal))
-- bump go.d.plugin to v0.40.1 [\#13704](https://github.com/netdata/netdata/pull/13704) ([ilyam8](https://github.com/ilyam8))
-- Build judy even without dbengine [\#13703](https://github.com/netdata/netdata/pull/13703) ([underhood](https://github.com/underhood))
-- alarms collector: ability to exclude certain alarms via config [\#13701](https://github.com/netdata/netdata/pull/13701) ([andrewm4894](https://github.com/andrewm4894))
-- Fix inconsistent alert class names [\#13699](https://github.com/netdata/netdata/pull/13699) ([ralphm](https://github.com/ralphm))
-- disable Postgres last vacuum/analyze alarms [\#13698](https://github.com/netdata/netdata/pull/13698) ([ilyam8](https://github.com/ilyam8))
-- QUERY\_TARGET: new query engine for Netdata Agent [\#13697](https://github.com/netdata/netdata/pull/13697) ([ktsaou](https://github.com/ktsaou))
-- Update dashboard to version v2.29.1. [\#13696](https://github.com/netdata/netdata/pull/13696) ([netdatabot](https://github.com/netdatabot))
-- docs: nvidia-smi in a container limitation note [\#13695](https://github.com/netdata/netdata/pull/13695) ([ilyam8](https://github.com/ilyam8))
-- Use CMake generated config.h also in out of tree CMake build [\#13692](https://github.com/netdata/netdata/pull/13692) ([underhood](https://github.com/underhood))
-- Add info for Docker containers about using hostname from host. [\#13685](https://github.com/netdata/netdata/pull/13685) ([Ferroin](https://github.com/Ferroin))
-- add node level AR based example [\#13684](https://github.com/netdata/netdata/pull/13684) ([andrewm4894](https://github.com/andrewm4894))
-- Store nulls instead of empty strings in health tables [\#13683](https://github.com/netdata/netdata/pull/13683) ([MrZammler](https://github.com/MrZammler))
-- Fix warnings during compilation time on ARM \(32 bits\) [\#13681](https://github.com/netdata/netdata/pull/13681) ([thiagoftsm](https://github.com/thiagoftsm))
-- dictionary updated documentation and cosmetics [\#13679](https://github.com/netdata/netdata/pull/13679) ([ktsaou](https://github.com/ktsaou))
-- disable internal log [\#13678](https://github.com/netdata/netdata/pull/13678) ([ktsaou](https://github.com/ktsaou))
-- bump go.d.plugin v0.40.0 [\#13675](https://github.com/netdata/netdata/pull/13675) ([ilyam8](https://github.com/ilyam8))
-- remove \_instance\_family label [\#13674](https://github.com/netdata/netdata/pull/13674) ([ilyam8](https://github.com/ilyam8))
-- fix typo not deleting collected flag; force removing collected flag on child disconnect [\#13672](https://github.com/netdata/netdata/pull/13672) ([ktsaou](https://github.com/ktsaou))
-- feat\(health\): add Postgres alarms [\#13671](https://github.com/netdata/netdata/pull/13671) ([ilyam8](https://github.com/ilyam8))
-- add proxysql dashboard info [\#13669](https://github.com/netdata/netdata/pull/13669) ([ilyam8](https://github.com/ilyam8))
-- Additional sqlite statistics [\#13668](https://github.com/netdata/netdata/pull/13668) ([stelfrag](https://github.com/stelfrag))
-- Advance the buffer properly to scan the journal file [\#13666](https://github.com/netdata/netdata/pull/13666) ([stelfrag](https://github.com/stelfrag))
-- Add sqlite page cache hit and miss statistics [\#13665](https://github.com/netdata/netdata/pull/13665) ([stelfrag](https://github.com/stelfrag))
-- bump go.d.plugin to v0.39.0 [\#13662](https://github.com/netdata/netdata/pull/13662) ([ilyam8](https://github.com/ilyam8))
-- update Postgres dashboard info [\#13661](https://github.com/netdata/netdata/pull/13661) ([ilyam8](https://github.com/ilyam8))
-- Use mmap if possible during startup for journal replay [\#13660](https://github.com/netdata/netdata/pull/13660) ([stelfrag](https://github.com/stelfrag))
-- Remove anomaly detector [\#13657](https://github.com/netdata/netdata/pull/13657) ([vkalintiris](https://github.com/vkalintiris))
-- Update dashboard to version v2.29.0. [\#13654](https://github.com/netdata/netdata/pull/13654) ([netdatabot](https://github.com/netdatabot))
-- Fix container virtualization info [\#13653](https://github.com/netdata/netdata/pull/13653) ([vlvkobal](https://github.com/vlvkobal))
-- Do not free AR dimensions from within ML. [\#13651](https://github.com/netdata/netdata/pull/13651) ([vkalintiris](https://github.com/vkalintiris))
-- Remove Chart/Dim based communication [\#13650](https://github.com/netdata/netdata/pull/13650) ([underhood](https://github.com/underhood))
-- Improve agent shutdown time [\#13649](https://github.com/netdata/netdata/pull/13649) ([stelfrag](https://github.com/stelfrag))
-- add \_collect\_job label to python.d/\* charts [\#13648](https://github.com/netdata/netdata/pull/13648) ([ilyam8](https://github.com/ilyam8))
-- RRD structures managed by dictionaries [\#13646](https://github.com/netdata/netdata/pull/13646) ([ktsaou](https://github.com/ktsaou))
-- fix rrdcontexts left in the post-processing queue from the garbage collector [\#13645](https://github.com/netdata/netdata/pull/13645) ([ktsaou](https://github.com/ktsaou))
-- apps.plugin: Re-add `chrome` to the `webbrowser` group. [\#13642](https://github.com/netdata/netdata/pull/13642) ([Ferroin](https://github.com/Ferroin))
-- Fix a memory leak on archived host creation [\#13641](https://github.com/netdata/netdata/pull/13641) ([stelfrag](https://github.com/stelfrag))
-- fix compile issues [\#13640](https://github.com/netdata/netdata/pull/13640) ([ktsaou](https://github.com/ktsaou))
-- Obsolete RRDSET state [\#13635](https://github.com/netdata/netdata/pull/13635) ([ktsaou](https://github.com/ktsaou))
-- Updated tc.plugin \(linux bandwidth QoS\) [\#13634](https://github.com/netdata/netdata/pull/13634) ([ktsaou](https://github.com/ktsaou))
-- Fix worker utilization cleanup [\#13633](https://github.com/netdata/netdata/pull/13633) ([stelfrag](https://github.com/stelfrag))
-- remove forgotten avl structure from rrdcalc [\#13632](https://github.com/netdata/netdata/pull/13632) ([ktsaou](https://github.com/ktsaou))
-- Deaggregate the `gui` and `email` app groupx and improve GUI coverage. [\#13631](https://github.com/netdata/netdata/pull/13631) ([Ferroin](https://github.com/Ferroin))
-- Faster rrdcontext [\#13629](https://github.com/netdata/netdata/pull/13629) ([ktsaou](https://github.com/ktsaou))
-- Update uninstaller documentation. [\#13627](https://github.com/netdata/netdata/pull/13627) ([Ferroin](https://github.com/Ferroin))
-- eBPF different improvements [\#13624](https://github.com/netdata/netdata/pull/13624) ([thiagoftsm](https://github.com/thiagoftsm))
-- adjust systemdunits alarms [\#13623](https://github.com/netdata/netdata/pull/13623) ([ilyam8](https://github.com/ilyam8))
-- fix apps plugin users charts descriptipon [\#13621](https://github.com/netdata/netdata/pull/13621) ([ilyam8](https://github.com/ilyam8))
-- add Postgres total connection utilization alarm [\#13620](https://github.com/netdata/netdata/pull/13620) ([ilyam8](https://github.com/ilyam8))
-- update Postgres "connections" dashboard info [\#13619](https://github.com/netdata/netdata/pull/13619) ([ilyam8](https://github.com/ilyam8))
-- Assorted updates for apps\_groups.conf. [\#13618](https://github.com/netdata/netdata/pull/13618) ([Ferroin](https://github.com/Ferroin))
-- add spiceproxy to proxmox group [\#13615](https://github.com/netdata/netdata/pull/13615) ([ilyam8](https://github.com/ilyam8))
-- Improve coverage of Linux kernel threads in apps\_groups.conf [\#13612](https://github.com/netdata/netdata/pull/13612) ([Ferroin](https://github.com/Ferroin))
-- Clean chart hash map [\#13611](https://github.com/netdata/netdata/pull/13611) ([stelfrag](https://github.com/stelfrag))
-- Update dashboard to version v2.28.8. [\#13609](https://github.com/netdata/netdata/pull/13609) ([netdatabot](https://github.com/netdatabot))
-- Don't try to load db rows when chart\_id or dim\_id is null [\#13608](https://github.com/netdata/netdata/pull/13608) ([MrZammler](https://github.com/MrZammler))
-- Add info text for wal and update for checkpoints [\#13607](https://github.com/netdata/netdata/pull/13607) ([shyamvalsan](https://github.com/shyamvalsan))
-- bump go.d.plugin to v0.38.0 [\#13603](https://github.com/netdata/netdata/pull/13603) ([ilyam8](https://github.com/ilyam8))
-- Use prepared statements for context related queries [\#13602](https://github.com/netdata/netdata/pull/13602) ([stelfrag](https://github.com/stelfrag))
-- fix\(cgroups.plugin\): fix chart id length check [\#13601](https://github.com/netdata/netdata/pull/13601) ([ilyam8](https://github.com/ilyam8))
-- Temporary fix for command injection vulnerability in GHA workflow. [\#13600](https://github.com/netdata/netdata/pull/13600) ([Ferroin](https://github.com/Ferroin))
-- update logind dashboard info [\#13597](https://github.com/netdata/netdata/pull/13597) ([ilyam8](https://github.com/ilyam8))
-- Add link to the performance optimization guide [\#13595](https://github.com/netdata/netdata/pull/13595) ([cakrit](https://github.com/cakrit))
-- sqlite3 global statistics [\#13594](https://github.com/netdata/netdata/pull/13594) ([ktsaou](https://github.com/ktsaou))
-- feat\(python.d/nvidia\_smi\): collect power state [\#13580](https://github.com/netdata/netdata/pull/13580) ([ilyam8](https://github.com/ilyam8))
-- fix\(python.d/nvidia\_smi\): repsect update\_every for polling [\#13579](https://github.com/netdata/netdata/pull/13579) ([ilyam8](https://github.com/ilyam8))
-- prevent crash on rrdcontext apis when rrdcontexts is not initialized [\#13578](https://github.com/netdata/netdata/pull/13578) ([ktsaou](https://github.com/ktsaou))
-- CMake improvements part 1 [\#13575](https://github.com/netdata/netdata/pull/13575) ([underhood](https://github.com/underhood))
-- bump go.d.plugin to v0.37.2 [\#13574](https://github.com/netdata/netdata/pull/13574) ([ilyam8](https://github.com/ilyam8))
-- Updating info for postgreqsql metrics [\#13573](https://github.com/netdata/netdata/pull/13573) ([shyamvalsan](https://github.com/shyamvalsan))
-- add `apt` to `apps_groups.conf` [\#13571](https://github.com/netdata/netdata/pull/13571) ([andrewm4894](https://github.com/andrewm4894))
-- Deduplicate all netdata strings [\#13570](https://github.com/netdata/netdata/pull/13570) ([ktsaou](https://github.com/ktsaou))
-- eBPF cmake missing include dir [\#13568](https://github.com/netdata/netdata/pull/13568) ([underhood](https://github.com/underhood))
-- chore: removing logging that a chart collection in the same interpolation point [\#13567](https://github.com/netdata/netdata/pull/13567) ([ilyam8](https://github.com/ilyam8))
-- add more monitoring tools to `apps_groups.conf` [\#13566](https://github.com/netdata/netdata/pull/13566) ([andrewm4894](https://github.com/andrewm4894))
-- fix\(health.d/mysql\): adjust `mysql_galera_cluster_size_max_2m` lookup to make time in warn/crit predictable [\#13563](https://github.com/netdata/netdata/pull/13563) ([ilyam8](https://github.com/ilyam8))
-- Update dashboard to version v2.28.6. [\#13562](https://github.com/netdata/netdata/pull/13562) ([netdatabot](https://github.com/netdatabot))
-- Prefer context attributes from non archived charts [\#13559](https://github.com/netdata/netdata/pull/13559) ([MrZammler](https://github.com/MrZammler))
-- bump go.d.plugin to v0.37.1 [\#13555](https://github.com/netdata/netdata/pull/13555) ([ilyam8](https://github.com/ilyam8))
-- Fix coverity 380387 [\#13551](https://github.com/netdata/netdata/pull/13551) ([MrZammler](https://github.com/MrZammler))
-- add docker dashboard info [\#13547](https://github.com/netdata/netdata/pull/13547) ([ilyam8](https://github.com/ilyam8))
-- bump go.d.plugin to v0.37.0 [\#13546](https://github.com/netdata/netdata/pull/13546) ([ilyam8](https://github.com/ilyam8))
-- feat\(python.d/sensors\): discover chips, features at runtime [\#13545](https://github.com/netdata/netdata/pull/13545) ([ilyam8](https://github.com/ilyam8))
-- Remove aclk\_api.\[ch\] [\#13540](https://github.com/netdata/netdata/pull/13540) ([underhood](https://github.com/underhood))
-- Cleanup of APIs [\#13539](https://github.com/netdata/netdata/pull/13539) ([underhood](https://github.com/underhood))
-- bump go.d version to v0.36.0 [\#13538](https://github.com/netdata/netdata/pull/13538) ([ilyam8](https://github.com/ilyam8))
-- chore\(python.d\): rename dockerd job on lock registration [\#13537](https://github.com/netdata/netdata/pull/13537) ([ilyam8](https://github.com/ilyam8))
-- Update MacOS community support details [\#13536](https://github.com/netdata/netdata/pull/13536) ([DShreve2](https://github.com/DShreve2))
-- Fix a crash when xen libraries are misconfigured [\#13535](https://github.com/netdata/netdata/pull/13535) ([vlvkobal](https://github.com/vlvkobal))
-- Add summary dashboard for PostgreSQL [\#13534](https://github.com/netdata/netdata/pull/13534) ([shyamvalsan](https://github.com/shyamvalsan))
-- add `jupyter` to `apps_groups.conf` [\#13533](https://github.com/netdata/netdata/pull/13533) ([andrewm4894](https://github.com/andrewm4894))
-- Schedule next rotation based on absolute time [\#13531](https://github.com/netdata/netdata/pull/13531) ([MrZammler](https://github.com/MrZammler))
-- Improve PID monitoring \(step 2\) [\#13530](https://github.com/netdata/netdata/pull/13530) ([thiagoftsm](https://github.com/thiagoftsm))
-- fix\(health\): set default curl connection timeout if not set [\#13529](https://github.com/netdata/netdata/pull/13529) ([ilyam8](https://github.com/ilyam8))
-- Update FreeIPMI and CUPS plugin documentation. [\#13526](https://github.com/netdata/netdata/pull/13526) ([Ferroin](https://github.com/Ferroin))
-- Use LVM UUIDs in chart ids for logical volumes [\#13525](https://github.com/netdata/netdata/pull/13525) ([vlvkobal](https://github.com/vlvkobal))
-- fix\(cgroups.plugin\): use Docker API for name resolution when Docker is a snap package [\#13523](https://github.com/netdata/netdata/pull/13523) ([ilyam8](https://github.com/ilyam8))
-- remove reference to charts now in netdata monitoring [\#13521](https://github.com/netdata/netdata/pull/13521) ([andrewm4894](https://github.com/andrewm4894))
-- fix\(ci\): fix fetching tags in Build workflow [\#13517](https://github.com/netdata/netdata/pull/13517) ([ilyam8](https://github.com/ilyam8))
-- docs\(postfix\): add a note about `authorized_mailq_users` [\#13515](https://github.com/netdata/netdata/pull/13515) ([ilyam8](https://github.com/ilyam8))
-- Remove extra U from log message [\#13514](https://github.com/netdata/netdata/pull/13514) ([uplime](https://github.com/uplime))
-- Print rrdcontexts versions with PRIu64 [\#13511](https://github.com/netdata/netdata/pull/13511) ([MrZammler](https://github.com/MrZammler))
-- Calculate name hash after rrdvar\_fix\_name [\#13509](https://github.com/netdata/netdata/pull/13509) ([MrZammler](https://github.com/MrZammler))
-- fix\(packaging\): add CAP\_NET\_ADMIN for go.d.plugin [\#13507](https://github.com/netdata/netdata/pull/13507) ([ilyam8](https://github.com/ilyam8))
-- netdata.service: Update PIDFile to avoid systemd legacy path warning [\#13504](https://github.com/netdata/netdata/pull/13504) ([candrews](https://github.com/candrews))
-- chore\(python.d\): remove python.d/\* announced in v1.36.0 deprecation notice [\#13503](https://github.com/netdata/netdata/pull/13503) ([ilyam8](https://github.com/ilyam8))
-- Add Fedora 37 to CI and package builds. [\#13489](https://github.com/netdata/netdata/pull/13489) ([Ferroin](https://github.com/Ferroin))
-- Overhaul handling of installation of Netdata as a system service. [\#13451](https://github.com/netdata/netdata/pull/13451) ([Ferroin](https://github.com/Ferroin))
-- reduce memcpy and memory usage on mqtt5 [\#13450](https://github.com/netdata/netdata/pull/13450) ([underhood](https://github.com/underhood))
## [v1.36.1](https://github.com/netdata/netdata/tree/v1.36.1) (2022-08-15)
@@ -324,46 +373,6 @@
[Full Changelog](https://github.com/netdata/netdata/compare/v1.35.1...v1.36.0)
-**Merged pull requests:**
-
-- rrdcontexts allow not linked dimensions and charts [\#13501](https://github.com/netdata/netdata/pull/13501) ([ktsaou](https://github.com/ktsaou))
-- docs: add deprecation notice to python.d/postgres readme [\#13497](https://github.com/netdata/netdata/pull/13497) ([ilyam8](https://github.com/ilyam8))
-- docs: change postgres links to go version [\#13496](https://github.com/netdata/netdata/pull/13496) ([ilyam8](https://github.com/ilyam8))
-- bump go.d version to v0.35.0 [\#13494](https://github.com/netdata/netdata/pull/13494) ([ilyam8](https://github.com/ilyam8))
-- add PgBouncer charts description and icon to dashboard info [\#13493](https://github.com/netdata/netdata/pull/13493) ([ilyam8](https://github.com/ilyam8))
-- Add chart\_context to alert snapshots [\#13492](https://github.com/netdata/netdata/pull/13492) ([MrZammler](https://github.com/MrZammler))
-- Remove prompt to add dashboard issues [\#13490](https://github.com/netdata/netdata/pull/13490) ([cakrit](https://github.com/cakrit))
-- docs: fix unresolved file references [\#13488](https://github.com/netdata/netdata/pull/13488) ([ilyam8](https://github.com/ilyam8))
-- docs: add a note about edit-config for docker installs [\#13487](https://github.com/netdata/netdata/pull/13487) ([ilyam8](https://github.com/ilyam8))
-- health: disable go python last collected alarms [\#13485](https://github.com/netdata/netdata/pull/13485) ([ilyam8](https://github.com/ilyam8))
-- bump go.d.plugin version to v0.34.0 [\#13484](https://github.com/netdata/netdata/pull/13484) ([ilyam8](https://github.com/ilyam8))
-- chore: add WireGuard description and icon to dashboard info [\#13483](https://github.com/netdata/netdata/pull/13483) ([ilyam8](https://github.com/ilyam8))
-- feat\(cgroups.plugin\): resolve nomad containers name \(docker driver only\) [\#13481](https://github.com/netdata/netdata/pull/13481) ([ilyam8](https://github.com/ilyam8))
-- Check for protected when excluding mounts [\#13479](https://github.com/netdata/netdata/pull/13479) ([MrZammler](https://github.com/MrZammler))
-- update postgres dashboard info [\#13474](https://github.com/netdata/netdata/pull/13474) ([ilyam8](https://github.com/ilyam8))
-- Remove the single threaded arrayallocator optiomization during agent startup [\#13473](https://github.com/netdata/netdata/pull/13473) ([stelfrag](https://github.com/stelfrag))
-- Handle cases where entries where stored as text \(with strftime\("%s"\)\) [\#13472](https://github.com/netdata/netdata/pull/13472) ([stelfrag](https://github.com/stelfrag))
-- Enable rrdcontexts by default [\#13471](https://github.com/netdata/netdata/pull/13471) ([stelfrag](https://github.com/stelfrag))
-- Fix cgroup name detection for docker containers in containerd cgroup [\#13470](https://github.com/netdata/netdata/pull/13470) ([xkisu](https://github.com/xkisu))
-- Trimmed-median, trimmed-mean and percentile [\#13469](https://github.com/netdata/netdata/pull/13469) ([ktsaou](https://github.com/ktsaou))
-- rrdcontext support for hidden charts [\#13466](https://github.com/netdata/netdata/pull/13466) ([ktsaou](https://github.com/ktsaou))
-- Load host labels for archived hosts [\#13464](https://github.com/netdata/netdata/pull/13464) ([stelfrag](https://github.com/stelfrag))
-- fix\(python.d/smartd\_log\): handle log rotation [\#13460](https://github.com/netdata/netdata/pull/13460) ([ilyam8](https://github.com/ilyam8))
-- docs: add a note about network interface monitoring when running in a Docker container [\#13458](https://github.com/netdata/netdata/pull/13458) ([ilyam8](https://github.com/ilyam8))
-- fix a guide so we can reference it's subsections [\#13455](https://github.com/netdata/netdata/pull/13455) ([tkatsoulas](https://github.com/tkatsoulas))
-- Revert "Query queue only for queries" [\#13452](https://github.com/netdata/netdata/pull/13452) ([stelfrag](https://github.com/stelfrag))
-- /api/v1/weights endpoint [\#13449](https://github.com/netdata/netdata/pull/13449) ([ktsaou](https://github.com/ktsaou))
-- Get last\_entry\_t only when st changes [\#13448](https://github.com/netdata/netdata/pull/13448) ([MrZammler](https://github.com/MrZammler))
-- additional stats [\#13445](https://github.com/netdata/netdata/pull/13445) ([ktsaou](https://github.com/ktsaou))
-- Store host label information in the metadata database [\#13441](https://github.com/netdata/netdata/pull/13441) ([stelfrag](https://github.com/stelfrag))
-- Fix typo in PostgreSQL section header [\#13440](https://github.com/netdata/netdata/pull/13440) ([shyamvalsan](https://github.com/shyamvalsan))
-- Fix tests so that the actual metadata database is not accessed [\#13439](https://github.com/netdata/netdata/pull/13439) ([stelfrag](https://github.com/stelfrag))
-- Delete aclk\_alert table on start streaming from seq 1 batch 1 [\#13438](https://github.com/netdata/netdata/pull/13438) ([MrZammler](https://github.com/MrZammler))
-- Fix agent crash when archived host has not been registered to the cloud [\#13437](https://github.com/netdata/netdata/pull/13437) ([stelfrag](https://github.com/stelfrag))
-- Dont duplicate buffered bytes [\#13435](https://github.com/netdata/netdata/pull/13435) ([vlvkobal](https://github.com/vlvkobal))
-- Show last 15 alerts in notification [\#13434](https://github.com/netdata/netdata/pull/13434) ([MrZammler](https://github.com/MrZammler))
-- Query queue only for queries [\#13431](https://github.com/netdata/netdata/pull/13431) ([underhood](https://github.com/underhood))
-
## [v1.35.1](https://github.com/netdata/netdata/tree/v1.35.1) (2022-06-10)
[Full Changelog](https://github.com/netdata/netdata/compare/v1.35.0...v1.35.1)
diff --git a/CMakeLists.txt b/CMakeLists.txt
index c12e9c81..263fb812 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -453,8 +453,8 @@ set(LIBNETDATA_FILES
libnetdata/adaptive_resortable_list/adaptive_resortable_list.h
libnetdata/config/appconfig.c
libnetdata/config/appconfig.h
- libnetdata/arrayalloc/arrayalloc.c
- libnetdata/arrayalloc/arrayalloc.h
+ libnetdata/aral/aral.c
+ libnetdata/aral/aral.h
libnetdata/avl/avl.c
libnetdata/avl/avl.h
libnetdata/buffer/buffer.c
@@ -471,6 +471,8 @@ set(LIBNETDATA_FILES
libnetdata/eval/eval.h
libnetdata/health/health.c
libnetdata/health/health.h
+ libnetdata/july/july.c
+ libnetdata/july/july.h
libnetdata/inlined.h
libnetdata/json/json.c
libnetdata/json/json.h
@@ -775,8 +777,12 @@ set(RRD_PLUGIN_FILES
database/engine/rrdengineapi.h
database/engine/pagecache.c
database/engine/pagecache.h
- database/engine/rrdenglocking.c
- database/engine/rrdenglocking.h
+ database/engine/cache.c
+ database/engine/cache.h
+ database/engine/metric.c
+ database/engine/metric.h
+ database/engine/pdc.c
+ database/engine/pdc.h
database/KolmogorovSmirnovDist.c
database/KolmogorovSmirnovDist.h
)
@@ -938,8 +944,6 @@ set(MQTT_WEBSOCKETS_FILES
mqtt_websockets/c-rbuf/src/ringbuffer.c
mqtt_websockets/c-rbuf/include/ringbuffer.h
mqtt_websockets/c-rbuf/src/ringbuffer_internal.h
- mqtt_websockets/MQTT-C/src/mqtt.c
- mqtt_websockets/MQTT-C/include/mqtt.h
)
set(SPAWN_PLUGIN_FILES
@@ -1002,6 +1006,8 @@ set(DAEMON_FILES
daemon/common.h
daemon/daemon.c
daemon/daemon.h
+ daemon/event_loop.c
+ daemon/event_loop.h
daemon/global_statistics.c
daemon/global_statistics.h
daemon/analytics.c
@@ -1030,12 +1036,17 @@ set(ML_FILES
IF(ENABLE_ML)
message(STATUS "ML: enabled")
list(APPEND ML_FILES
+ ml/ADCharts.h
+ ml/ADCharts.cc
+ ml/Chart.h
+ ml/Chart.cc
ml/Config.h
ml/Config.cc
- ml/Dimension.cc
ml/Dimension.h
+ ml/Dimension.cc
ml/Host.h
ml/Host.cc
+ ml/Mutex.h
ml/Query.h
ml/KMeans.h
ml/KMeans.cc
@@ -1260,7 +1271,6 @@ list(APPEND NETDATA_COMMON_INCLUDE_DIRS ${PROTOBUF_INCLUDE_DIRS})
list(APPEND NETDATA_COMMON_CFLAGS ${PROTOBUF_CFLAGS_OTHER})
list(APPEND NETDATA_FILES ${ACLK_FILES} ${ACLK_PROTO_BUILT_SRCS} ${ACLK_PROTO_BUILT_HDRS})
include_directories(BEFORE ${CMAKE_SOURCE_DIR}/aclk/aclk-schemas)
-include_directories(BEFORE ${CMAKE_SOURCE_DIR}/mqtt_websockets/MQTT-C/include)
include_directories(BEFORE ${CMAKE_SOURCE_DIR}/mqtt_websockets/src/include)
include_directories(BEFORE ${CMAKE_SOURCE_DIR}/mqtt_websockets/c-rbuf/include)
@@ -1269,7 +1279,8 @@ ADD_LIBRARY(mqttwebsockets STATIC
target_compile_options(mqttwebsockets PUBLIC
-DMQTT_WSS_CUSTOM_ALLOC
- -DRBUF_CUSTOM_MALLOC)
+ -DRBUF_CUSTOM_MALLOC
+ -DMQTT_WSS_CPUSTATS)
target_include_directories(mqttwebsockets PUBLIC
${CMAKE_SOURCE_DIR}/aclk/helpers)
diff --git a/Makefile.am b/Makefile.am
index 5e160523..3eaf1ee8 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -23,7 +23,6 @@ CLEANFILES = \
EXTRA_DIST = \
.gitignore \
- .csslintrc \
.eslintignore \
.eslintrc \
.github/CODEOWNERS \
@@ -132,8 +131,8 @@ LIBNETDATA_FILES = \
libnetdata/adaptive_resortable_list/adaptive_resortable_list.h \
libnetdata/config/appconfig.c \
libnetdata/config/appconfig.h \
- libnetdata/arrayalloc/arrayalloc.c \
- libnetdata/arrayalloc/arrayalloc.h \
+ libnetdata/aral/aral.c \
+ libnetdata/aral/aral.h \
libnetdata/avl/avl.c \
libnetdata/avl/avl.h \
libnetdata/buffer/buffer.c \
@@ -149,6 +148,8 @@ LIBNETDATA_FILES = \
libnetdata/eval/eval.c \
libnetdata/eval/eval.h \
libnetdata/inlined.h \
+ libnetdata/july/july.c \
+ libnetdata/july/july.h \
libnetdata/libnetdata.c \
libnetdata/libnetdata.h \
libnetdata/required_dummies.h \
@@ -236,10 +237,15 @@ ML_FILES += \
ml/ADCharts.cc \
ml/Config.h \
ml/Config.cc \
+ ml/Chart.cc \
+ ml/Chart.h \
+ ml/Stats.h \
ml/Dimension.cc \
ml/Dimension.h \
ml/Host.h \
ml/Host.cc \
+ ml/Mutex.h \
+ ml/Queue.h \
ml/Query.h \
ml/KMeans.h \
ml/KMeans.cc \
@@ -262,13 +268,6 @@ ml/ml.$(OBJEXT) : CXXFLAGS += -Wno-psabi
endif
-
-if ENABLE_ML_TESTS
-ML_TESTS_FILES = \
- ml/SamplesBufferTests.cc \
- $(NULL)
-endif
-
IDLEJITTER_PLUGIN_FILES = \
collectors/idlejitter.plugin/plugin_idlejitter.c \
$(NULL)
@@ -549,8 +548,12 @@ if ENABLE_DBENGINE
database/engine/rrdengineapi.h \
database/engine/pagecache.c \
database/engine/pagecache.h \
- database/engine/rrdenglocking.c \
- database/engine/rrdenglocking.h \
+ database/engine/cache.c \
+ database/engine/cache.h \
+ database/engine/metric.c \
+ database/engine/metric.h \
+ database/engine/pdc.c \
+ database/engine/pdc.h \
$(NULL)
endif
@@ -732,10 +735,15 @@ libmqttwebsockets_a_SOURCES = \
mqtt_websockets/c-rbuf/src/ringbuffer.c \
mqtt_websockets/c-rbuf/include/ringbuffer.h \
mqtt_websockets/c-rbuf/src/ringbuffer_internal.h \
- mqtt_websockets/MQTT-C/src/mqtt.c \
- mqtt_websockets/MQTT-C/include/mqtt.h
+ mqtt_websockets/c_rhash/src/c_rhash.c \
+ mqtt_websockets/c_rhash/include/c_rhash.h \
+ mqtt_websockets/c_rhash/src/c_rhash_internal.h
-libmqttwebsockets_a_CFLAGS = $(CFLAGS) -DMQTT_WSS_CUSTOM_ALLOC -DRBUF_CUSTOM_MALLOC -I$(srcdir)/aclk/helpers
+libmqttwebsockets_a_CFLAGS = $(CFLAGS) -DMQTT_WSS_CUSTOM_ALLOC -DRBUF_CUSTOM_MALLOC -DMQTT_WSS_CPUSTATS -I$(srcdir)/aclk/helpers -I$(srcdir)/mqtt_websockets/c_rhash/include
+
+if MQTT_WSS_DEBUG
+libmqttwebsockets_a_CFLAGS += -DMQTT_WSS_DEBUG
+endif
mqtt_websockets/src/mqtt_wss_client.$(OBJEXT) : CFLAGS += -Wno-unused-result
@@ -891,6 +899,8 @@ DAEMON_FILES = \
daemon/common.h \
daemon/daemon.c \
daemon/daemon.h \
+ daemon/event_loop.c \
+ daemon/event_loop.h \
daemon/global_statistics.c \
daemon/global_statistics.h \
daemon/analytics.c \
@@ -916,7 +926,6 @@ NETDATA_FILES = \
$(EXPORTING_ENGINE_FILES) \
$(HEALTH_PLUGIN_FILES) \
$(ML_FILES) \
- $(ML_TESTS_FILES) \
$(IDLEJITTER_PLUGIN_FILES) \
$(PLUGINSD_PLUGIN_FILES) \
$(REGISTRY_PLUGIN_FILES) \
@@ -1004,11 +1013,6 @@ if ENABLE_ACLK
$(NULL)
endif
-if ENABLE_ML_TESTS
- netdata_LDADD += $(OPTIONAL_ML_TESTS_LIBS) \
- $(NULL)
-endif
-
netdata_LINK = $(CXXLD) $(CXXFLAGS) $(LDFLAGS) -o $@
sbin_PROGRAMS += netdatacli
diff --git a/README.md b/README.md
index f1f9a4c1..a25438db 100644
--- a/README.md
+++ b/README.md
@@ -6,24 +6,29 @@
<a href="https://github.com/netdata/netdata/"><img src="https://img.shields.io/github/stars/netdata/netdata?style=social" alt="GitHub Stars"></a>
<br />
<a href="https://github.com/netdata/netdata/releases/latest"><img src="https://img.shields.io/github/release/netdata/netdata.svg" alt="Latest release"></a>
- <a href="https://storage.googleapis.com/netdata-nightlies/latest-version.txt"><img src="https://img.shields.io/badge/dynamic/xml?url=https://storage.googleapis.com/netdata-nightlies/latest-version.txt&label=nightly%20release&query=/text()" alt="Nightly release"></a>
+ <a href="https://github.com/netdata/netdata-nightlies/releases/latest"><img src="https://img.shields.io/github/release/netdata/netdata-nightlies.svg" alt="Latest nightly build"></a>
<br />
- <a href="https://travis-ci.com/netdata/netdata"><img src="https://travis-ci.com/netdata/netdata.svg?branch=master" alt="Build status"></a>
<a href="https://bestpractices.coreinfrastructure.org/projects/2231"><img src="https://bestpractices.coreinfrastructure.org/projects/2231/badge" alt="CII Best Practices"></a>
- <a href="https://www.gnu.org/licenses/gpl-3.0"><img src="https://img.shields.io/badge/License-GPL%20v3%2B-blue.svg" alt="License: GPL v3+"></a>
- <br />
<a href="https://codeclimate.com/github/netdata/netdata"><img src="https://codeclimate.com/github/netdata/netdata/badges/gpa.svg" alt="Code Climate"></a>
- <a href="https://lgtm.com/projects/g/netdata/netdata/context:cpp"><img src="https://img.shields.io/lgtm/grade/cpp/g/netdata/netdata.svg?logo=lgtm" alt="LGTM C"></a>
- <a href="https://lgtm.com/projects/g/netdata/netdata/context:javascript"><img src="https://img.shields.io/lgtm/grade/javascript/g/netdata/netdata.svg?logo=lgtm" alt=""LGTM JS></a>
- <a href="https://lgtm.com/projects/g/netdata/netdata/context:python"><img src="https://img.shields.io/lgtm/grade/python/g/netdata/netdata.svg?logo=lgtm" alt="LGTM PYTHON"></a>
+ <a href="https://www.gnu.org/licenses/gpl-3.0"><img src="https://img.shields.io/badge/License-GPL%20v3%2B-blue.svg" alt="License: GPL v3+"></a>
</p>
<img src="https://user-images.githubusercontent.com/1153921/95269366-1b814680-07ee-11eb-8ff4-c1b0b8758499.png" alt="---" style="max-width: 100%;" />
-Netdata's **distributed, real-time monitoring Agent** collects thousands of metrics from systems, hardware, containers,
-and applications with zero configuration. It runs permanently on all your physical/virtual servers, containers, cloud
-deployments, and edge/IoT devices, and is perfectly safe to install on your systems mid-incident without any
-preparation.
+Netdata is a distributed, real-time, performance and health monitoring platform for systems, hardware, containers and applications, collecting thousands of useful metrics with zero configuration needed. It runs permanently on all your physical/virtual servers, containers, cloud deployments, and edge/IoT devices, and is perfectly safe to install on your systems mid-incident without any preparation.
+
+The Netdata [Agent](https://github.com/netdata/netdata) is an enormously powerful, **Open-Sourced**, **Single Node** health monitoring and performance troubleshooting tool.
+It gives you the ability to automatically identify processes, collect and store metrics locally and even more - visualize all metrics without any configuration (of course you can tweak it later on if you need).
+
+[Netdata Cloud](https://www.netdata.cloud) is a hosted web interface that gives you **Free**, real-time visibility into your **Entire Infrastructure** with secure access to your Netdata Agents. It provides an ability to automatically route your requests to the most relevant agents to display your metrics, based on the stored metadata (Agents topology, what metrics are collected on specific Agents as well as the retention information for each metric).
+
+It gives you some extra features, like [Metric Correlations](https://github.com/netdata/netdata/blob/master/docs/cloud/insights/metric-correlations.md), [Anomaly Advisor](https://github.com/netdata/netdata/blob/master/docs/cloud/insights/anomaly-advisor.mdx), [anomaly rates on every chart](https://blog.netdata.cloud/anomaly-rate-in-every-chart/) and much more.
+
+Try it for yourself right now by checking out the Netdata Cloud [demo space](https://app.netdata.cloud/spaces/netdata-demo/rooms/all-nodes/overview) (No sign up or login needed).
+
+Netdata's mission is to help more people troubleshoot ever more complex IT infrastructures, this is why our **free** [community plan](https://www.netdata.cloud/pricing) gives you ability to monitor unlimited number of Nodes, Containers and Metrics (custom or built-in).
+
+Due to the distributed nature of Netdata, and to ensure high-availability of your monitoring system, please check our [Data Replication](https://www.netdata.cloud/blog/why-is-data-replication-important) recommendations to increase the data availability.
You can install Netdata on most Linux distributions (Ubuntu, Debian, CentOS, and more), container platforms (Kubernetes
clusters, Docker), and many other operating systems (FreeBSD, macOS). No `sudo` required.
@@ -72,7 +77,7 @@ Here's what you can expect from Netdata:
synchronize charts as you pan through time, zoom in on anomalies, and more.
- **Visual anomaly detection**: Our UI/UX emphasizes the relationships between charts to help you detect the root
cause of anomalies.
-- **Machine learning (ML) features out of the box**: Unsupervised ML-based [anomaly detection](https://learn.netdata.cloud/docs/cloud/insights/anomaly-advisor), every second, every metric, zero-config! [Metric correlations](https://learn.netdata.cloud/docs/cloud/insights/metric-correlations) to help with short-term change detection. And other [additional](https://learn.netdata.cloud/guides/monitor/anomaly-detection) ML-based features to help make your life easier.
+- **Machine learning (ML) features out of the box**: Unsupervised ML-based [anomaly detection](https://github.com/netdata/netdata/blob/master/docs/cloud/insights/anomaly-advisor.mdx), every second, every metric, zero-config! [Metric correlations](https://github.com/netdata/netdata/blob/master/docs/cloud/insights/metric-correlations.md) to help with short-term change detection. And other [additional](https://github.com/netdata/netdata/blob/master/docs/guides/monitor/anomaly-detection.md) ML-based features to help make your life easier.
- **Scales to infinity**: You can install it on all your servers, containers, VMs, and IoT devices. Metrics are not
centralized by default, so there is no limit.
- **Several operating modes**: Autonomous host monitoring (the default), headless data collector, forwarding proxy,
@@ -83,17 +88,17 @@ Netdata works with tons of applications, notifications platforms, and other time
- **300+ system, container, and application endpoints**: Collectors autodetect metrics from default endpoints and
immediately visualize them into meaningful charts designed for troubleshooting. See [everything we
- support](https://learn.netdata.cloud/docs/agent/collectors/collectors).
+ support](https://github.com/netdata/netdata/blob/master/collectors/COLLECTORS.md).
- **20+ notification platforms**: Netdata's health watchdog sends warning and critical alarms to your [favorite
- platform](https://learn.netdata.cloud/docs/monitor/enable-notifications) to inform you of anomalies just seconds
+ platform](https://github.com/netdata/netdata/blob/master/docs/monitor/enable-notifications.md) to inform you of anomalies just seconds
after they affect your node.
- **30+ external time-series databases**: Export resampled metrics as they're collected to other [local- and
- Cloud-based databases](https://learn.netdata.cloud/docs/export/external-databases) for best-in-class
+ Cloud-based databases](https://github.com/netdata/netdata/blob/master/docs/export/external-databases.md) for best-in-class
interoperability.
> 💡 **Want to leverage the monitoring power of Netdata across entire infrastructure**? View metrics from
> any number of distributed nodes in a single interface and unlock even more
-> [features](https://learn.netdata.cloud/docs/overview/why-netdata) with [Netdata
+> [features](https://github.com/netdata/netdata/blob/master/docs/overview/why-netdata.md) with [Netdata
> Cloud](https://learn.netdata.cloud/docs/overview/what-is-netdata#netdata-cloud).
## Get Netdata
@@ -110,12 +115,30 @@ Netdata works with tons of applications, notifications platforms, and other time
<a href="https://hub.docker.com/r/netdata/netdata"><img src="https://registry.my-netdata.io/api/v1/badge.svg?chart=dockerhub.pulls_sum&divide=1000&precision=1&units=k&label=docker+hub+pulls&after=-86400&group=incremental-sum&label=docker%20hub%20pulls%20today&options=unaligned&v44" alt="Docker Hub pulls today"></a>
</p>
+### Infrastructure view
+
+Due to the distributed nature of the Netdata ecosystem, it is recommended to setup not only one Netdata Agent on your production system, but also an additional Netdata Agent acting as a [Parent](https://github.com/netdata/netdata/blob/master/streaming/README.md). A local Netdata Agent (child), without any database or alarms, collects metrics and sends them to another Netdata Agent (parent). The same parent can collect data for any number of child nodes and serves as a centralized health check engine for each child by triggering alerts on their behalf.
+
+![Netdata Cloud](https://user-images.githubusercontent.com/423236/205926887-43024984-6d38-46ad-96cb-d0c388117c6d.png)
+
+Get started by [signing in](https://app.netdata.cloud/?utm_source=website&utm_content=top_navigation_sign_up) to Netdata.cloud and follow the setup guide.
+
+Community version is free to use forever. No restriction on number of nodes, clusters or metrics. Unlimited alerts.
+
+#### Claiming existing Agents
+
+You can easily [connect (claim)](https://github.com/netdata/netdata/blob/master/claim/README.md) your existing Agents to the Cloud to unlock features for free and to find weaknesses before they turn into outages.
+
+### Single Node view
+
+In case you do not need the infrastructure view of you system you can install standalone Agent and enjoy the local dashboard.
+
To install Netdata from source on most Linux systems (physical, virtual, container, IoT, edge), run our [one-line
installation script](https://learn.netdata.cloud/docs/agent/packaging/installer/methods/packages). This script downloads
and builds all dependencies, including those required to connect to [Netdata Cloud](https://netdata.cloud/cloud) if you
choose, and enables [automatic nightly
updates](https://learn.netdata.cloud/docs/agent/packaging/installer#nightly-vs-stable-releases) and [anonymous
-statistics](https://learn.netdata.cloud/docs/agent/anonymous-statistics).
+statistics](https://github.com/netdata/netdata/blob/master/docs/anonymous-statistics.md).
<!-- candidate for reuse -->
```bash
wget -O /tmp/netdata-kickstart.sh https://my-netdata.io/kickstart.sh && sh /tmp/netdata-kickstart.sh
@@ -126,7 +149,7 @@ To view the Netdata dashboard, navigate to `http://localhost:19999`, or `http://
### Docker
You can also try out Netdata's capabilities in a [Docker
-container](https://learn.netdata.cloud/docs/agent/packaging/docker/):
+container](https://github.com/netdata/netdata/blob/master/packaging/docker/README.md):
```bash
docker run -d --name=netdata \
@@ -150,29 +173,21 @@ To view the Netdata dashboard, navigate to `http://localhost:19999`, or `http://
### Other operating systems
See our documentation for [additional operating
-systems](/packaging/installer/README.md#have-a-different-operating-system-or-want-to-try-another-method), including
-[Kubernetes](/packaging/installer/methods/kubernetes.md), [`.deb`/`.rpm`
-packages](/packaging/installer/methods/kickstart.md#native-packages), and more.
+systems](https://github.com/netdata/netdata/blob/master/packaging/installer/README.md#have-a-different-operating-system-or-want-to-try-another-method), including
+[Kubernetes](https://github.com/netdata/netdata/blob/master/packaging/installer/methods/kubernetes.md), [`.deb`/`.rpm`
+packages](https://github.com/netdata/netdata/blob/master/packaging/installer/methods/kickstart.md#native-packages), and more.
### Post-installation
-When you're finished with installation, check out our [single-node](/docs/quickstart/single-node.md) or
-[infrastructure](/docs/quickstart/infrastructure.md) monitoring quickstart guides based on your use case.
+When you're finished with installation, check out our [single-node](https://github.com/netdata/netdata/blob/master/docs/quickstart/single-node.md) or
+[infrastructure](https://github.com/netdata/netdata/blob/master/docs/quickstart/infrastructure.md) monitoring quickstart guides based on your use case.
-Or, skip straight to [configuring the Netdata Agent](/docs/configure/nodes.md).
+Or, skip straight to [configuring the Netdata Agent](https://github.com/netdata/netdata/blob/master/docs/configure/nodes.md).
Read through Netdata's [documentation](https://learn.netdata.cloud/docs), which is structured based on actions and
solutions, to enable features like health monitoring, alarm notifications, long-term metrics storage, exporting to
external databases, and more.
-### Netdata Cloud
-
-Netdata Cloud works with Netdata's free, open-source monitoring agent to help you monitor and troubleshoot every
-layer of your systems to find weaknesses before they turn into outages. [Using both tools](https://learn.netdata.cloud/docs/agent/claim)
-can help you turn data into insights immediately.
-
-[Get Netdata Cloud now!](https://app.netdata.cloud/)
-
## How it works
Netdata is a highly efficient, highly modular, metrics management engine. Its lockless design makes it ideal for
@@ -189,7 +204,7 @@ This is a high-level overview of Netdata features and architecture. Click on it
has links to our documentation.
[![An infographic of how Netdata
-works](https://user-images.githubusercontent.com/43294513/60951037-8ba5d180-a2f8-11e9-906e-e27356f168bc.png)](https://my-netdata.io/infographic.html)
+works](https://user-images.githubusercontent.com/43294513/212722097-fdd85dee-2fc8-47f5-90dc-d3149428cdfa.png)](https://my-netdata.io/infographic.html)
## Documentation
@@ -200,7 +215,7 @@ to collect metrics, troubleshoot via charts, export to external databases, and m
## Community
-Netdata is an inclusive open-source project and community. Please read our [Code of Conduct](https://learn.netdata.cloud/contribute/code-of-conduct).
+Netdata is an inclusive open-source project and community. Please read our [Code of Conduct](https://github.com/netdata/.github/blob/main/CODE_OF_CONDUCT.md).
Find most of the Netdata team in our [community forums](https://community.netdata.cloud). It's the best place to
ask questions, find resources, and engage with passionate professionals. The team is also available and active in our [Discord](https://discord.com/invite/mPZ6WZKKG2) too.
@@ -220,18 +235,18 @@ You can also find Netdata on:
Contributions are the lifeblood of open-source projects. While we continue to invest in and improve Netdata, we need help to democratize monitoring!
-- Read our [Contributing Guide](https://learn.netdata.cloud/contribute/handbook), which contains all the information you need to contribute to Netdata, such as improving our documentation, engaging in the community, and developing new features. We've made it as frictionless as possible, but if you need help, just ping us on our community forums!
+- Read our [Contributing Guide](https://github.com/netdata/.github/blob/main/CONTRIBUTING.md), which contains all the information you need to contribute to Netdata, such as improving our documentation, engaging in the community, and developing new features. We've made it as frictionless as possible, but if you need help, just ping us on our community forums!
- We have a whole category dedicated to contributing and extending Netdata on our [community forums](https://community.netdata.cloud/c/agent-development/9)
- Found a bug? Open a [GitHub issue](https://github.com/netdata/netdata/issues/new?assignees=&labels=bug%2Cneeds+triage&template=BUG_REPORT.yml&title=%5BBug%5D%3A+).
- View our [Security Policy](https://github.com/netdata/netdata/security/policy).
-Package maintainers should read the guide on [building Netdata from source](/packaging/installer/methods/source.md) for
+Package maintainers should read the guide on [building Netdata from source](https://github.com/netdata/netdata/blob/master/packaging/installer/methods/source.md) for
instructions on building each Netdata component from source and preparing a package.
## License
-The Netdata Agent is [GPLv3+](/LICENSE). Netdata re-distributes other open-source tools and libraries. Please check the
-[third party licenses](/REDISTRIBUTED.md).
+The Netdata Agent is [GPLv3+](https://github.com/netdata/netdata/blob/master/LICENSE). Netdata re-distributes other open-source tools and libraries. Please check the
+[third party licenses](https://github.com/netdata/netdata/blob/master/REDISTRIBUTED.md).
## Is it any good?
diff --git a/REDISTRIBUTED.md b/REDISTRIBUTED.md
index 6b82cd13..25d01d15 100644
--- a/REDISTRIBUTED.md
+++ b/REDISTRIBUTED.md
@@ -1,6 +1,10 @@
<!--
title: "Redistributed software"
custom_edit_url: https://github.com/netdata/netdata/edit/master/REDISTRIBUTED.md
+sidebar_label: "Redistributed Software"
+learn_status: "Published"
+learn_topic_type: "Concepts"
+learn_rel_path: "Developers"
-->
# Redistributed software
diff --git a/aclk/README.md b/aclk/README.md
index af0f5fdd..5b338dc2 100644
--- a/aclk/README.md
+++ b/aclk/README.md
@@ -1,8 +1,12 @@
<!--
title: "Agent-Cloud link (ACLK)"
description: "The Agent-Cloud link (ACLK) is the mechanism responsible for connecting a Netdata agent to Netdata Cloud."
-date: 2020-05-11
-custom_edit_url: https://github.com/netdata/netdata/edit/master/aclk/README.md
+date: "2020-05-11"
+custom_edit_url: "https://github.com/netdata/netdata/edit/master/aclk/README.md"
+sidebar_label: "Agent-Cloud link (ACLK)"
+learn_status: "Published"
+learn_topic_type: "Tasks"
+learn_rel_path: "Setup"
-->
# Agent-cloud link (ACLK)
@@ -25,8 +29,8 @@ this is not an option in your case always verify the current domain resolution (
:::
For a guide to connecting a node using the ACLK, plus additional troubleshooting and reference information, read our [get
-started with Cloud](https://learn.netdata.cloud/docs/cloud/get-started) guide or the full [connect to Cloud
-documentation](/claim/README.md).
+started with Cloud](https://github.com/netdata/netdata/blob/master/docs/cloud/get-started.mdx) guide or the full [connect to Cloud
+documentation](https://github.com/netdata/netdata/blob/master/claim/README.md).
## Data privacy
[Data privacy](https://netdata.cloud/privacy/) is very important to us. We firmly believe that your data belongs to
@@ -37,7 +41,7 @@ The data passes through our systems, but it isn't stored.
However, to be able to offer the stunning visualizations and advanced functionality of Netdata Cloud, it does store a limited number of _metadata_.
-Read more about [Data privacy in the Netdata Cloud](https://learn.netdata.cloud/docs/cloud/data-privacy) in the documentation.
+Read more about [Data privacy in the Netdata Cloud](https://github.com/netdata/netdata/blob/master/docs/cloud/data-privacy.mdx) in the documentation.
## Enable and configure the ACLK
@@ -53,7 +57,7 @@ configuration uses two settings:
```
If your Agent needs to use a proxy to access the internet, you must [set up a proxy for
-connecting to cloud](/claim/README.md#connect-through-a-proxy).
+connecting to cloud](https://github.com/netdata/netdata/blob/master/claim/README.md#connect-through-a-proxy).
You can configure following keys in the `netdata.conf` section `[cloud]`:
```
@@ -72,8 +76,8 @@ You have two options if you prefer to disable the ACLK and not use Netdata Cloud
### Disable at installation
You can pass the `--disable-cloud` parameter to the Agent installation when using a kickstart script
-([kickstart.sh](/packaging/installer/methods/kickstart.md), or a [manual installation from
-Git](/packaging/installer/methods/manual.md).
+([kickstart.sh](https://github.com/netdata/netdata/blob/master/packaging/installer/methods/kickstart.md), or a [manual installation from
+Git](https://github.com/netdata/netdata/blob/master/packaging/installer/methods/manual.md).
When you pass this parameter, the installer does not download or compile any extra libraries. Once running, the Agent
kills the thread responsible for the ACLK and connecting behavior, and behaves as though the ACLK, and thus Netdata Cloud,
@@ -127,12 +131,12 @@ Restart your Agent to disable the ACLK.
### Re-enable the ACLK
If you first disable the ACLK and any Cloud functionality and then decide you would like to use Cloud, you must either
-[reinstall Netdata](/packaging/installer/REINSTALL.md) with Cloud enabled or change the runtime setting in your
+[reinstall Netdata](https://github.com/netdata/netdata/blob/master/packaging/installer/REINSTALL.md) with Cloud enabled or change the runtime setting in your
`cloud.conf` file.
If you passed `--disable-cloud` to `netdata-installer.sh` during installation, you must
-[reinstall](/packaging/installer/REINSTALL.md) your Agent. Use the same method as before, but pass `--require-cloud` to
-the installer. When installation finishes you can [connect your node](/claim/README.md#how-to-connect-a-node).
+[reinstall](https://github.com/netdata/netdata/blob/master/packaging/installer/REINSTALL.md) your Agent. Use the same method as before, but pass `--require-cloud` to
+the installer. When installation finishes you can [connect your node](https://github.com/netdata/netdata/blob/master/claim/README.md#how-to-connect-a-node).
If you changed the runtime setting in your `var/lib/netdata/cloud.d/cloud.conf` file, edit the file again and change
`enabled` to `yes`:
@@ -142,6 +146,6 @@ If you changed the runtime setting in your `var/lib/netdata/cloud.d/cloud.conf`
enabled = yes
```
-Restart your Agent and [connect your node](/claim/README.md#how-to-connect-a-node).
+Restart your Agent and [connect your node](https://github.com/netdata/netdata/blob/master/claim/README.md#how-to-connect-a-node).
diff --git a/aclk/aclk.c b/aclk/aclk.c
index 3b035b84..e8089722 100644
--- a/aclk/aclk.c
+++ b/aclk/aclk.c
@@ -49,6 +49,8 @@ float last_backoff_value = 0;
time_t aclk_block_until = 0;
+int aclk_alert_reloaded = 0; //1 on health log exchange, and again on health_reload
+
#ifdef ENABLE_ACLK
mqtt_wss_client mqttwss_client;
@@ -61,6 +63,26 @@ struct aclk_shared_state aclk_shared_state = {
.mqtt_shutdown_msg_rcvd = 0
};
+#ifdef MQTT_WSS_DEBUG
+#include <openssl/ssl.h>
+#define DEFAULT_SSKEYLOGFILE_NAME "SSLKEYLOGFILE"
+const char *ssl_log_filename = NULL;
+FILE *ssl_log_file = NULL;
+static void aclk_ssl_keylog_cb(const SSL *ssl, const char *line)
+{
+ (void)ssl;
+ if (!ssl_log_file)
+ ssl_log_file = fopen(ssl_log_filename, "a");
+ if (!ssl_log_file) {
+ error("Couldn't open ssl_log file (%s) for append.", ssl_log_filename);
+ return;
+ }
+ fputs(line, ssl_log_file);
+ putc('\n', ssl_log_file);
+ fflush(ssl_log_file);
+}
+#endif
+
#if OPENSSL_VERSION_NUMBER >= OPENSSL_VERSION_300
OSSL_DECODER_CTX *aclk_dctx = NULL;
EVP_PKEY *aclk_private_key = NULL;
@@ -137,7 +159,7 @@ static int wait_till_cloud_enabled()
info("Waiting for Cloud to be enabled");
while (!netdata_cloud_setting) {
sleep_usec(USEC_PER_SEC * 1);
- if (netdata_exit)
+ if (!service_running(SERVICE_ACLK))
return 1;
}
return 0;
@@ -156,7 +178,7 @@ static int wait_till_agent_claimed(void)
char *agent_id = get_agent_claimid();
while (likely(!agent_id)) {
sleep_usec(USEC_PER_SEC * 1);
- if (netdata_exit)
+ if (!service_running(SERVICE_ACLK))
return 1;
agent_id = get_agent_claimid();
}
@@ -176,7 +198,7 @@ static int wait_till_agent_claimed(void)
static int wait_till_agent_claim_ready()
{
url_t url;
- while (!netdata_exit) {
+ while (service_running(SERVICE_ACLK)) {
if (wait_till_agent_claimed())
return 1;
@@ -288,7 +310,7 @@ static void puback_callback(uint16_t packet_id)
static int read_query_thread_count()
{
- int threads = MIN(processors/2, 6);
+ int threads = MIN(get_netdata_cpus()/2, 6);
threads = MAX(threads, 2);
threads = config_get_number(CONFIG_SECTION_CLOUD, "query thread count", threads);
if(threads < 1) {
@@ -310,7 +332,7 @@ void aclk_graceful_disconnect(mqtt_wss_client client);
static int handle_connection(mqtt_wss_client client)
{
time_t last_periodic_query_wakeup = now_monotonic_sec();
- while (!netdata_exit) {
+ while (service_running(SERVICE_ACLK)) {
// timeout 1000 to check at least once a second
// for netdata_exit
if (mqtt_wss_service(client, 1000) < 0){
@@ -365,6 +387,10 @@ static inline void mqtt_connected_actions(mqtt_wss_client client)
aclk_rcvd_cloud_msgs = 0;
aclk_connection_counter++;
+ aclk_topic_cache_iter_t iter = ACLK_TOPIC_CACHE_ITER_T_INITIALIZER;
+ while ((topic = (char*)aclk_topic_cache_iterate(&iter)) != NULL)
+ mqtt_wss_set_topic_alias(client, topic);
+
aclk_send_agent_connection_update(client, 1);
}
@@ -435,11 +461,11 @@ static int aclk_block_till_recon_allowed() {
next_connection_attempt = now_realtime_sec() + (recon_delay / MSEC_PER_SEC);
last_backoff_value = (float)recon_delay / MSEC_PER_SEC;
- info("Wait before attempting to reconnect in %.3f seconds\n", recon_delay / (float)MSEC_PER_SEC);
+ info("Wait before attempting to reconnect in %.3f seconds", recon_delay / (float)MSEC_PER_SEC);
// we want to wake up from time to time to check netdata_exit
while (recon_delay)
{
- if (netdata_exit)
+ if (!service_running(SERVICE_ACLK))
return 1;
if (recon_delay > NETDATA_EXIT_POLL_MS) {
sleep_usec(NETDATA_EXIT_POLL_MS * USEC_PER_MS);
@@ -449,7 +475,7 @@ static int aclk_block_till_recon_allowed() {
sleep_usec(recon_delay * USEC_PER_MS);
recon_delay = 0;
}
- return netdata_exit;
+ return !service_running(SERVICE_ACLK);
}
#ifndef ACLK_DISABLE_CHALLENGE
@@ -492,7 +518,7 @@ static int aclk_attempt_to_connect(mqtt_wss_client client)
url_t mqtt_url;
#endif
- while (!netdata_exit) {
+ while (service_running(SERVICE_ACLK)) {
char *cloud_base_url = appconfig_get(&cloud_config, CONFIG_SECTION_GLOBAL, "cloud base url", NULL);
if (cloud_base_url == NULL) {
error_report("Do not move the cloud base url out of post_conf_load!!");
@@ -512,7 +538,7 @@ static int aclk_attempt_to_connect(mqtt_wss_client client)
}
struct mqtt_wss_proxy proxy_conf = { .host = NULL, .port = 0, .username = NULL, .password = NULL, .type = MQTT_WSS_DIRECT };
- aclk_set_proxy((char**)&proxy_conf.host, &proxy_conf.port, &proxy_conf.type);
+ aclk_set_proxy((char**)&proxy_conf.host, &proxy_conf.port, (char**)&proxy_conf.username, (char**)&proxy_conf.password, &proxy_conf.type);
struct mqtt_connect_params mqtt_conn_params = {
.clientid = "anon",
@@ -540,7 +566,7 @@ static int aclk_attempt_to_connect(mqtt_wss_client client)
continue;
}
- if (netdata_exit)
+ if (!service_running(SERVICE_ACLK))
return 1;
if (aclk_env->encoding != ACLK_ENC_PROTO) {
@@ -610,7 +636,10 @@ static int aclk_attempt_to_connect(mqtt_wss_client client)
freez((char*)mqtt_conn_params.username);
#endif
- freez((char *)mqtt_conn_params.will_msg);
+ freez((char*)mqtt_conn_params.will_msg);
+ freez((char*)proxy_conf.host);
+ freez((char*)proxy_conf.username);
+ freez((char*)proxy_conf.password);
if (!ret) {
last_conn_time_mqtt = now_realtime_sec();
@@ -672,11 +701,23 @@ void *aclk_main(void *ptr)
if (wait_till_agent_claim_ready())
goto exit;
- if (!(mqttwss_client = mqtt_wss_new("mqtt_wss", aclk_mqtt_wss_log_cb, msg_callback, puback_callback, 1))) {
+ if (!(mqttwss_client = mqtt_wss_new("mqtt_wss", aclk_mqtt_wss_log_cb, msg_callback, puback_callback))) {
error("Couldn't initialize MQTT_WSS network library");
goto exit;
}
+#ifdef MQTT_WSS_DEBUG
+ size_t default_ssl_log_filename_size = strlen(netdata_configured_log_dir) + strlen(DEFAULT_SSKEYLOGFILE_NAME) + 2;
+ char *default_ssl_log_filename = mallocz(default_ssl_log_filename_size);
+ snprintfz(default_ssl_log_filename, default_ssl_log_filename_size, "%s/%s", netdata_configured_log_dir, DEFAULT_SSKEYLOGFILE_NAME);
+ ssl_log_filename = config_get(CONFIG_SECTION_CLOUD, "aclk ssl keylog file", default_ssl_log_filename);
+ freez(default_ssl_log_filename);
+ if (ssl_log_filename) {
+ error_report("SSLKEYLOGFILE active (path:\"%s\")!", ssl_log_filename);
+ mqtt_wss_set_SSL_CTX_keylog_cb(mqttwss_client, aclk_ssl_keylog_cb);
+ }
+#endif
+
// Enable MQTT buffer growth if necessary
// e.g. old cloud architecture clients with huge nodes
// that send JSON payloads of 10 MB as single messages
@@ -690,8 +731,8 @@ void *aclk_main(void *ptr)
stats_thread->client = mqttwss_client;
aclk_stats_thread_prepare(query_threads.count, proto_hdl_cnt);
netdata_thread_create(
- stats_thread->thread, ACLK_STATS_THREAD_NAME, NETDATA_THREAD_OPTION_JOINABLE, aclk_stats_main_thread,
- stats_thread);
+ stats_thread->thread, "ACLK_STATS", NETDATA_THREAD_OPTION_JOINABLE, aclk_stats_main_thread,
+ stats_thread);
}
// Keep reconnecting and talking until our time has come
@@ -709,10 +750,15 @@ void *aclk_main(void *ptr)
aclk_connected = 0;
log_access("ACLK DISCONNECTED");
}
- } while (!netdata_exit);
+ } while (service_running(SERVICE_ACLK));
aclk_graceful_disconnect(mqttwss_client);
+#ifdef MQTT_WSS_DEBUG
+ if (ssl_log_file)
+ fclose(ssl_log_file);
+#endif
+
exit_full:
// Tear Down
QUERY_THREAD_WAKEUP_ALL;
@@ -739,35 +785,40 @@ exit:
void aclk_host_state_update(RRDHOST *host, int cmd)
{
uuid_t node_id;
- int ret;
+ int ret = 0;
if (!aclk_connected)
return;
- ret = get_node_id(&host->host_uuid, &node_id);
- if (ret > 0) {
- // this means we were not able to check if node_id already present
- error("Unable to check for node_id. Ignoring the host state update.");
- return;
+ if (host->node_id && !uuid_is_null(*host->node_id)) {
+ uuid_copy(node_id, *host->node_id);
}
- if (ret < 0) {
- // node_id not found
- aclk_query_t create_query;
- create_query = aclk_query_new(REGISTER_NODE);
- rrdhost_aclk_state_lock(localhost);
- node_instance_creation_t node_instance_creation = {
- .claim_id = localhost->aclk_state.claimed_id,
- .hops = host->system_info->hops,
- .hostname = rrdhost_hostname(host),
- .machine_guid = host->machine_guid
- };
- create_query->data.bin_payload.payload = generate_node_instance_creation(&create_query->data.bin_payload.size, &node_instance_creation);
- rrdhost_aclk_state_unlock(localhost);
- create_query->data.bin_payload.topic = ACLK_TOPICID_CREATE_NODE;
- create_query->data.bin_payload.msg_name = "CreateNodeInstance";
- info("Registering host=%s, hops=%u",host->machine_guid, host->system_info->hops);
- aclk_queue_query(create_query);
- return;
+ else {
+ ret = get_node_id(&host->host_uuid, &node_id);
+ if (ret > 0) {
+ // this means we were not able to check if node_id already present
+ error("Unable to check for node_id. Ignoring the host state update.");
+ return;
+ }
+ if (ret < 0) {
+ // node_id not found
+ aclk_query_t create_query;
+ create_query = aclk_query_new(REGISTER_NODE);
+ rrdhost_aclk_state_lock(localhost);
+ node_instance_creation_t node_instance_creation = {
+ .claim_id = localhost->aclk_state.claimed_id,
+ .hops = host->system_info->hops,
+ .hostname = rrdhost_hostname(host),
+ .machine_guid = host->machine_guid};
+ create_query->data.bin_payload.payload =
+ generate_node_instance_creation(&create_query->data.bin_payload.size, &node_instance_creation);
+ rrdhost_aclk_state_unlock(localhost);
+ create_query->data.bin_payload.topic = ACLK_TOPICID_CREATE_NODE;
+ create_query->data.bin_payload.msg_name = "CreateNodeInstance";
+ info("Registering host=%s, hops=%u", host->machine_guid, host->system_info->hops);
+ aclk_queue_query(create_query);
+ return;
+ }
}
aclk_query_t query = aclk_query_new(NODE_STATE_UPDATE);
@@ -896,7 +947,7 @@ char *aclk_state(void)
#ifndef ENABLE_ACLK
return strdupz("ACLK Available: No");
#else
- BUFFER *wb = buffer_create(1024);
+ BUFFER *wb = buffer_create(1024, &netdata_buffers_statistics.buffers_aclk);
struct tm *tmptr, tmbuf;
char *ret;
diff --git a/aclk/aclk.h b/aclk/aclk.h
index 6aed548b..56b24add 100644
--- a/aclk/aclk.h
+++ b/aclk/aclk.h
@@ -26,6 +26,8 @@ extern time_t aclk_block_until;
extern int disconnect_req;
+extern int aclk_alert_reloaded;
+
#ifdef ENABLE_ACLK
void *aclk_main(void *ptr);
diff --git a/aclk/aclk_capas.c b/aclk/aclk_capas.c
index df9d18f6..290e7d8f 100644
--- a/aclk/aclk_capas.c
+++ b/aclk/aclk_capas.c
@@ -36,7 +36,7 @@ struct capability *aclk_get_node_instance_capas(RRDHOST *host)
{ .name = "funcs", .version = 0, .enabled = 0 },
{ .name = NULL, .version = 0, .enabled = 0 }
};
- if (host->receiver && stream_has_capability(host->receiver, STREAM_CAP_FUNCTIONS)) {
+ if (host == localhost || (host->receiver && stream_has_capability(host->receiver, STREAM_CAP_FUNCTIONS))) {
ni_caps[4].version = 1;
ni_caps[4].enabled = 1;
}
diff --git a/aclk/aclk_otp.c b/aclk/aclk_otp.c
index 2bdbb70f..391313ff 100644
--- a/aclk/aclk_otp.c
+++ b/aclk/aclk_otp.c
@@ -14,15 +14,19 @@ static int aclk_https_request(https_req_t *request, https_req_response_t *respon
// wrapper for ACLK only which loads ACLK specific proxy settings
// then only calls https_request
struct mqtt_wss_proxy proxy_conf = { .host = NULL, .port = 0, .username = NULL, .password = NULL, .type = MQTT_WSS_DIRECT };
- aclk_set_proxy((char**)&proxy_conf.host, &proxy_conf.port, &proxy_conf.type);
+ aclk_set_proxy((char**)&proxy_conf.host, &proxy_conf.port, (char**)&proxy_conf.username, (char**)&proxy_conf.password, &proxy_conf.type);
if (proxy_conf.type == MQTT_WSS_PROXY_HTTP) {
request->proxy_host = (char*)proxy_conf.host; // TODO make it const as well
request->proxy_port = proxy_conf.port;
+ request->proxy_username = proxy_conf.username;
+ request->proxy_password = proxy_conf.password;
}
rc = https_request(request, response);
freez((char*)proxy_conf.host);
+ freez((char*)proxy_conf.username);
+ freez((char*)proxy_conf.password);
return rc;
}
@@ -303,25 +307,6 @@ inline static int base64_decode_helper(unsigned char *out, int *outl, const unsi
return 0;
}
-inline static int base64_encode_helper(unsigned char *out, int *outl, const unsigned char *in, int in_len)
-{
- int len;
- unsigned char *str = out;
- EVP_ENCODE_CTX *ctx = EVP_ENCODE_CTX_new();
- EVP_EncodeInit(ctx);
- EVP_EncodeUpdate(ctx, str, outl, in, in_len);
- str += *outl;
- EVP_EncodeFinal(ctx, str, &len);
- *outl += len;
- // if we ever expect longer output than what OpenSSL would pack into single line
- // we would have to skip the endlines, until then we can just cut the string short
- str = (unsigned char*)strchr((char*)out, '\n');
- if (str)
- *str = 0;
- EVP_ENCODE_CTX_free(ctx);
- return 0;
-}
-
#define OTP_URL_PREFIX "/api/v1/auth/node/"
int aclk_get_otp_challenge(url_t *target, const char *agent_id, unsigned char **challenge, int *challenge_bytes)
{
@@ -329,7 +314,7 @@ int aclk_get_otp_challenge(url_t *target, const char *agent_id, unsigned char **
https_req_t req = HTTPS_REQ_T_INITIALIZER;
https_req_response_t resp = HTTPS_REQ_RESPONSE_T_INITIALIZER;
- BUFFER *url = buffer_create(strlen(OTP_URL_PREFIX) + UUID_STR_LEN + 20);
+ BUFFER *url = buffer_create(strlen(OTP_URL_PREFIX) + UUID_STR_LEN + 20, &netdata_buffers_statistics.buffers_aclk);
req.host = target->host;
req.port = target->port;
@@ -409,8 +394,8 @@ int aclk_send_otp_response(const char *agent_id, const unsigned char *response,
base64_encode_helper(base64, &len, response, response_bytes);
- BUFFER *url = buffer_create(strlen(OTP_URL_PREFIX) + UUID_STR_LEN + 20);
- BUFFER *resp_json = buffer_create(strlen(OTP_URL_PREFIX) + UUID_STR_LEN + 20);
+ BUFFER *url = buffer_create(strlen(OTP_URL_PREFIX) + UUID_STR_LEN + 20, &netdata_buffers_statistics.buffers_aclk);
+ BUFFER *resp_json = buffer_create(strlen(OTP_URL_PREFIX) + UUID_STR_LEN + 20, &netdata_buffers_statistics.buffers_aclk);
buffer_sprintf(url, "%s/node/%s/password", target->path, agent_id);
buffer_sprintf(resp_json, "{\"response\":\"%s\"}", base64);
@@ -829,7 +814,7 @@ exit:
}
int aclk_get_env(aclk_env_t *env, const char* aclk_hostname, int aclk_port) {
- BUFFER *buf = buffer_create(1024);
+ BUFFER *buf = buffer_create(1024, &netdata_buffers_statistics.buffers_aclk);
https_req_t req = HTTPS_REQ_T_INITIALIZER;
https_req_response_t resp = HTTPS_REQ_RESPONSE_T_INITIALIZER;
diff --git a/aclk/aclk_query.c b/aclk/aclk_query.c
index 5301c281..9eced081 100644
--- a/aclk/aclk_query.c
+++ b/aclk/aclk_query.c
@@ -4,8 +4,6 @@
#include "aclk_stats.h"
#include "aclk_tx_msgs.h"
-#define ACLK_QUERY_THREAD_NAME "ACLK_Query"
-
#define WEB_HDR_ACCEPT_ENC "Accept-Encoding:"
pthread_cond_t query_cond_wait = PTHREAD_COND_INITIALIZER;
@@ -64,19 +62,19 @@ static int http_api_v2(struct aclk_query_thread *query_thr, aclk_query_t query)
int retval = 0;
usec_t t;
BUFFER *local_buffer = NULL;
- BUFFER *log_buffer = buffer_create(NETDATA_WEB_REQUEST_URL_SIZE);
+ BUFFER *log_buffer = buffer_create(NETDATA_WEB_REQUEST_URL_SIZE, &netdata_buffers_statistics.buffers_aclk);
RRDHOST *query_host = localhost;
#ifdef NETDATA_WITH_ZLIB
int z_ret;
- BUFFER *z_buffer = buffer_create(NETDATA_WEB_RESPONSE_INITIAL_SIZE);
+ BUFFER *z_buffer = buffer_create(NETDATA_WEB_RESPONSE_INITIAL_SIZE, &netdata_buffers_statistics.buffers_aclk);
char *start, *end;
#endif
struct web_client *w = (struct web_client *)callocz(1, sizeof(struct web_client));
- w->response.data = buffer_create(NETDATA_WEB_RESPONSE_INITIAL_SIZE);
- w->response.header = buffer_create(NETDATA_WEB_RESPONSE_HEADER_SIZE);
- w->response.header_output = buffer_create(NETDATA_WEB_RESPONSE_HEADER_SIZE);
+ w->response.data = buffer_create(NETDATA_WEB_RESPONSE_INITIAL_SIZE, &netdata_buffers_statistics.buffers_aclk);
+ w->response.header = buffer_create(NETDATA_WEB_RESPONSE_HEADER_SIZE, &netdata_buffers_statistics.buffers_aclk);
+ w->response.header_output = buffer_create(NETDATA_WEB_RESPONSE_HEADER_SIZE, &netdata_buffers_statistics.buffers_aclk);
strcpy(w->origin, "*"); // Simulate web_client_create_on_fd()
w->cookie1[0] = 0; // Simulate web_client_create_on_fd()
w->cookie2[0] = 0; // Simulate web_client_create_on_fd()
@@ -193,7 +191,7 @@ static int http_api_v2(struct aclk_query_thread *query_thr, aclk_query_t query)
w->response.data->date = w->tv_ready.tv_sec;
web_client_build_http_header(w);
- local_buffer = buffer_create(NETDATA_WEB_RESPONSE_INITIAL_SIZE);
+ local_buffer = buffer_create(NETDATA_WEB_RESPONSE_INITIAL_SIZE, &netdata_buffers_statistics.buffers_aclk);
local_buffer->contenttype = CT_APPLICATION_JSON;
buffer_strcat(local_buffer, w->response.header_output->buffer);
@@ -327,6 +325,11 @@ static void worker_aclk_register(void) {
}
}
+static void aclk_query_request_cancel(void *data)
+{
+ pthread_cond_broadcast((pthread_cond_t *) data);
+}
+
/**
* Main query processing thread
*/
@@ -336,7 +339,9 @@ void *aclk_query_main_thread(void *ptr)
struct aclk_query_thread *query_thr = ptr;
- while (!netdata_exit) {
+ service_register(SERVICE_THREAD_TYPE_NETDATA, aclk_query_request_cancel, NULL, &query_cond_wait, false);
+
+ while (service_running(SERVICE_ACLK | ABILITY_DATA_QUERIES)) {
aclk_query_process_msgs(query_thr);
worker_is_idle();
@@ -359,14 +364,13 @@ void aclk_query_threads_start(struct aclk_query_threads *query_threads, mqtt_wss
query_threads->thread_list = callocz(query_threads->count, sizeof(struct aclk_query_thread));
for (int i = 0; i < query_threads->count; i++) {
query_threads->thread_list[i].idx = i; //thread needs to know its index for statistics
+ query_threads->thread_list[i].client = client;
- if(unlikely(snprintfz(thread_name, TASK_LEN_MAX, "%s_%d", ACLK_QUERY_THREAD_NAME, i) < 0))
+ if(unlikely(snprintfz(thread_name, TASK_LEN_MAX, "ACLK_QRY[%d]", i) < 0))
error("snprintf encoding error");
netdata_thread_create(
&query_threads->thread_list[i].thread, thread_name, NETDATA_THREAD_OPTION_JOINABLE, aclk_query_main_thread,
&query_threads->thread_list[i]);
-
- query_threads->thread_list[i].client = client;
}
}
diff --git a/aclk/aclk_query_queue.c b/aclk/aclk_query_queue.c
index 9a450571..e7cad5de 100644
--- a/aclk/aclk_query_queue.c
+++ b/aclk/aclk_query_queue.c
@@ -26,7 +26,7 @@ static inline int _aclk_queue_query(aclk_query_t query)
ACLK_QUEUE_LOCK;
if (aclk_query_queue.block_push) {
ACLK_QUEUE_UNLOCK;
- if(!netdata_exit)
+ if(service_running(SERVICE_ACLK | ABILITY_DATA_QUERIES))
error("Query Queue is blocked from accepting new requests. This is normally the case when ACLK prepares to shutdown.");
aclk_query_free(query);
return 1;
@@ -66,7 +66,7 @@ aclk_query_t aclk_queue_pop(void)
ACLK_QUEUE_LOCK;
if (aclk_query_queue.block_push) {
ACLK_QUEUE_UNLOCK;
- if(!netdata_exit)
+ if(service_running(SERVICE_ACLK | ABILITY_DATA_QUERIES))
error("POP Query Queue is blocked from accepting new requests. This is normally the case when ACLK prepares to shutdown.");
return NULL;
}
diff --git a/aclk/aclk_rx_msgs.c b/aclk/aclk_rx_msgs.c
index 83bc5508..104fbcb3 100644
--- a/aclk/aclk_rx_msgs.c
+++ b/aclk/aclk_rx_msgs.c
@@ -283,9 +283,7 @@ int create_node_instance_result(const char *msg, size_t msg_len)
node_state_update.live = 1;
node_state_update.hops = 0;
} else {
- netdata_mutex_lock(&host->receiver_lock);
- node_state_update.live = (host->receiver != NULL);
- netdata_mutex_unlock(&host->receiver_lock);
+ node_state_update.live = (!rrdhost_flag_check(host, RRDHOST_FLAG_ORPHAN));
node_state_update.hops = host->system_info->hops;
}
}
diff --git a/aclk/aclk_stats.c b/aclk/aclk_stats.c
index 215313ff..2b4d5e48 100644
--- a/aclk/aclk_stats.c
+++ b/aclk/aclk_stats.c
@@ -1,5 +1,7 @@
// SPDX-License-Identifier: GPL-3.0-or-later
+#define MQTT_WSS_CPUSTATS
+
#include "aclk_stats.h"
#include "aclk_query.h"
@@ -143,7 +145,9 @@ static char *cloud_req_http_type_names[ACLK_STATS_CLOUD_HTTP_REQ_TYPE_CNT] = {
"alarms",
"alarm_log",
"chart",
- "charts"
+ "charts",
+ "function",
+ "functions"
// if you change then update `ACLK_STATS_CLOUD_HTTP_REQ_TYPE_CNT`.
};
@@ -257,6 +261,23 @@ static void aclk_stats_mqtt_wss(struct mqtt_wss_stats *stats)
static uint64_t sent = 0;
static uint64_t recvd = 0;
+ static RRDSET *st_txbuf_perc = NULL;
+ static RRDDIM *rd_txbuf_perc = NULL;
+
+ static RRDSET *st_txbuf = NULL;
+ static RRDDIM *rd_tx_buffer_usable = NULL;
+ static RRDDIM *rd_tx_buffer_reclaimable = NULL;
+ static RRDDIM *rd_tx_buffer_used = NULL;
+ static RRDDIM *rd_tx_buffer_free = NULL;
+ static RRDDIM *rd_tx_buffer_size = NULL;
+
+ static RRDSET *st_timing = NULL;
+ static RRDDIM *rd_keepalive = NULL;
+ static RRDDIM *rd_read_socket = NULL;
+ static RRDDIM *rd_write_socket = NULL;
+ static RRDDIM *rd_process_websocket = NULL;
+ static RRDDIM *rd_process_mqtt = NULL;
+
sent += stats->bytes_tx;
recvd += stats->bytes_rx;
@@ -269,10 +290,61 @@ static void aclk_stats_mqtt_wss(struct mqtt_wss_stats *stats)
rd_recvd = rrddim_add(st, "received", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
}
+ if (unlikely(!st_txbuf_perc)) {
+ st_txbuf_perc = rrdset_create_localhost(
+ "netdata", "aclk_mqtt_tx_perc", NULL, "aclk", NULL, "Actively used percentage of MQTT Tx Buffer,", "%",
+ "netdata", "stats", 200012, localhost->rrd_update_every, RRDSET_TYPE_LINE);
+
+ rd_txbuf_perc = rrddim_add(st_txbuf_perc, "used", NULL, 1, 100, RRD_ALGORITHM_ABSOLUTE);
+ }
+
+ if (unlikely(!st_txbuf)) {
+ st_txbuf = rrdset_create_localhost(
+ "netdata", "aclk_mqtt_tx_queue", NULL, "aclk", NULL, "State of transmit MQTT queue.", "B",
+ "netdata", "stats", 200013, localhost->rrd_update_every, RRDSET_TYPE_LINE);
+
+ rd_tx_buffer_usable = rrddim_add(st_txbuf, "usable", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
+ rd_tx_buffer_reclaimable = rrddim_add(st_txbuf, "reclaimable", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
+ rd_tx_buffer_used = rrddim_add(st_txbuf, "used", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
+ rd_tx_buffer_free = rrddim_add(st_txbuf, "free", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
+ rd_tx_buffer_size = rrddim_add(st_txbuf, "size", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
+ }
+
+ if (unlikely(!st_timing)) {
+ st_timing = rrdset_create_localhost(
+ "netdata", "aclk_mqtt_wss_time", NULL, "aclk", NULL, "Time spent handling MQTT, WSS, SSL and network communication.", "us",
+ "netdata", "stats", 200014, localhost->rrd_update_every, RRDSET_TYPE_STACKED);
+
+ rd_keepalive = rrddim_add(st_timing, "keep-alive", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
+ rd_read_socket = rrddim_add(st_timing, "socket_read_ssl", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
+ rd_write_socket = rrddim_add(st_timing, "socket_write_ssl", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
+ rd_process_websocket = rrddim_add(st_timing, "process_websocket", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
+ rd_process_mqtt = rrddim_add(st_timing, "process_mqtt", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
+ }
+
rrddim_set_by_pointer(st, rd_sent, sent);
rrddim_set_by_pointer(st, rd_recvd, recvd);
+ float usage = ((float)stats->mqtt.tx_buffer_free + stats->mqtt.tx_buffer_reclaimable) / stats->mqtt.tx_buffer_size;
+ usage = (1 - usage) * 10000;
+ rrddim_set_by_pointer(st_txbuf_perc, rd_txbuf_perc, usage);
+
+ rrddim_set_by_pointer(st_txbuf, rd_tx_buffer_usable, stats->mqtt.tx_buffer_reclaimable + stats->mqtt.tx_buffer_free);
+ rrddim_set_by_pointer(st_txbuf, rd_tx_buffer_reclaimable, stats->mqtt.tx_buffer_reclaimable);
+ rrddim_set_by_pointer(st_txbuf, rd_tx_buffer_used, stats->mqtt.tx_buffer_used);
+ rrddim_set_by_pointer(st_txbuf, rd_tx_buffer_free, stats->mqtt.tx_buffer_free);
+ rrddim_set_by_pointer(st_txbuf, rd_tx_buffer_size, stats->mqtt.tx_buffer_size);
+
+ rrddim_set_by_pointer(st_timing, rd_keepalive, stats->time_keepalive);
+ rrddim_set_by_pointer(st_timing, rd_read_socket, stats->time_read_socket);
+ rrddim_set_by_pointer(st_timing, rd_write_socket, stats->time_write_socket);
+ rrddim_set_by_pointer(st_timing, rd_process_websocket, stats->time_process_websocket);
+ rrddim_set_by_pointer(st_timing, rd_process_mqtt, stats->time_process_mqtt);
+
rrdset_done(st);
+ rrdset_done(st_txbuf_perc);
+ rrdset_done(st_txbuf);
+ rrdset_done(st_timing);
}
void aclk_stats_thread_prepare(int query_thread_count, unsigned int proto_hdl_cnt)
@@ -312,13 +384,13 @@ void *aclk_stats_main_thread(void *ptr)
struct aclk_metrics_per_sample per_sample;
struct aclk_metrics permanent;
- while (!netdata_exit) {
+ while (service_running(SERVICE_ACLK | SERVICE_COLLECTORS)) {
netdata_thread_testcancel();
// ------------------------------------------------------------------------
// Wait for the next iteration point.
heartbeat_next(&hb, step_ut);
- if (netdata_exit) break;
+ if (!service_running(SERVICE_ACLK | SERVICE_COLLECTORS)) break;
ACLK_STATS_LOCK;
// to not hold lock longer than necessary, especially not to hold it
diff --git a/aclk/aclk_stats.h b/aclk/aclk_stats.h
index bec9ac24..002ebcfa 100644
--- a/aclk/aclk_stats.h
+++ b/aclk/aclk_stats.h
@@ -8,15 +8,13 @@
#include "aclk_query_queue.h"
#include "mqtt_wss_client.h"
-#define ACLK_STATS_THREAD_NAME "ACLK_Stats"
-
extern netdata_mutex_t aclk_stats_mutex;
#define ACLK_STATS_LOCK netdata_mutex_lock(&aclk_stats_mutex)
#define ACLK_STATS_UNLOCK netdata_mutex_unlock(&aclk_stats_mutex)
// if you change update `cloud_req_http_type_names`.
-#define ACLK_STATS_CLOUD_HTTP_REQ_TYPE_CNT 7
+#define ACLK_STATS_CLOUD_HTTP_REQ_TYPE_CNT 9
int aclk_cloud_req_http_type_to_idx(const char *name);
diff --git a/aclk/aclk_util.c b/aclk/aclk_util.c
index 01eaedc8..ebf428ff 100644
--- a/aclk/aclk_util.c
+++ b/aclk/aclk_util.c
@@ -308,6 +308,24 @@ const char *aclk_get_topic(enum aclk_topics topic)
}
/*
+ * Allows iterating all topics in topic cache without
+ * having to resort to callbacks.
+ */
+
+const char *aclk_topic_cache_iterate(aclk_topic_cache_iter_t *iter)
+{
+ if (!aclk_topic_cache) {
+ error("Topic cache not initialized when %s was called.", __FUNCTION__);
+ return NULL;
+ }
+
+ if (*iter >= aclk_topic_cache_items)
+ return NULL;
+
+ return aclk_topic_cache[(*iter)++]->topic;
+}
+
+/*
* TBEB with randomness
*
* @param reset 1 - to reset the delay,
@@ -346,43 +364,117 @@ unsigned long int aclk_tbeb_delay(int reset, int base, unsigned long int min, un
return delay;
}
+static inline int aclk_parse_pair(const char *src, const char c, char **a, char **b)
+{
+ const char *ptr = strchr(src, c);
+ if (ptr == NULL)
+ return 1;
+
+// allow empty string
+/* if (!*(ptr+1))
+ return 1;*/
+
+ *a = callocz(1, ptr - src + 1);
+ memcpy (*a, src, ptr - src);
+
+ *b = strdupz(ptr+1);
+
+ return 0;
+}
#define HTTP_PROXY_PREFIX "http://"
-void aclk_set_proxy(char **ohost, int *port, enum mqtt_wss_proxy_type *type)
+void aclk_set_proxy(char **ohost, int *port, char **uname, char **pwd, enum mqtt_wss_proxy_type *type)
{
ACLK_PROXY_TYPE pt;
const char *ptr = aclk_get_proxy(&pt);
char *tmp;
- char *host;
+
if (pt != PROXY_TYPE_HTTP)
return;
+ *uname = NULL;
+ *pwd = NULL;
*port = 0;
+ char *proxy = strdupz(ptr);
+ ptr = proxy;
+
if (!strncmp(ptr, HTTP_PROXY_PREFIX, strlen(HTTP_PROXY_PREFIX)))
ptr += strlen(HTTP_PROXY_PREFIX);
- if ((tmp = strchr(ptr, '@')))
- ptr = tmp;
+ if ((tmp = strchr(ptr, '@'))) {
+ *tmp = 0;
+ if(aclk_parse_pair(ptr, ':', uname, pwd)) {
+ error_report("Failed to get username and password for proxy. Will attempt connection without authentication");
+ }
+ ptr = tmp+1;
+ }
- if ((tmp = strchr(ptr, '/'))) {
- host = mallocz((tmp - ptr) + 1);
- memcpy(host, ptr, (tmp - ptr));
- host[tmp - ptr] = 0;
- } else
- host = strdupz(ptr);
+ if (!*ptr) {
+ freez(proxy);
+ freez(*uname);
+ freez(*pwd);
+ return;
+ }
- if ((tmp = strchr(host, ':'))) {
+ if ((tmp = strchr(ptr, ':'))) {
*tmp = 0;
tmp++;
- *port = atoi(tmp);
+ if(*tmp)
+ *port = atoi(tmp);
}
+ *ohost = strdupz(ptr);
if (*port <= 0 || *port > 65535)
*port = 8080;
- *ohost = host;
-
if (type)
*type = MQTT_WSS_PROXY_HTTP;
+ else {
+ freez(*uname);
+ freez(*pwd);
+ }
+
+ freez(proxy);
+}
+
+#if defined(OPENSSL_VERSION_NUMBER) && OPENSSL_VERSION_NUMBER < OPENSSL_VERSION_110
+static EVP_ENCODE_CTX *EVP_ENCODE_CTX_new(void)
+{
+ EVP_ENCODE_CTX *ctx = OPENSSL_malloc(sizeof(*ctx));
+
+ if (ctx != NULL) {
+ memset(ctx, 0, sizeof(*ctx));
+ }
+ return ctx;
+}
+static void EVP_ENCODE_CTX_free(EVP_ENCODE_CTX *ctx)
+{
+ OPENSSL_free(ctx);
+ return;
+}
+#endif
+
+int base64_encode_helper(unsigned char *out, int *outl, const unsigned char *in, int in_len)
+{
+ int len;
+ unsigned char *str = out;
+ EVP_ENCODE_CTX *ctx = EVP_ENCODE_CTX_new();
+ EVP_EncodeInit(ctx);
+ EVP_EncodeUpdate(ctx, str, outl, in, in_len);
+ str += *outl;
+ EVP_EncodeFinal(ctx, str, &len);
+ *outl += len;
+
+ str = out;
+ while(*str) {
+ if (*str != 0x0D && *str != 0x0A)
+ *out++ = *str++;
+ else
+ str++;
+ }
+ *out = 0;
+
+ EVP_ENCODE_CTX_free(ctx);
+ return 0;
}
diff --git a/aclk/aclk_util.h b/aclk/aclk_util.h
index ed715e04..76dc8cad 100644
--- a/aclk/aclk_util.h
+++ b/aclk/aclk_util.h
@@ -93,9 +93,13 @@ enum aclk_topics {
ACLK_TOPICID_CTXS_UPDATED = 20
};
+typedef size_t aclk_topic_cache_iter_t;
+#define ACLK_TOPIC_CACHE_ITER_T_INITIALIZER (0)
+
const char *aclk_get_topic(enum aclk_topics topic);
int aclk_generate_topic_cache(struct json_object *json);
void free_topic_cache(void);
+const char *aclk_topic_cache_iterate(aclk_topic_cache_iter_t *iter);
// TODO
// aclk_topics_reload //when claim id changes
@@ -107,6 +111,8 @@ extern volatile int aclk_conversation_log_counter;
unsigned long int aclk_tbeb_delay(int reset, int base, unsigned long int min, unsigned long int max);
#define aclk_tbeb_reset(x) aclk_tbeb_delay(1, 0, 0, 0)
-void aclk_set_proxy(char **ohost, int *port, enum mqtt_wss_proxy_type *type);
+void aclk_set_proxy(char **ohost, int *port, char **uname, char **pwd, enum mqtt_wss_proxy_type *type);
+
+int base64_encode_helper(unsigned char *out, int *outl, const unsigned char *in, int in_len);
#endif /* ACLK_UTIL_H */
diff --git a/aclk/https_client.c b/aclk/https_client.c
index 1a32f833..e2a42eef 100644
--- a/aclk/https_client.c
+++ b/aclk/https_client.c
@@ -6,6 +6,10 @@
#include "mqtt_websockets/c-rbuf/include/ringbuffer.h"
+#include "aclk_util.h"
+
+#include "daemon/global_statistics.h"
+
enum http_parse_state {
HTTP_PARSE_INITIAL = 0,
HTTP_PARSE_HEADERS,
@@ -352,7 +356,7 @@ static int read_parse_response(https_req_ctx_t *ctx) {
#define TX_BUFFER_SIZE 8192
#define RX_BUFFER_SIZE (TX_BUFFER_SIZE*2)
static int handle_http_request(https_req_ctx_t *ctx) {
- BUFFER *hdr = buffer_create(TX_BUFFER_SIZE);
+ BUFFER *hdr = buffer_create(TX_BUFFER_SIZE, &netdata_buffers_statistics.buffers_aclk);
int rc = 0;
http_parse_ctx_clear(&ctx->parse_ctx);
@@ -392,6 +396,24 @@ static int handle_http_request(https_req_ctx_t *ctx) {
if (ctx->request->request_type == HTTP_REQ_POST && ctx->request->payload && ctx->request->payload_size) {
buffer_sprintf(hdr, "Content-Length: %zu\x0D\x0A", ctx->request->payload_size);
}
+ if (ctx->request->proxy_username) {
+ size_t creds_plain_len = strlen(ctx->request->proxy_username) + strlen(ctx->request->proxy_password) + 1 /* ':' */;
+ char *creds_plain = callocz(1, creds_plain_len + 1);
+ char *ptr = creds_plain;
+ strcpy(ptr, ctx->request->proxy_username);
+ ptr += strlen(ctx->request->proxy_username);
+ *ptr++ = ':';
+ strcpy(ptr, ctx->request->proxy_password);
+
+ int creds_base64_len = (((4 * creds_plain_len / 3) + 3) & ~3);
+ // OpenSSL encoder puts newline every 64 output bytes
+ // we remove those but during encoding we need that space in the buffer
+ creds_base64_len += (1+(creds_base64_len/64)) * strlen("\n");
+ char *creds_base64 = callocz(1, creds_base64_len + 1);
+ base64_encode_helper((unsigned char*)creds_base64, &creds_base64_len, (unsigned char*)creds_plain, creds_plain_len);
+ buffer_sprintf(hdr, "Proxy-Authorization: Basic %s\x0D\x0A", creds_base64);
+ freez(creds_plain);
+ }
buffer_strcat(hdr, "\x0D\x0A");
@@ -491,6 +513,8 @@ int https_request(https_req_t *request, https_req_response_t *response) {
req.host = request->host;
req.port = request->port;
req.url = request->url;
+ req.proxy_username = request->proxy_username;
+ req.proxy_password = request->proxy_password;
ctx->request = &req;
if (handle_http_request(ctx)) {
error("Failed to CONNECT with proxy");
diff --git a/aclk/https_client.h b/aclk/https_client.h
index f7bc3d43..daf4766f 100644
--- a/aclk/https_client.h
+++ b/aclk/https_client.h
@@ -25,6 +25,8 @@ typedef struct {
char *proxy_host;
int proxy_port;
+ const char *proxy_username;
+ const char *proxy_password;
} https_req_t;
typedef struct {
diff --git a/build-artifacts.sh b/build-artifacts.sh
deleted file mode 100755
index a759efd6..00000000
--- a/build-artifacts.sh
+++ /dev/null
@@ -1,27 +0,0 @@
-#!/bin/sh
-
-BASENAME="netdata-$(git describe)"
-
-mkdir -p artifacts
-
-autoreconf -ivf
-./configure \
- --prefix=/usr \
- --sysconfdir=/etc \
- --localstatedir=/var \
- --libexecdir=/usr/libexec \
- --with-zlib \
- --with-math \
- --with-user=netdata \
- CFLAGS=-O2
-make dist
-mv "${BASENAME}.tar.gz" artifacts/
-
-USER="" ./packaging/makeself/build-x86_64-static.sh
-
-cp packaging/version artifacts/latest-version.txt
-
-cd artifacts || exit 1
-ln -s "${BASENAME}.tar.gz" netdata-latest.tar.gz
-ln -s "${BASENAME}.gz.run" netdata-latest.gz.run
-sha256sum -b ./* > "sha256sums.txt"
diff --git a/build_external/scenarios/aclk-testing/agent_netdata.conf b/build_external/scenarios/aclk-testing/agent_netdata.conf
index 0d3627de..b76f5fad 100644
--- a/build_external/scenarios/aclk-testing/agent_netdata.conf
+++ b/build_external/scenarios/aclk-testing/agent_netdata.conf
@@ -80,7 +80,6 @@
# go.d = yes
# apps = yes
# charts.d = yes
- # fping = yes
# python.d = yes
# perf = yes
# ioping = yes
@@ -251,10 +250,6 @@
# update every = 1
# command options =
-[plugin:fping]
- # update every = 1
- # command options =
-
[plugin:python.d]
# update every = 1
# command options =
diff --git a/build_external/scenarios/gaps_lo/mostly_off.conf b/build_external/scenarios/gaps_lo/mostly_off.conf
index 079fef06..e66f928d 100644
--- a/build_external/scenarios/gaps_lo/mostly_off.conf
+++ b/build_external/scenarios/gaps_lo/mostly_off.conf
@@ -25,7 +25,6 @@ errors flood protection period = 0
apps = no
go.d = no
perf = no
- fping = no
python.d = no
charts.d = no
nfacct = no
diff --git a/claim/README.md b/claim/README.md
index 3731d200..f1d893eb 100644
--- a/claim/README.md
+++ b/claim/README.md
@@ -1,17 +1,21 @@
<!--
title: "Connect Agent to Cloud"
description: "Connecting a Netdata Agent, running on a distributed node, to Netdata Cloud securely via the encrypted Agent-Cloud link (ACLK)."
-custom_edit_url: https://github.com/netdata/netdata/edit/master/claim/README.md
+custom_edit_url: "https://github.com/netdata/netdata/edit/master/claim/README.md"
+sidebar_label: "Connect Agent to Cloud"
+learn_status: "Published"
+learn_topic_type: "Tasks"
+learn_rel_path: "Setup"
-->
# Connect Agent to Cloud
You can securely connect a Netdata Agent, running on a distributed node, to Netdata Cloud. A Space's
administrator creates a **claiming token**, which is used to add an Agent to their Space via the [Agent-Cloud link
-(ACLK)](/aclk/README.md).
+(ACLK)](https://github.com/netdata/netdata/blob/master/aclk/README.md).
Are you just starting out with Netdata Cloud? See our [get started with
-Cloud](https://learn.netdata.cloud/docs/cloud/get-started) guide for a walkthrough of the process and simplified
+Cloud](https://github.com/netdata/netdata/blob/master/docs/cloud/cloud.mdx) guide for a walkthrough of the process and simplified
instructions.
When connecting an agent (also referred to as a node) to Netdata Cloud, you must complete a verification process that proves you have some level of authorization to manage the node itself. This verification is a security feature that helps prevent unauthorized users from seeing the data on your node.
@@ -22,13 +26,13 @@ Netdata Cloud.
> The connection process ensures no third party can add your node, and then view your node's metrics, in a Cloud account,
> Space, or War Room that you did not authorize.
-By connecting a node, you opt-in to sending data from your Agent to Netdata Cloud via the [ACLK](/aclk/README.md). This
+By connecting a node, you opt-in to sending data from your Agent to Netdata Cloud via the [ACLK](https://github.com/netdata/netdata/blob/master/aclk/README.md). This
data is encrypted by TLS while it is in transit. We use the RSA keypair created during the connection process to authenticate the
identity of the Netdata Agent when it connects to the Cloud. While the data does flow through Netdata Cloud servers on its way
from Agents to the browser, we do not store or log it.
You can connect a node during the Netdata Cloud onboarding process, or after you created a Space by clicking on **Connect
-Nodes** in the [Spaces management area](https://learn.netdata.cloud/docs/cloud/spaces#manage-spaces).
+Nodes** in the [Spaces management area](https://github.com/netdata/netdata/blob/master/docs/cloud/cloud.mdx#manage-spaces).
There are two important notes regarding connecting nodes:
@@ -42,7 +46,7 @@ There will be three main flows from where you might want to connect a node to Ne
* when you are on an [
War Room](#empty-war-room) and you want to connect your first node
* when you are at the [Manage Space](#manage-space-or-war-room) area and you select **Connect Nodes** to connect a node, coming from Manage Space or Manage War Room
-* when you are on the [Nodes view page](https://learn.netdata.cloud/docs/cloud/visualize/nodes) and want to connect a node - this process falls into the [Manage Space](#manage-space-or-war-room) flow
+* when you are on the [Nodes view page](https://github.com/netdata/netdata/blob/master/docs/cloud/visualize/nodes.md) and want to connect a node - this process falls into the [Manage Space](#manage-space-or-war-room) flow
Please note that only the administrators of a Space in Netdata Cloud can view the claiming token and accompanying script, generated by Netdata Cloud, to trigger the connection process.
@@ -66,11 +70,11 @@ finished onboarding.
To connect a node, select which War Rooms you want to add this node to with the dropdown, then copy and paste the script
given by Netdata Cloud into your node's terminal.
-When coming from [Nodes view page](https://learn.netdata.cloud/docs/cloud/visualize/nodes) the room parameter is already defined to current War Room.
+When coming from [Nodes view page](https://github.com/netdata/netdata/blob/master/docs/cloud/visualize/nodes.md) the room parameter is already defined to current War Room.
### Connect an agent running in Linux
-If you want to connect a node that is running on a Linux environment, the script that will be provided to you by Netdata Cloud is the [kickstart](/packaging/installer/README.md#automatic-one-line-installation-script) which will install the Netdata Agent on your node, if it isn't already installed, and connect the node to Netdata Cloud. It should be similar to:
+If you want to connect a node that is running on a Linux environment, the script that will be provided to you by Netdata Cloud is the [kickstart](https://github.com/netdata/netdata/blob/master/packaging/installer/README.md#automatic-one-line-installation-script) which will install the Netdata Agent on your node, if it isn't already installed, and connect the node to Netdata Cloud. It should be similar to:
```
wget -O /tmp/netdata-kickstart.sh https://my-netdata.io/kickstart.sh && sh /tmp/netdata-kickstart.sh --claim-token TOKEN --claim-rooms ROOM1,ROOM2 --claim-url https://api.netdata.cloud
@@ -80,7 +84,7 @@ the node in your Space after 60 seconds, see the [troubleshooting information](#
Please note that to run it you will either need to have root privileges or run it with the user that is running the agent, more details on the [Connect an agent without root privileges](#connect-an-agent-without-root-privileges) section.
-For more details on what are the extra parameters `claim-token`, `claim-rooms` and `claim-url` please refer to [Connect node to Netdata Cloud during installation](/packaging/installer/methods/kickstart.md#connect-node-to-netdata-cloud-during-installation).
+For more details on what are the extra parameters `claim-token`, `claim-rooms` and `claim-url` please refer to [Connect node to Netdata Cloud during installation](https://github.com/netdata/netdata/blob/master/packaging/installer/methods/kickstart.md#connect-node-to-netdata-cloud-during-installation).
### Connect an agent without root privileges
@@ -114,7 +118,7 @@ connected on startup or restart.
For the connection process to work, the contents of `/var/lib/netdata` _must_ be preserved across container
restarts using a persistent volume. See our [recommended `docker run` and Docker Compose
-examples](/packaging/docker/README.md#create-a-new-netdata-agent-container) for details.
+examples](https://github.com/netdata/netdata/blob/master/packaging/docker/README.md#create-a-new-netdata-agent-container) for details.
#### Known issues on older hosts with seccomp enabled
@@ -285,17 +289,17 @@ you don't see the node in your Space after 60 seconds, see the [troubleshooting
### Connect an agent running in macOS
-To connect a node that is running on a macOS environment the script that will be provided to you by Netdata Cloud is the [kickstart](/packaging/installer/methods/macos.md#install-netdata-with-our-automatic-one-line-installation-script) which will install the Netdata Agent on your node, if it isn't already installed, and connect the node to Netdata Cloud. It should be similar to:
+To connect a node that is running on a macOS environment the script that will be provided to you by Netdata Cloud is the [kickstart](https://github.com/netdata/netdata/blob/master/packaging/installer/methods/macos.md#install-netdata-with-our-automatic-one-line-installation-script) which will install the Netdata Agent on your node, if it isn't already installed, and connect the node to Netdata Cloud. It should be similar to:
```bash
-curl https://my-netdata.io/kickstart.sh > /tmp/netdata-kickstart.sh && sh /tmp/netdata-kickstart.sh --install /usr/local/ --claim-token TOKEN --claim-rooms ROOM1,ROOM2 --claim-url https://api.netdata.cloud
+curl https://my-netdata.io/kickstart.sh > /tmp/netdata-kickstart.sh && sh /tmp/netdata-kickstart.sh --install-prefix /usr/local/ --claim-token TOKEN --claim-rooms ROOM1,ROOM2 --claim-url https://api.netdata.cloud
```
The script should return `Agent was successfully claimed.`. If the connecting to Netdata Cloud process returns errors, or if you don't see
the node in your Space after 60 seconds, see the [troubleshooting information](#troubleshooting).
### Connect a Kubernetes cluster's parent Netdata pod
-Read our [Kubernetes installation](/packaging/installer/methods/kubernetes.md#connect-your-kubernetes-cluster-to-netdata-cloud)
+Read our [Kubernetes installation](https://github.com/netdata/netdata/blob/master/packaging/installer/methods/kubernetes.md#connect-your-kubernetes-cluster-to-netdata-cloud)
for details on connecting a parent Netdata pod.
### Connect through a proxy
@@ -324,7 +328,7 @@ For example, a HTTP proxy setting may look like the following:
proxy = http://proxy.example.com:1080 # With a URL
```
-You can now move on to connecting. When you connect with the [kickstart](/packaging/installer/README.md#automatic-one-line-installation-script) script, add the `--claim-proxy=` parameter and
+You can now move on to connecting. When you connect with the [kickstart](https://github.com/netdata/netdata/blob/master/packaging/installer/README.md#automatic-one-line-installation-script) script, add the `--claim-proxy=` parameter and
append the same proxy setting you added to `netdata.conf`.
```bash
@@ -336,7 +340,7 @@ you don't see the node in your Space after 60 seconds, see the [troubleshooting
### Troubleshooting
-If you're having trouble connecting a node, this may be because the [ACLK](/aclk/README.md) cannot connect to Cloud.
+If you're having trouble connecting a node, this may be because the [ACLK](https://github.com/netdata/netdata/blob/master/aclk/README.md) cannot connect to Cloud.
With the Netdata Agent running, visit `http://NODE:19999/api/v1/info` in your browser, replacing `NODE` with the IP
address or hostname of your Agent. The returned JSON contains four keys that will be helpful to diagnose any issues you
@@ -369,7 +373,7 @@ If you run the kickstart script and get the following error `Existing install ap
If you are using an unsupported package, such as a third-party `.deb`/`.rpm` package provided by your distribution,
please remove that package and reinstall using our [recommended kickstart
-script](/docs/get-started.mdx#install-on-linux-with-one-line-installer).
+script](https://github.com/netdata/netdata/blob/master/docs/get-started.mdx#install-on-linux-with-one-line-installer).
#### kickstart: Failed to write new machine GUID
@@ -382,14 +386,14 @@ For a successful execution you will need to run the script with root privileges
#### bash: netdata-claim.sh: command not found
-If you run the claiming script and see a `command not found` error, you either installed Netdata in a non-standard
-location or are using an unsupported package. If you installed Netdata in a non-standard path using the `--install`
-option, you need to update your `$PATH` or run `netdata-claim.sh` using the full path. For example, if you installed
-Netdata to `/opt/netdata`, use `/opt/netdata/bin/netdata-claim.sh` to run the claiming script.
+If you run the claiming script and see a `command not found` error, you either installed Netdata in a
+non-standard location or are using an unsupported package. If you installed Netdata in a non-standard path using the
+`--install-prefix` option, you need to update your `$PATH` or run `netdata-claim.sh` using the full path. For example,
+if you installed Netdata to `/opt/netdata`, use `/opt/netdata/bin/netdata-claim.sh` to run the claiming script.
If you are using an unsupported package, such as a third-party `.deb`/`.rpm` package provided by your distribution,
please remove that package and reinstall using our [recommended kickstart
-script](/docs/get-started.mdx#install-on-linux-with-one-line-installer).
+script](https://github.com/netdata/netdata/blob/master/docs/get-started.mdx#install-on-linux-with-one-line-installer).
#### Connecting on older distributions (Ubuntu 14.04, Debian 8, CentOS 6)
@@ -398,7 +402,7 @@ If you're running an older Linux distribution or one that has reached EOL, such
versions of OpenSSL cannot perform [hostname validation](https://wiki.openssl.org/index.php/Hostname_validation), which
helps securely encrypt SSL connections.
-We recommend you reinstall Netdata with a [static build](/packaging/installer/methods/kickstart.md#static-builds), which uses an
+We recommend you reinstall Netdata with a [static build](https://github.com/netdata/netdata/blob/master/packaging/installer/methods/kickstart.md#static-builds), which uses an
up-to-date version of OpenSSL with hostname validation enabled.
If you choose to continue using the outdated version of OpenSSL, your node will still connect to Netdata Cloud, albeit
@@ -416,7 +420,7 @@ Additionally, check that the `enabled` setting in `var/lib/netdata/cloud.d/cloud
enabled = true
```
-To fix this issue, reinstall Netdata using your [preferred method](/packaging/installer/README.md) and do not add the
+To fix this issue, reinstall Netdata using your [preferred method](https://github.com/netdata/netdata/blob/master/packaging/installer/README.md) and do not add the
`--disable-cloud` option.
#### cloud-available is false / ACLK Available: No
@@ -506,20 +510,20 @@ tool, and details about the files found in `cloud.d`.
### The `cloud.conf` file
-This section defines how and whether your Agent connects to [Netdata Cloud](https://learn.netdata.cloud/docs/cloud/)
-using the [ACLK](/aclk/README.md).
+This section defines how and whether your Agent connects to [Netdata Cloud](https://github.com/netdata/netdata/blob/master/docs/cloud/cloud.mdx)
+using the [ACLK](https://github.com/netdata/netdata/blob/master/aclk/README.md).
| setting | default | info |
|:-------------- |:------------------------- |:-------------------------------------------------------------------------------------------------------------------------------------- |
| cloud base url | https://api.netdata.cloud | The URL for the Netdata Cloud web application. You should not change this. If you want to disable Cloud, change the `enabled` setting. |
-| enabled | yes | The runtime option to disable the [Agent-Cloud link](/aclk/README.md) and prevent your Agent from connecting to Netdata Cloud. |
+| enabled | yes | The runtime option to disable the [Agent-Cloud link](https://github.com/netdata/netdata/blob/master/aclk/README.md) and prevent your Agent from connecting to Netdata Cloud. |
### kickstart script
-The best way to install Netdata and connect your nodes to Netdata Cloud is with our automatic one-line installation script, [kickstart](/packaging/installer/README.md#automatic-one-line-installation-script). This script will install the Netdata Agent, in case it isn't already installed, and connect your node to Netdata Cloud.
+The best way to install Netdata and connect your nodes to Netdata Cloud is with our automatic one-line installation script, [kickstart](https://github.com/netdata/netdata/blob/master/packaging/installer/README.md#automatic-one-line-installation-script). This script will install the Netdata Agent, in case it isn't already installed, and connect your node to Netdata Cloud.
This works with:
-* most Linux distributions, see [Netdata's platform support policy](/packaging/PLATFORM_SUPPORT.md)
+* most Linux distributions, see [Netdata's platform support policy](https://github.com/netdata/netdata/blob/master/packaging/PLATFORM_SUPPORT.md)
* macOS
For details on how to run this script please check [How to connect a node](#how-to-connect-a-node) and choose your environment.
@@ -538,7 +542,7 @@ wget -O /tmp/netdata-kickstart.sh https://my-netdata.io/kickstart.sh && sh /tmp/
**macOS**
```bash
-curl https://my-netdata.io/kickstart.sh > /tmp/netdata-kickstart.sh && sh /tmp/netdata-kickstart.sh --install /usr/local/
+curl https://my-netdata.io/kickstart.sh > /tmp/netdata-kickstart.sh && sh /tmp/netdata-kickstart.sh --install-prefix /usr/local/
```
### Claiming script
@@ -574,7 +578,7 @@ netdatacli reload-claiming-state
This reloads the Agent connection state from disk.
-Our recommendation is to trigger the connection process using the [kickstart](/packaging/installer/README.md#automatic-one-line-installation-script) whenever possible.
+Our recommendation is to trigger the connection process using the [kickstart](https://github.com/netdata/netdata/blob/master/packaging/installer/README.md#automatic-one-line-installation-script) whenever possible.
### Netdata Agent command line
diff --git a/claim/claim.c b/claim/claim.c
index d997fc84..9fe156d2 100644
--- a/claim/claim.c
+++ b/claim/claim.c
@@ -171,8 +171,8 @@ void load_claiming_state(void)
invalidate_node_instances(&localhost->host_uuid, claimed_id ? &uuid : NULL);
metaqueue_store_claim_id(&localhost->host_uuid, claimed_id ? &uuid : NULL);
-
rrdhost_aclk_state_unlock(localhost);
+
if (!claimed_id) {
info("Unable to load '%s', setting state to AGENT_UNCLAIMED", filename);
return;
diff --git a/cli/README.md b/cli/README.md
index afd5651c..09f20174 100644
--- a/cli/README.md
+++ b/cli/README.md
@@ -1,7 +1,11 @@
<!--
title: "Netdata CLI"
description: "The Netdata Agent includes a command-line experience for reloading health configuration, reopening log files, halting the daemon, and more."
-custom_edit_url: https://github.com/netdata/netdata/edit/master/cli/README.md
+custom_edit_url: "https://github.com/netdata/netdata/edit/master/cli/README.md"
+sidebar_label: "Agent CLI"
+learn_status: "Published"
+learn_topic_type: "References"
+learn_rel_path: "References"
-->
# Netdata CLI
@@ -35,6 +39,6 @@ aclk-state [json]
Returns current state of ACLK and Cloud connection. (optionally in json)
```
-Those commands are the same that can be sent to netdata via [signals](/daemon/README.md#command-line-options).
+Those commands are the same that can be sent to netdata via [signals](https://github.com/netdata/netdata/blob/master/daemon/README.md#command-line-options).
diff --git a/collectors/COLLECTORS.md b/collectors/COLLECTORS.md
index 7f66076f..a61a32dd 100644
--- a/collectors/COLLECTORS.md
+++ b/collectors/COLLECTORS.md
@@ -1,7 +1,11 @@
<!--
title: "Supported collectors list"
description: "Netdata gathers real-time metrics from hundreds of data sources using collectors. Most require zero configuration and are pre-configured out of the box."
-custom_edit_url: https://github.com/netdata/netdata/edit/master/collectors/COLLECTORS.md
+custom_edit_url: "https://github.com/netdata/netdata/edit/master/collectors/COLLECTORS.md"
+sidebar_label: "Supported collectors list"
+learn_status: "Published"
+learn_topic_type: "Tasks"
+learn_rel_path: "References/Collectors"
-->
# Supported collectors list
@@ -10,16 +14,19 @@ Netdata uses collectors to help you gather metrics from your favorite applicatio
real-time, interactive charts. The following list includes collectors for both external services/applications and
internal system metrics.
-Learn more about [how collectors work](/docs/collect/how-collectors-work.md), and then learn how to [enable or
-configure](/docs/collect/enable-configure.md) any of the below collectors using the same process.
+Learn more
+about [how collectors work](https://github.com/netdata/netdata/blob/master/docs/collect/how-collectors-work.md), and
+then learn how to [enable or
+configure](https://github.com/netdata/netdata/blob/master/docs/collect/enable-configure.md) any of the below collectors using the same process.
Some collectors have both Go and Python versions as we continue our effort to migrate all collectors to Go. In these
cases, _Netdata always prioritizes the Go version_, and we highly recommend you use the Go versions for the best
experience.
-If you want to use a Python version of a collector, you need to explicitly [disable the Go
-version](/docs/collect/enable-configure.md), and enable the Python version. Netdata then skips the Go version and
-attempts to load the Python version and its accompanying configuration file.
+If you want to use a Python version of a collector, you need to
+explicitly [disable the Go version](https://github.com/netdata/netdata/blob/masterhttps://github.com/netdata/netdata/blob/master/docs/collect/enable-configure.md),
+and enable the Python version. Netdata then skips the Go version and attempts to load the Python version and its
+accompanying configuration file.
If you don't see the app/service you'd like to monitor in this list:
@@ -29,7 +36,7 @@ If you don't see the app/service you'd like to monitor in this list:
a [feature request](https://github.com/netdata/netdata/issues/new/choose) on GitHub.
- If you have basic software development skills, you can add your own plugin
in [Go](https://learn.netdata.cloud/docs/agent/collectors/go.d.plugin#how-to-develop-a-collector)
- or [Python](https://learn.netdata.cloud/guides/python-collector)
+ or [Python](https://github.com/netdata/netdata/blob/master/docs/guides/python-collector.md)
Supported Collectors List:
@@ -72,257 +79,300 @@ configure any of these collectors according to your setup and infrastructure.
### Generic
-- [Prometheus endpoints](https://learn.netdata.cloud/docs/agent/collectors/go.d.plugin/modules/prometheus): Gathers
+- [Prometheus endpoints](https://github.com/netdata/go.d.plugin/blob/master/modules/prometheus/README.md): Gathers
metrics from any number of Prometheus endpoints, with support to autodetect more than 600 services and applications.
-- [Pandas](https://learn.netdata.cloud/docs/agent/collectors/python.d.plugin/pandas): A Python collector that gathers
- metrics from a [pandas](https://pandas.pydata.org/) dataframe. Pandas is a high level data processing library in
- Python that can read various formats of data from local files or web endpoints. Custom processing and transformation
+- [Pandas](https://github.com/netdata/netdata/blob/master/collectors/python.d.plugin/pandas/README.md): A Python
+ collector that gathers
+ metrics from a [pandas](https://pandas.pydata.org/) dataframe. Pandas is a high level data processing library in
+ Python that can read various formats of data from local files or web endpoints. Custom processing and transformation
logic can also be expressed as part of the collector configuration.
### APM (application performance monitoring)
-- [Go applications](/collectors/python.d.plugin/go_expvar/README.md): Monitor any Go application that exposes its
+- [Go applications](https://github.com/netdata/netdata/blob/master/collectors/python.d.plugin/go_expvar/README.md):
+ Monitor any Go application that exposes its
metrics with the `expvar` package from the Go standard library.
-- [Java Spring Boot 2
- applications](https://learn.netdata.cloud/docs/agent/collectors/go.d.plugin/modules/springboot2/) (Go version):
+- [Java Spring Boot 2 applications](https://github.com/netdata/go.d.plugin/blob/master/modules/springboot2/README.md):
Monitor running Java Spring Boot 2 applications that expose their metrics with the use of the Spring Boot Actuator.
-- [Java Spring Boot 2 applications](/collectors/python.d.plugin/springboot/README.md) (Python version): Monitor
- running Java Spring Boot applications that expose their metrics with the use of the Spring Boot Actuator.
-- [statsd](/collectors/statsd.plugin/README.md): Implement a high performance `statsd` server for Netdata.
-- [phpDaemon](https://learn.netdata.cloud/docs/agent/collectors/go.d.plugin/modules/phpdaemon/): Collect worker
+- [statsd](https://github.com/netdata/netdata/blob/master/collectors/statsd.plugin/README.md): Implement a high
+ performance `statsd` server for Netdata.
+- [phpDaemon](https://github.com/netdata/go.d.plugin/blob/master/modules/phpdaemon/README.md): Collect worker
statistics (total, active, idle), and uptime for web and network applications.
-- [uWSGI](/collectors/python.d.plugin/uwsgi/README.md): Monitor performance metrics exposed by the uWSGI Stats
+- [uWSGI](https://github.com/netdata/netdata/blob/master/collectors/python.d.plugin/uwsgi/README.md): Monitor
+ performance metrics exposed by the uWSGI Stats
Server.
### Containers and VMs
-- [Docker containers](/collectors/cgroups.plugin/README.md): Monitor the health and performance of individual Docker
- containers using the cgroups collector plugin.
-- [DockerD](/collectors/python.d.plugin/dockerd/README.md): Collect container health statistics.
-- [Docker Engine](https://learn.netdata.cloud/docs/agent/collectors/go.d.plugin/modules/docker_engine/): Collect
+- [Docker containers](https://github.com/netdata/netdata/blob/master/collectors/cgroups.plugin/README.md): Monitor the
+ health and performance of individual Docker containers using the cgroups collector plugin.
+- [DockerD](https://github.com/netdata/go.d.plugin/blob/master/modules/docker/README.md): Collect container health
+ statistics.
+- [Docker Engine](https://github.com/netdata/go.d.plugin/blob/master/modules/docker_engine/README.md): Collect
runtime statistics from the `docker` daemon using the `metrics-address` feature.
-- [Docker Hub](https://learn.netdata.cloud/docs/agent/collectors/go.d.plugin/modules/dockerhub/): Collect statistics
+- [Docker Hub](https://github.com/netdata/go.d.plugin/blob/master/modules/dockerhub/README.md): Collect statistics
about Docker repositories, such as pulls, starts, status, time since last update, and more.
-- [Libvirt](/collectors/cgroups.plugin/README.md): Monitor the health and performance of individual Libvirt containers
+- [Libvirt](https://github.com/netdata/netdata/blob/master/collectors/cgroups.plugin/README.md): Monitor the health and
+ performance of individual Libvirt containers
using the cgroups collector plugin.
-- [LXC](/collectors/cgroups.plugin/README.md): Monitor the health and performance of individual LXC containers using
+- [LXC](https://github.com/netdata/netdata/blob/master/collectors/cgroups.plugin/README.md): Monitor the health and
+ performance of individual LXC containers using
the cgroups collector plugin.
-- [LXD](/collectors/cgroups.plugin/README.md): Monitor the health and performance of individual LXD containers using
+- [LXD](https://github.com/netdata/netdata/blob/master/collectors/cgroups.plugin/README.md): Monitor the health and
+ performance of individual LXD containers using
the cgroups collector plugin.
-- [systemd-nspawn](/collectors/cgroups.plugin/README.md): Monitor the health and performance of individual
+- [systemd-nspawn](https://github.com/netdata/netdata/blob/master/collectors/cgroups.plugin/README.md): Monitor the
+ health and performance of individual
systemd-nspawn containers using the cgroups collector plugin.
-- [vCenter Server Appliance](https://learn.netdata.cloud/docs/agent/collectors/go.d.plugin/modules/vcsa/): Monitor
+- [vCenter Server Appliance](https://github.com/netdata/go.d.plugin/blob/master/modules/vcsa/README.md): Monitor
appliance system, components, and software update health statuses via the Health API.
-- [vSphere](https://learn.netdata.cloud/docs/agent/collectors/go.d.plugin/modules/vsphere/): Collect host and virtual
+- [vSphere](https://github.com/netdata/go.d.plugin/blob/master/modules/vsphere/README.md): Collect host and virtual
machine performance metrics.
-- [Xen/XCP-ng](/collectors/xenstat.plugin/README.md): Collect XenServer and XCP-ng metrics using `libxenstat`.
+- [Xen/XCP-ng](https://github.com/netdata/netdata/blob/master/collectors/xenstat.plugin/README.md): Collect XenServer
+ and XCP-ng metrics using `libxenstat`.
### Data stores
-- [CockroachDB](https://learn.netdata.cloud/docs/agent/collectors/go.d.plugin/modules/cockroachdb/): Monitor various
+- [CockroachDB](https://github.com/netdata/go.d.plugin/blob/master/modules/cockroachdb/README.md): Monitor various
database components using `_status/vars` endpoint.
-- [Consul](https://learn.netdata.cloud/docs/agent/collectors/go.d.plugin/modules/consul/): Capture service and unbound
+- [Consul](https://github.com/netdata/go.d.plugin/blob/master/modules/consul/README.md): Capture service and unbound
checks status (passing, warning, critical, maintenance).
-- [Couchbase](https://learn.netdata.cloud/docs/agent/collectors/go.d.plugin/modules/couchbase/): Gather per-bucket
+- [Couchbase](https://github.com/netdata/go.d.plugin/blob/master/modules/couchbase/README.md): Gather per-bucket
metrics from any number of instances of the distributed JSON document database.
-- [CouchDB](https://learn.netdata.cloud/docs/agent/collectors/go.d.plugin/modules/couchdb): Monitor database health and
+- [CouchDB](https://github.com/netdata/go.d.plugin/blob/master/modules/couchdb/README.md): Monitor database health and
performance metrics
(reads/writes, HTTP traffic, replication status, etc).
-- [MongoDB](/collectors/python.d.plugin/mongodb/README.md): Collect memory-caching system performance metrics and
- reads the server's response to `stats` command (stats interface).
-- [MySQL](https://learn.netdata.cloud/docs/agent/collectors/go.d.plugin/modules/mysql/): Collect database global,
+- [MongoDB](https://github.com/netdata/go.d.plugin/blob/master/modules/mongodb/README.md): Collect server, database,
+ replication and sharding performance and health metrics.
+- [MySQL](https://github.com/netdata/go.d.plugin/blob/master/modules/mysql/README.md): Collect database global,
replication and per user statistics.
-- [OracleDB](/collectors/python.d.plugin/oracledb/README.md): Monitor database performance and health metrics.
-- [Pika](https://learn.netdata.cloud/docs/agent/collectors/go.d.plugin/modules/pika/): Gather metric, such as clients,
+- [OracleDB](https://github.com/netdata/netdata/blob/master/collectors/python.d.plugin/oracledb/README.md): Monitor
+ database performance and health metrics.
+- [Pika](https://github.com/netdata/go.d.plugin/blob/master/modules/pika/README.md): Gather metric, such as clients,
memory usage, queries, and more from the Redis interface-compatible database.
-- [Postgres](https://learn.netdata.cloud/docs/agent/collectors/go.d.plugin/modules/postgres): Collect database health
+- [Postgres](https://github.com/netdata/go.d.plugin/blob/master/modules/postgres/README.md): Collect database health
and performance metrics.
-- [ProxySQL](/collectors/python.d.plugin/proxysql/README.md): Monitor database backend and frontend performance
- metrics.
-- [Redis](https://learn.netdata.cloud/docs/agent/collectors/go.d.plugin/modules/redis/): Monitor status from any
+- [ProxySQL](https://github.com/netdata/go.d.plugin/blob/master/modules/proxysql/README.md): Monitor database backend
+ and frontend performance metrics.
+- [Redis](https://github.com/netdata/go.d.plugin/blob/master/modules/redis/README.md): Monitor status from any
number of database instances by reading the server's response to the `INFO ALL` command.
-- [RethinkDB](/collectors/python.d.plugin/rethinkdbs/README.md): Collect database server and cluster statistics.
-- [Riak KV](/collectors/python.d.plugin/riakkv/README.md): Collect database stats from the `/stats` endpoint.
-- [Zookeeper](https://learn.netdata.cloud/docs/agent/collectors/go.d.plugin/modules/zookeeper/): Monitor application
+- [RethinkDB](https://github.com/netdata/netdata/blob/master/collectors/python.d.plugin/rethinkdbs/README.md): Collect
+ database server and cluster statistics.
+- [Riak KV](https://github.com/netdata/netdata/blob/master/collectors/python.d.plugin/riakkv/README.md): Collect
+ database stats from the `/stats` endpoint.
+- [Zookeeper](https://github.com/netdata/go.d.plugin/blob/master/modules/zookeeper/README.md): Monitor application
health metrics reading the server's response to the `mntr` command.
-- [Memcached](/collectors/python.d.plugin/memcached/README.md): Collect memory-caching system performance metrics.
+- [Memcached](https://github.com/netdata/netdata/blob/master/collectors/python.d.plugin/memcached/README.md): Collect
+ memory-caching system performance metrics.
### Distributed computing
-- [BOINC](/collectors/python.d.plugin/boinc/README.md): Monitor the total number of tasks, open tasks, and task
+- [BOINC](https://github.com/netdata/netdata/blob/master/collectors/python.d.plugin/boinc/README.md): Monitor the total
+ number of tasks, open tasks, and task
states for the distributed computing client.
-- [Gearman](/collectors/python.d.plugin/gearman/README.md): Collect application summary (queued, running) and per-job
+- [Gearman](https://github.com/netdata/netdata/blob/master/collectors/python.d.plugin/gearman/README.md): Collect
+ application summary (queued, running) and per-job
worker statistics (queued, idle, running).
### Email
-- [Dovecot](/collectors/python.d.plugin/dovecot/README.md): Collect email server performance metrics by reading the
+- [Dovecot](https://github.com/netdata/netdata/blob/master/collectors/python.d.plugin/dovecot/README.md): Collect email
+ server performance metrics by reading the
server's response to the `EXPORT global` command.
-- [EXIM](/collectors/python.d.plugin/exim/README.md): Uses the `exim` tool to monitor the queue length of a
+- [EXIM](https://github.com/netdata/netdata/blob/master/collectors/python.d.plugin/exim/README.md): Uses the `exim` tool
+ to monitor the queue length of a
mail/message transfer agent (MTA).
-- [Postfix](/collectors/python.d.plugin/postfix/README.md): Uses the `postqueue` tool to monitor the queue length of a
+- [Postfix](https://github.com/netdata/netdata/blob/master/collectors/python.d.plugin/postfix/README.md): Uses
+ the `postqueue` tool to monitor the queue length of a
mail/message transfer agent (MTA).
### Kubernetes
-- [Kubelet](https://learn.netdata.cloud/docs/agent/collectors/go.d.plugin/modules/k8s_kubelet/): Monitor one or more
+- [Kubelet](https://github.com/netdata/go.d.plugin/blob/master/modules/k8s_kubelet/README.md): Monitor one or more
instances of the Kubelet agent and collects metrics on number of pods/containers running, volume of Docker
operations, and more.
-- [kube-proxy](https://learn.netdata.cloud/docs/agent/collectors/go.d.plugin/modules/k8s_kubeproxy/): Collect
+- [kube-proxy](https://github.com/netdata/go.d.plugin/blob/master/modules/k8s_kubeproxy/README.md): Collect
metrics, such as syncing proxy rules and REST client requests, from one or more instances of `kube-proxy`.
-- [Service discovery](https://github.com/netdata/agent-service-discovery/): Find what services are running on a
+- [Service discovery](https://github.com/netdata/agent-service-discovery/README.md): Find what services are running on a
cluster's pods, converts that into configuration files, and exports them so they can be monitored by Netdata.
### Logs
-- [Fluentd](https://learn.netdata.cloud/docs/agent/collectors/go.d.plugin/modules/fluentd/): Gather application
+- [Fluentd](https://github.com/netdata/go.d.plugin/blob/master/modules/fluentd/README.md): Gather application
plugins metrics from an endpoint provided by `in_monitor plugin`.
-- [Logstash](https://learn.netdata.cloud/docs/agent/collectors/go.d.plugin/modules/logstash/): Monitor JVM threads,
+- [Logstash](https://github.com/netdata/go.d.plugin/blob/master/modules/logstash/README.md): Monitor JVM threads,
memory usage, garbage collection statistics, and more.
-- [OpenVPN status logs](https://learn.netdata.cloud/docs/agent/collectors/go.d.plugin/modules/openvpn_status_log): Parse
+- [OpenVPN status logs](https://github.com/netdata/go.d.plugin/blob/master/modules/openvpn_status_log/README.md): Parse
server log files and provide summary (client, traffic) metrics.
-- [Squid web server logs](https://learn.netdata.cloud/docs/agent/collectors/go.d.plugin/modules/squidlog/): Tail Squid
+- [Squid web server logs](https://github.com/netdata/go.d.plugin/blob/master/modules/squidlog/README.md): Tail Squid
access logs to return the volume of requests, types of requests, bandwidth, and much more.
- [Web server logs (Go version for Apache,
- NGINX)](https://learn.netdata.cloud/docs/agent/collectors/go.d.plugin/modules/weblog/): Tail access logs and provide
+ NGINX)](https://github.com/netdata/go.d.plugin/blob/master/modules/weblog/README.md/): Tail access logs and provide
very detailed web server performance statistics. This module is able to parse 200k+ rows in less than half a second.
-- [Web server logs (Apache, NGINX)](https://learn.netdata.cloud/docs/agent/collectors/go.d.plugin/modules/weblog): Tail
+- [Web server logs (Apache, NGINX)](https://github.com/netdata/go.d.plugin/blob/master/modules/weblog/README.md): Tail
access log
file and collect web server/caching proxy metrics.
### Messaging
-- [ActiveMQ](https://learn.netdata.cloud/docs/agent/collectors/go.d.plugin/modules/activemq/): Collect message broker
+- [ActiveMQ](https://github.com/netdata/go.d.plugin/blob/master/modules/activemq/README.md): Collect message broker
queues and topics statistics using the ActiveMQ Console API.
-- [Beanstalk](/collectors/python.d.plugin/beanstalk/README.md): Collect server and tube-level statistics, such as CPU
+- [Beanstalk](https://github.com/netdata/netdata/blob/master/collectors/python.d.plugin/beanstalk/README.md): Collect
+ server and tube-level statistics, such as CPU
usage, jobs rates, commands, and more.
-- [Pulsar](https://learn.netdata.cloud/docs/agent/collectors/go.d.plugin/modules/pulsar/): Collect summary,
+- [Pulsar](https://github.com/netdata/go.d.plugin/blob/master/modules/pulsar/README.md): Collect summary,
namespaces, and topics performance statistics.
-- [RabbitMQ (Go)](https://learn.netdata.cloud/docs/agent/collectors/go.d.plugin/modules/rabbitmq/): Collect message
+- [RabbitMQ (Go)](https://github.com/netdata/go.d.plugin/blob/master/modules/rabbitmq/README.md): Collect message
broker overview, system and per virtual host metrics.
-- [RabbitMQ (Python)](/collectors/python.d.plugin/rabbitmq/README.md): Collect message broker global and per virtual
+- [RabbitMQ (Python)](https://github.com/netdata/netdata/blob/master/collectors/python.d.plugin/rabbitmq/README.md):
+ Collect message broker global and per virtual
host metrics.
-- [VerneMQ](https://learn.netdata.cloud/docs/agent/collectors/go.d.plugin/modules/vernemq/): Monitor MQTT broker
+- [VerneMQ](https://github.com/netdata/go.d.plugin/blob/master/modules/vernemq/README.md): Monitor MQTT broker
health and performance metrics. It collects all available info for both MQTTv3 and v5 communication
### Network
-- [Bind 9](https://learn.netdata.cloud/docs/agent/collectors/go.d.plugin/modules/bind/): Collect nameserver summary
+- [Bind 9](https://github.com/netdata/go.d.plugin/blob/master/modules/bind/README.md): Collect nameserver summary
performance statistics via a web interface (`statistics-channels` feature).
-- [Chrony](https://learn.netdata.cloud/docs/agent/collectors/go.d.plugin/modules/chrony): Monitor the precision and
+- [Chrony](https://github.com/netdata/go.d.plugin/blob/master/modules/chrony/README.md): Monitor the precision and
statistics of a local `chronyd` server.
-- [CoreDNS](https://learn.netdata.cloud/docs/agent/collectors/go.d.plugin/modules/coredns/): Measure DNS query round
+- [CoreDNS](https://github.com/netdata/go.d.plugin/blob/master/modules/coredns/README.md): Measure DNS query round
trip time.
-- [Dnsmasq](https://learn.netdata.cloud/docs/agent/collectors/go.d.plugin/modules/dnsmasq_dhcp/): Automatically
+- [Dnsmasq](https://github.com/netdata/go.d.plugin/blob/master/modules/dnsmasq_dhcp/README.md): Automatically
detects all configured `Dnsmasq` DHCP ranges and Monitor their utilization.
-- [DNSdist](https://learn.netdata.cloud/docs/agent/collectors/go.d.plugin/modules/dnsdist/): Collect
+- [DNSdist](https://github.com/netdata/go.d.plugin/blob/master/modules/dnsdist/README.md): Collect
load-balancer performance and health metrics.
-- [Dnsmasq DNS Forwarder](https://learn.netdata.cloud/docs/agent/collectors/go.d.plugin/modules/dnsmasq/): Gather
+- [Dnsmasq DNS Forwarder](https://github.com/netdata/go.d.plugin/blob/master/modules/dnsmasq/README.md): Gather
queries, entries, operations, and events for the lightweight DNS forwarder.
-- [DNS Query Time](https://learn.netdata.cloud/docs/agent/collectors/go.d.plugin/modules/dnsquery/): Monitor the round
+- [DNS Query Time](https://github.com/netdata/go.d.plugin/blob/master/modules/dnsquery/README.md): Monitor the round
trip time for DNS queries in milliseconds.
-- [Freeradius](https://learn.netdata.cloud/docs/agent/collectors/go.d.plugin/modules/freeradius/): Collect
+- [Freeradius](https://github.com/netdata/go.d.plugin/blob/master/modules/freeradius/README.md): Collect
server authentication and accounting statistics from the `status server`.
-- [Libreswan](/collectors/charts.d.plugin/libreswan/README.md): Collect bytes-in, bytes-out, and uptime metrics.
-- [Icecast](/collectors/python.d.plugin/icecast/README.md): Monitor the number of listeners for active sources.
-- [ISC Bind (RDNC)](/collectors/python.d.plugin/bind_rndc/README.md): Collect nameserver summary performance
+- [Libreswan](https://github.com/netdata/netdata/blob/master/collectors/charts.d.plugin/libreswan/README.md): Collect
+ bytes-in, bytes-out, and uptime metrics.
+- [Icecast](https://github.com/netdata/netdata/blob/master/collectors/python.d.plugin/icecast/README.md): Monitor the
+ number of listeners for active sources.
+- [ISC Bind (RDNC)](https://github.com/netdata/netdata/blob/master/collectors/python.d.plugin/bind_rndc/README.md):
+ Collect nameserver summary performance
statistics using the `rndc` tool.
-- [ISC DHCP](https://learn.netdata.cloud/docs/agent/collectors/go.d.plugin/modules/isc_dhcpd): Reads a
+- [ISC DHCP](https://github.com/netdata/go.d.plugin/blob/master/modules/isc_dhcpd/README.md): Reads a
`dhcpd.leases` file and collects metrics on total active leases, pool active leases, and pool utilization.
-- [OpenLDAP](/collectors/python.d.plugin/openldap/README.md): Provides statistics information from the OpenLDAP
+- [OpenLDAP](https://github.com/netdata/netdata/blob/master/collectors/python.d.plugin/openldap/README.md): Provides
+ statistics information from the OpenLDAP
(`slapd`) server.
-- [NSD](/collectors/python.d.plugin/nsd/README.md): Monitor nameserver performance metrics using the `nsd-control`
+- [NSD](https://github.com/netdata/netdata/blob/master/collectors/python.d.plugin/nsd/README.md): Monitor nameserver
+ performance metrics using the `nsd-control`
tool.
-- [NTP daemon](/collectors/python.d.plugin/ntpd/README.md): Monitor the system variables of the local `ntpd` daemon
- (optionally including variables of the polled peers) using the NTP Control Message Protocol via a UDP socket.
-- [OpenSIPS](/collectors/charts.d.plugin/opensips/README.md): Collect server health and performance metrics using the
+- [NTP daemon](https://learn.netdata.cloud/docs/agent/collectors/go.d.plugin/modules/ntpd): Monitor the system variables
+ of the local `ntpd` daemon (optionally including variables of the polled peers) using the NTP Control Message Protocol
+ via a UDP socket.
+- [OpenSIPS](https://github.com/netdata/netdata/blob/master/collectors/charts.d.plugin/opensips/README.md): Collect
+ server health and performance metrics using the
`opensipsctl` tool.
-- [OpenVPN](https://learn.netdata.cloud/docs/agent/collectors/go.d.plugin/modules/openvpn/): Gather server summary
+- [OpenVPN](https://github.com/netdata/go.d.plugin/blob/master/modules/openvpn/README.md): Gather server summary
(client, traffic) and per user metrics (traffic, connection time) stats using `management-interface`.
-- [Pi-hole](https://learn.netdata.cloud/docs/agent/collectors/go.d.plugin/modules/pihole/): Monitor basic (DNS
+- [Pi-hole](https://github.com/netdata/go.d.plugin/blob/master/modules/pihole/README.md): Monitor basic (DNS
queries, clients, blocklist) and extended (top clients, top permitted, and blocked domains) statistics using the PHP
API.
-- [PowerDNS Authoritative Server](https://learn.netdata.cloud/docs/agent/collectors/go.d.plugin/modules/powerdns):
+- [PowerDNS Authoritative Server](https://github.com/netdata/go.d.plugin/blob/master/modules/powerdns/README.md):
Monitor one or more instances of the nameserver software to collect questions, events, and latency metrics.
-- [PowerDNS Recursor](https://learn.netdata.cloud/docs/agent/collectors/go.d.plugin/modules/powerdns_recursor):
+- [PowerDNS Recursor](https://github.com/netdata/go.d.plugin/blob/master/modules/powerdns/README.md_recursor):
Gather incoming/outgoing questions, drops, timeouts, and cache usage from any number of DNS recursor instances.
-- [RetroShare](/collectors/python.d.plugin/retroshare/README.md): Monitor application bandwidth, peers, and DHT
+- [RetroShare](https://github.com/netdata/netdata/blob/master/collectors/python.d.plugin/retroshare/README.md): Monitor
+ application bandwidth, peers, and DHT
metrics.
-- [Tor](/collectors/python.d.plugin/tor/README.md): Capture traffic usage statistics using the Tor control port.
-- [Unbound](https://learn.netdata.cloud/docs/agent/collectors/go.d.plugin/modules/unbound/): Collect DNS resolver
+- [Tor](https://github.com/netdata/netdata/blob/master/collectors/python.d.plugin/tor/README.md): Capture traffic usage
+ statistics using the Tor control port.
+- [Unbound](https://github.com/netdata/go.d.plugin/blob/master/modules/unbound/README.md): Collect DNS resolver
summary and extended system and per thread metrics via the `remote-control` interface.
### Provisioning
-- [Puppet](/collectors/python.d.plugin/puppet/README.md): Monitor the status of Puppet Server and Puppet DB.
+- [Puppet](https://github.com/netdata/netdata/blob/master/collectors/python.d.plugin/puppet/README.md): Monitor the
+ status of Puppet Server and Puppet DB.
### Remote devices
-- [AM2320](/collectors/python.d.plugin/am2320/README.md): Monitor sensor temperature and humidity.
-- [Access point](/collectors/charts.d.plugin/ap/README.md): Monitor client, traffic and signal metrics using the `aw`
+- [AM2320](https://github.com/netdata/netdata/blob/master/collectors/python.d.plugin/am2320/README.md): Monitor sensor
+ temperature and humidity.
+- [Access point](https://github.com/netdata/netdata/blob/master/collectors/charts.d.plugin/ap/README.md): Monitor
+ client, traffic and signal metrics using the `aw`
tool.
-- [APC UPS](/collectors/charts.d.plugin/apcupsd/README.md): Capture status information using the `apcaccess` tool.
-- [Energi Core](https://learn.netdata.cloud/docs/agent/collectors/go.d.plugin/modules/energid): Monitor
+- [APC UPS](https://github.com/netdata/netdata/blob/master/collectors/charts.d.plugin/apcupsd/README.md): Capture status
+ information using the `apcaccess` tool.
+- [Energi Core](https://github.com/netdata/go.d.plugin/blob/master/modules/energid/README.md): Monitor
blockchain indexes, memory usage, network usage, and transactions of wallet instances.
-- [UPS/PDU](/collectors/charts.d.plugin/nut/README.md): Read the status of UPS/PDU devices using the `upsc` tool.
-- [SNMP devices](https://learn.netdata.cloud/docs/agent/collectors/go.d.plugin/modules/snmp): Gather data using the SNMP
+- [UPS/PDU](https://github.com/netdata/netdata/blob/master/collectors/charts.d.plugin/nut/README.md): Read the status of
+ UPS/PDU devices using the `upsc` tool.
+- [SNMP devices](https://github.com/netdata/go.d.plugin/blob/master/modules/snmp/README.md): Gather data using the SNMP
protocol.
-- [1-Wire sensors](/collectors/python.d.plugin/w1sensor/README.md): Monitor sensor temperature.
+- [1-Wire sensors](https://github.com/netdata/netdata/blob/master/collectors/python.d.plugin/w1sensor/README.md):
+ Monitor sensor temperature.
### Search
-- [Elasticsearch](https://learn.netdata.cloud/docs/agent/collectors/go.d.plugin/modules/elasticsearch): Collect
+- [Elasticsearch](https://github.com/netdata/go.d.plugin/blob/master/modules/elasticsearch/README.md): Collect
dozens of metrics on search engine performance from local nodes and local indices. Includes cluster health and
statistics.
-- [Solr](https://learn.netdata.cloud/docs/agent/collectors/go.d.plugin/modules/solr/): Collect application search
+- [Solr](https://github.com/netdata/go.d.plugin/blob/master/modules/solr/README.md): Collect application search
requests, search errors, update requests, and update errors statistics.
### Storage
-- [Ceph](/collectors/python.d.plugin/ceph/README.md): Monitor the Ceph cluster usage and server data consumption.
-- [HDFS](https://learn.netdata.cloud/docs/agent/collectors/go.d.plugin/modules/hdfs/): Monitor health and performance
+- [Ceph](https://github.com/netdata/netdata/blob/master/collectors/python.d.plugin/ceph/README.md): Monitor the Ceph
+ cluster usage and server data consumption.
+- [HDFS](https://github.com/netdata/go.d.plugin/blob/master/modules/hdfs/README.md): Monitor health and performance
metrics for filesystem datanodes and namenodes.
-- [IPFS](/collectors/python.d.plugin/ipfs/README.md): Collect file system bandwidth, peers, and repo metrics.
-- [Scaleio](https://learn.netdata.cloud/docs/agent/collectors/go.d.plugin/modules/scaleio/): Monitor storage system,
+- [IPFS](https://github.com/netdata/netdata/blob/master/collectors/python.d.plugin/ipfs/README.md): Collect file system
+ bandwidth, peers, and repo metrics.
+- [Scaleio](https://github.com/netdata/go.d.plugin/blob/master/modules/scaleio/README.md): Monitor storage system,
storage pools, and SDCS health and performance metrics via VxFlex OS Gateway API.
-- [Samba](/collectors/python.d.plugin/samba/README.md): Collect file sharing metrics using the `smbstatus` tool.
+- [Samba](https://github.com/netdata/netdata/blob/master/collectors/python.d.plugin/samba/README.md): Collect file
+ sharing metrics using the `smbstatus` tool.
### Web
-- [Apache](https://learn.netdata.cloud/docs/agent/collectors/go.d.plugin/modules/apache/): Collect Apache web
+- [Apache](https://github.com/netdata/go.d.plugin/blob/master/modules/apache/README.md): Collect Apache web
server performance metrics via the `server-status?auto` endpoint.
-- [HAProxy](/collectors/python.d.plugin/haproxy/README.md): Collect frontend, backend, and health metrics.
-- [HTTP endpoints](https://learn.netdata.cloud/docs/agent/collectors/go.d.plugin/modules/httpcheck/): Monitor
+- [HAProxy](https://github.com/netdata/netdata/blob/master/collectors/python.d.plugin/haproxy/README.md): Collect
+ frontend, backend, and health metrics.
+- [HTTP endpoints](https://github.com/netdata/go.d.plugin/blob/master/modules/httpcheck/README.md): Monitor
any HTTP endpoint's availability and response time.
-- [Lighttpd](https://learn.netdata.cloud/docs/agent/collectors/go.d.plugin/modules/lighttpd/): Collect web server
+- [Lighttpd](https://github.com/netdata/go.d.plugin/blob/master/modules/lighttpd/README.md): Collect web server
performance metrics using the `server-status?auto` endpoint.
-- [Lighttpd2](https://learn.netdata.cloud/docs/agent/collectors/go.d.plugin/modules/lighttpd2/): Collect web server
+- [Lighttpd2](https://github.com/netdata/go.d.plugin/blob/master/modules/lighttpd2/README.md): Collect web server
performance metrics using the `server-status?format=plain` endpoint.
-- [Litespeed](/collectors/python.d.plugin/litespeed/README.md): Collect web server data (network, connection,
+- [Litespeed](https://github.com/netdata/netdata/blob/master/collectors/python.d.plugin/litespeed/README.md): Collect
+ web server data (network, connection,
requests, cache) by reading `.rtreport*` files.
-- [Nginx](https://learn.netdata.cloud/docs/agent/collectors/go.d.plugin/modules/nginx/): Monitor web server
+- [Nginx](https://github.com/netdata/go.d.plugin/blob/master/modules/nginx/README.md): Monitor web server
status information by gathering metrics via `ngx_http_stub_status_module`.
-- [Nginx VTS](https://learn.netdata.cloud/docs/agent/collectors/go.d.plugin/modules/nginxvts/): Gathers metrics from
+- [Nginx VTS](https://github.com/netdata/go.d.plugin/blob/master/modules/nginxvts/README.md): Gathers metrics from
any Nginx deployment with the _virtual host traffic status module_ enabled, including metrics on uptime, memory
usage, and cache, and more.
-- [PHP-FPM](https://learn.netdata.cloud/docs/agent/collectors/go.d.plugin/modules/phpfpm/): Collect application
+- [PHP-FPM](https://github.com/netdata/go.d.plugin/blob/master/modules/phpfpm/README.md): Collect application
summary and processes health metrics by scraping the status page (`/status?full`).
-- [TCP endpoints](https://learn.netdata.cloud/docs/agent/collectors/go.d.plugin/modules/portcheck/): Monitor any
+- [TCP endpoints](https://github.com/netdata/go.d.plugin/blob/master/modules/portcheck/README.md): Monitor any
TCP endpoint's availability and response time.
-- [Spigot Minecraft servers](/collectors/python.d.plugin/spigotmc/README.md): Monitor average ticket rate and number
+- [Spigot Minecraft servers](https://github.com/netdata/netdata/blob/master/collectors/python.d.plugin/spigotmc/README.md):
+ Monitor average ticket rate and number
of users.
-- [Squid](/collectors/python.d.plugin/squid/README.md): Monitor client and server bandwidth/requests by gathering
+- [Squid](https://github.com/netdata/netdata/blob/master/collectors/python.d.plugin/squid/README.md): Monitor client and
+ server bandwidth/requests by gathering
data from the Cache Manager component.
-- [Tengine](https://learn.netdata.cloud/docs/agent/collectors/go.d.plugin/modules/tengine/): Monitor web server
+- [Tengine](https://github.com/netdata/go.d.plugin/blob/master/modules/tengine/README.md): Monitor web server
statistics using information provided by `ngx_http_reqstat_module`.
-- [Tomcat](/collectors/python.d.plugin/tomcat/README.md): Collect web server performance metrics from the Manager App
+- [Tomcat](https://github.com/netdata/netdata/blob/master/collectors/python.d.plugin/tomcat/README.md): Collect web
+ server performance metrics from the Manager App
(`/manager/status?XML=true`).
-- [Traefik](/collectors/python.d.plugin/traefik/README.md): Uses Traefik's Health API to provide statistics.
-- [Varnish](/collectors/python.d.plugin/varnish/README.md): Provides HTTP accelerator global, backends (VBE), and
+- [Traefik](https://github.com/netdata/netdata/blob/master/collectors/python.d.plugin/traefik/README.md): Uses Traefik's
+ Health API to provide statistics.
+- [Varnish](https://github.com/netdata/netdata/blob/master/collectors/python.d.plugin/varnish/README.md): Provides HTTP
+ accelerator global, backends (VBE), and
disks (SMF) statistics using the `varnishstat` tool.
-- [x509 check](https://learn.netdata.cloud/docs/agent/collectors/go.d.plugin/modules/x509check/): Monitor certificate
+- [x509 check](https://github.com/netdata/go.d.plugin/blob/master/modules/x509check/README.md): Monitor certificate
expiration time.
-- [Whois domain expiry](https://learn.netdata.cloud/docs/agent/collectors/go.d.plugin/modules/whoisquery/): Checks the
+- [Whois domain expiry](https://github.com/netdata/go.d.plugin/blob/master/modules/whoisquery/README.md): Checks the
remaining time until a given domain is expired.
## System collectors
@@ -332,139 +382,198 @@ The Netdata Agent can collect these system- and hardware-level metrics using a v
### Applications
-- [Fail2ban](/collectors/python.d.plugin/fail2ban/README.md): Parses configuration files to detect all jails, then
+- [Fail2ban](https://github.com/netdata/netdata/blob/master/collectors/python.d.plugin/fail2ban/README.md): Parses
+ configuration files to detect all jails, then
uses log files to report ban rates and volume of banned IPs.
-- [Monit](/collectors/python.d.plugin/monit/README.md): Monitor statuses of targets (service-checks) using the XML
+- [Monit](https://github.com/netdata/netdata/blob/master/collectors/python.d.plugin/monit/README.md): Monitor statuses
+ of targets (service-checks) using the XML
stats interface.
- [WMI (Windows Management Instrumentation)
- exporter](https://learn.netdata.cloud/docs/agent/collectors/go.d.plugin/modules/wmi/): Collect CPU, memory,
+ exporter](https://github.com/netdata/go.d.plugin/blob/master/modules/wmi/README.md): Collect CPU, memory,
network, disk, OS, system, and log-in metrics scraping `wmi_exporter`.
### Disks and filesystems
-- [BCACHE](/collectors/proc.plugin/README.md): Monitor BCACHE statistics with the the `proc.plugin` collector.
-- [Block devices](/collectors/proc.plugin/README.md): Gather metrics about the health and performance of block
+- [BCACHE](https://github.com/netdata/netdata/blob/master/collectors/proc.plugin/README.md): Monitor BCACHE statistics
+ with the the `proc.plugin` collector.
+- [Block devices](https://github.com/netdata/netdata/blob/master/collectors/proc.plugin/README.md): Gather metrics about
+ the health and performance of block
devices using the the `proc.plugin` collector.
-- [Btrfs](/collectors/proc.plugin/README.md): Monitors Btrfs filesystems with the the `proc.plugin` collector.
-- [Device mapper](/collectors/proc.plugin/README.md): Gather metrics about the Linux device mapper with the proc
+- [Btrfs](https://github.com/netdata/netdata/blob/master/collectors/proc.plugin/README.md): Monitors Btrfs filesystems
+ with the the `proc.plugin` collector.
+- [Device mapper](https://github.com/netdata/netdata/blob/master/collectors/proc.plugin/README.md): Gather metrics about
+ the Linux device mapper with the proc
collector.
-- [Disk space](/collectors/diskspace.plugin/README.md): Collect disk space usage metrics on Linux mount points.
-- [Clock synchronization](/collectors/timex.plugin/README.md): Collect the system clock synchronization status on Linux.
-- [Files and directories](https://learn.netdata.cloud/docs/agent/collectors/go.d.plugin/modules/filecheck): Gather
+- [Disk space](https://github.com/netdata/netdata/blob/master/collectors/diskspace.plugin/README.md): Collect disk space
+ usage metrics on Linux mount points.
+- [Clock synchronization](https://github.com/netdata/netdata/blob/master/collectors/timex.plugin/README.md): Collect the
+ system clock synchronization status on Linux.
+- [Files and directories](https://github.com/netdata/go.d.plugin/blob/master/modules/filecheck/README.md): Gather
metrics about the existence, modification time, and size of files or directories.
-- [ioping.plugin](/collectors/ioping.plugin/README.md): Measure disk read/write latency.
-- [NFS file servers and clients](/collectors/proc.plugin/README.md): Gather operations, utilization, and space usage
+- [ioping.plugin](https://github.com/netdata/netdata/blob/master/collectors/ioping.plugin/README.md): Measure disk
+ read/write latency.
+- [NFS file servers and clients](https://github.com/netdata/netdata/blob/master/collectors/proc.plugin/README.md):
+ Gather operations, utilization, and space usage
using the the `proc.plugin` collector.
-- [RAID arrays](/collectors/proc.plugin/README.md): Collect health, disk status, operation status, and more with the
+- [RAID arrays](https://github.com/netdata/netdata/blob/master/collectors/proc.plugin/README.md): Collect health, disk
+ status, operation status, and more with the
the `proc.plugin` collector.
-- [Veritas Volume Manager](/collectors/proc.plugin/README.md): Gather metrics about the Veritas Volume Manager (VVM).
-- [ZFS](/collectors/proc.plugin/README.md): Monitor bandwidth and utilization of ZFS disks/partitions using the proc
+- [Veritas Volume Manager](https://github.com/netdata/netdata/blob/master/collectors/proc.plugin/README.md): Gather
+ metrics about the Veritas Volume Manager (VVM).
+- [ZFS](https://github.com/netdata/netdata/blob/master/collectors/proc.plugin/README.md): Monitor bandwidth and
+ utilization of ZFS disks/partitions using the proc
collector.
### eBPF
-- [Files](/collectors/ebpf.plugin/README.md): Provides information about how often a system calls kernel
+- [Files](https://github.com/netdata/netdata/blob/master/collectors/ebpf.plugin/README.md): Provides information about
+ how often a system calls kernel
functions related to file descriptors using the eBPF collector.
-- [Virtual file system (VFS)](/collectors/ebpf.plugin/README.md): Monitor IO, errors, deleted objects, and
+- [Virtual file system (VFS)](https://github.com/netdata/netdata/blob/master/collectors/ebpf.plugin/README.md): Monitor
+ IO, errors, deleted objects, and
more for kernel virtual file systems (VFS) using the eBPF collector.
-- [Processes](/collectors/ebpf.plugin/README.md): Monitor threads, task exits, and errors using the eBPF collector.
+- [Processes](https://github.com/netdata/netdata/blob/master/collectors/ebpf.plugin/README.md): Monitor threads, task
+ exits, and errors using the eBPF collector.
### Hardware
-- [Adaptec RAID](/collectors/python.d.plugin/adaptec_raid/README.md): Monitor logical and physical devices health
+- [Adaptec RAID](https://github.com/netdata/netdata/blob/master/collectors/python.d.plugin/adaptec_raid/README.md):
+ Monitor logical and physical devices health
metrics using the `arcconf` tool.
-- [CUPS](/collectors/cups.plugin/README.md): Monitor CUPS.
-- [FreeIPMI](/collectors/freeipmi.plugin/README.md): Uses `libipmimonitoring-dev` or `libipmimonitoring-devel` to
+- [CUPS](https://github.com/netdata/netdata/blob/master/collectors/cups.plugin/README.md): Monitor CUPS.
+- [FreeIPMI](https://github.com/netdata/netdata/blob/master/collectors/freeipmi.plugin/README.md):
+ Uses `libipmimonitoring-dev` or `libipmimonitoring-devel` to
monitor the number of sensors, temperatures, voltages, currents, and more.
-- [Hard drive temperature](/collectors/python.d.plugin/hddtemp/README.md): Monitor the temperature of storage
+- [Hard drive temperature](https://github.com/netdata/netdata/blob/master/collectors/python.d.plugin/hddtemp/README.md):
+ Monitor the temperature of storage
devices.
-- [HP Smart Storage Arrays](/collectors/python.d.plugin/hpssa/README.md): Monitor controller, cache module, logical
+- [HP Smart Storage Arrays](https://github.com/netdata/netdata/blob/master/collectors/python.d.plugin/hpssa/README.md):
+ Monitor controller, cache module, logical
and physical drive state, and temperature using the `ssacli` tool.
-- [MegaRAID controllers](/collectors/python.d.plugin/megacli/README.md): Collect adapter, physical drives, and
+- [MegaRAID controllers](https://github.com/netdata/netdata/blob/master/collectors/python.d.plugin/megacli/README.md):
+ Collect adapter, physical drives, and
battery stats using the `megacli` tool.
-- [NVIDIA GPU](/collectors/python.d.plugin/nvidia_smi/README.md): Monitor performance metrics (memory usage, fan
+- [NVIDIA GPU](https://github.com/netdata/netdata/blob/master/collectors/python.d.plugin/nvidia_smi/README.md): Monitor
+ performance metrics (memory usage, fan
speed, pcie bandwidth utilization, temperature, and more) using the `nvidia-smi` tool.
-- [Sensors](/collectors/python.d.plugin/sensors/README.md): Reads system sensors information (temperature, voltage,
+- [Sensors](https://github.com/netdata/netdata/blob/master/collectors/python.d.plugin/sensors/README.md): Reads system
+ sensors information (temperature, voltage,
electric current, power, and more) from `/sys/devices/`.
-- [S.M.A.R.T](/collectors/python.d.plugin/smartd_log/README.md): Reads SMART Disk Monitoring daemon logs.
+- [S.M.A.R.T](https://github.com/netdata/netdata/blob/master/collectors/python.d.plugin/smartd_log/README.md): Reads
+ SMART Disk Monitoring daemon logs.
### Memory
-- [Available memory](/collectors/proc.plugin/README.md): Tracks changes in available RAM using the the `proc.plugin`
+- [Available memory](https://github.com/netdata/netdata/blob/master/collectors/proc.plugin/README.md): Tracks changes in
+ available RAM using the the `proc.plugin`
collector.
-- [Committed memory](/collectors/proc.plugin/README.md): Monitor committed memory using the `proc.plugin` collector.
-- [Huge pages](/collectors/proc.plugin/README.md): Gather metrics about huge pages in Linux and FreeBSD with the
+- [Committed memory](https://github.com/netdata/netdata/blob/master/collectors/proc.plugin/README.md): Monitor committed
+ memory using the `proc.plugin` collector.
+- [Huge pages](https://github.com/netdata/netdata/blob/master/collectors/proc.plugin/README.md): Gather metrics about
+ huge pages in Linux and FreeBSD with the
`proc.plugin` collector.
-- [KSM](/collectors/proc.plugin/README.md): Measure the amount of merging, savings, and effectiveness using the
+- [KSM](https://github.com/netdata/netdata/blob/master/collectors/proc.plugin/README.md): Measure the amount of merging,
+ savings, and effectiveness using the
`proc.plugin` collector.
-- [Numa](/collectors/proc.plugin/README.md): Gather metrics on the number of non-uniform memory access (NUMA) events
+- [Numa](https://github.com/netdata/netdata/blob/master/collectors/proc.plugin/README.md): Gather metrics on the number
+ of non-uniform memory access (NUMA) events
every second using the `proc.plugin` collector.
-- [Page faults](/collectors/proc.plugin/README.md): Collect the number of memory page faults per second using the
+- [Page faults](https://github.com/netdata/netdata/blob/master/collectors/proc.plugin/README.md): Collect the number of
+ memory page faults per second using the
`proc.plugin` collector.
-- [RAM](/collectors/proc.plugin/README.md): Collect metrics on system RAM, available RAM, and more using the
+- [RAM](https://github.com/netdata/netdata/blob/master/collectors/proc.plugin/README.md): Collect metrics on system RAM,
+ available RAM, and more using the
`proc.plugin` collector.
-- [SLAB](/collectors/slabinfo.plugin/README.md): Collect kernel SLAB details on Linux systems.
-- [swap](/collectors/proc.plugin/README.md): Monitor the amount of free and used swap at every second using the
+- [SLAB](https://github.com/netdata/netdata/blob/master/collectors/slabinfo.plugin/README.md): Collect kernel SLAB
+ details on Linux systems.
+- [swap](https://github.com/netdata/netdata/blob/master/collectors/proc.plugin/README.md): Monitor the amount of free
+ and used swap at every second using the
`proc.plugin` collector.
-- [Writeback memory](/collectors/proc.plugin/README.md): Collect how much memory is actively being written to disk at
+- [Writeback memory](https://github.com/netdata/netdata/blob/master/collectors/proc.plugin/README.md): Collect how much
+ memory is actively being written to disk at
every second using the `proc.plugin` collector.
### Networks
-- [Access points](/collectors/charts.d.plugin/ap/README.md): Visualizes data related to access points.
-- [fping.plugin](fping.plugin/README.md): Measure network latency, jitter and packet loss between the monitored node
+- [Access points](https://github.com/netdata/netdata/blob/master/collectors/charts.d.plugin/ap/README.md): Visualizes
+ data related to access points.
+- [Ping](https://github.com/netdata/go.d.plugin/blob/master/modules/ping/README.md): Measure network latency, jitter and
+ packet loss between the monitored node
and any number of remote network end points.
-- [Netfilter](/collectors/nfacct.plugin/README.md): Collect netfilter firewall, connection tracker, and accounting
+- [Netfilter](https://github.com/netdata/netdata/blob/master/collectors/nfacct.plugin/README.md): Collect netfilter
+ firewall, connection tracker, and accounting
metrics using `libmnl` and `libnetfilter_acct`.
-- [Network stack](/collectors/proc.plugin/README.md): Monitor the networking stack for errors, TCP connection aborts,
+- [Network stack](https://github.com/netdata/netdata/blob/master/collectors/proc.plugin/README.md): Monitor the
+ networking stack for errors, TCP connection aborts,
bandwidth, and more.
-- [Network QoS](/collectors/tc.plugin/README.md): Collect traffic QoS metrics (`tc`) of Linux network interfaces.
-- [SYNPROXY](/collectors/proc.plugin/README.md): Monitor entries uses, SYN packets received, TCP cookies, and more.
+- [Network QoS](https://github.com/netdata/netdata/blob/master/collectors/tc.plugin/README.md): Collect traffic QoS
+ metrics (`tc`) of Linux network interfaces.
+- [SYNPROXY](https://github.com/netdata/netdata/blob/master/collectors/proc.plugin/README.md): Monitor entries uses, SYN
+ packets received, TCP cookies, and more.
### Operating systems
-- [freebsd.plugin](freebsd.plugin/README.md): Collect resource usage and performance data on FreeBSD systems.
-- [macOS](/collectors/macos.plugin/README.md): Collect resource usage and performance data on macOS systems.
+- [freebsd.plugin](https://github.com/netdata/netdata/blob/master/collectors/freebsd.plugin/README.md): Collect resource
+ usage and performance data on FreeBSD systems.
+- [macOS](https://github.com/netdata/netdata/blob/master/collectors/macos.plugin/README.md): Collect resource usage and
+ performance data on macOS systems.
### Processes
-- [Applications](/collectors/apps.plugin/README.md): Gather CPU, disk, memory, network, eBPF, and other metrics per
+- [Applications](https://github.com/netdata/netdata/blob/master/collectors/apps.plugin/README.md): Gather CPU, disk,
+ memory, network, eBPF, and other metrics per
application using the `apps.plugin` collector.
-- [systemd](/collectors/cgroups.plugin/README.md): Monitor the CPU and memory usage of systemd services using the
+- [systemd](https://github.com/netdata/netdata/blob/master/collectors/cgroups.plugin/README.md): Monitor the CPU and
+ memory usage of systemd services using the
`cgroups.plugin` collector.
-- [systemd unit states](https://learn.netdata.cloud/docs/agent/collectors/go.d.plugin/modules/systemdunits): See the
+- [systemd unit states](https://github.com/netdata/go.d.plugin/blob/master/modules/systemdunits/README.md): See the
state (active, inactive, activating, deactivating, failed) of various systemd unit types.
-- [System processes](/collectors/proc.plugin/README.md): Collect metrics on system load and total processes running
+- [System processes](https://github.com/netdata/netdata/blob/master/collectors/proc.plugin/README.md): Collect metrics
+ on system load and total processes running
using `/proc/loadavg` and the `proc.plugin` collector.
-- [Uptime](/collectors/proc.plugin/README.md): Monitor the uptime of a system using the `proc.plugin` collector.
+- [Uptime](https://github.com/netdata/netdata/blob/master/collectors/proc.plugin/README.md): Monitor the uptime of a
+ system using the `proc.plugin` collector.
### Resources
-- [CPU frequency](/collectors/proc.plugin/README.md): Monitor CPU frequency, as set by the `cpufreq` kernel module,
+- [CPU frequency](https://github.com/netdata/netdata/blob/master/collectors/proc.plugin/README.md): Monitor CPU
+ frequency, as set by the `cpufreq` kernel module,
using the `proc.plugin` collector.
-- [CPU idle](/collectors/proc.plugin/README.md): Measure CPU idle every second using the `proc.plugin` collector.
-- [CPU performance](/collectors/perf.plugin/README.md): Collect CPU performance metrics using performance monitoring
+- [CPU idle](https://github.com/netdata/netdata/blob/master/collectors/proc.plugin/README.md): Measure CPU idle every
+ second using the `proc.plugin` collector.
+- [CPU performance](https://github.com/netdata/netdata/blob/master/collectors/perf.plugin/README.md): Collect CPU
+ performance metrics using performance monitoring
units (PMU).
-- [CPU throttling](/collectors/proc.plugin/README.md): Gather metrics about thermal throttling using the `/proc/stat`
+- [CPU throttling](https://github.com/netdata/netdata/blob/master/collectors/proc.plugin/README.md): Gather metrics
+ about thermal throttling using the `/proc/stat`
module and the `proc.plugin` collector.
-- [CPU utilization](/collectors/proc.plugin/README.md): Capture CPU utilization, both system-wide and per-core, using
+- [CPU utilization](https://github.com/netdata/netdata/blob/master/collectors/proc.plugin/README.md): Capture CPU
+ utilization, both system-wide and per-core, using
the `/proc/stat` module and the `proc.plugin` collector.
-- [Entropy](/collectors/proc.plugin/README.md): Monitor the available entropy on a system using the `proc.plugin`
+- [Entropy](https://github.com/netdata/netdata/blob/master/collectors/proc.plugin/README.md): Monitor the available
+ entropy on a system using the `proc.plugin`
collector.
-- [Interprocess Communication (IPC)](/collectors/proc.plugin/README.md): Monitor IPC semaphores and shared memory
+- [Interprocess Communication (IPC)](https://github.com/netdata/netdata/blob/master/collectors/proc.plugin/README.md):
+ Monitor IPC semaphores and shared memory
using the `proc.plugin` collector.
-- [Interrupts](/collectors/proc.plugin/README.md): Monitor interrupts per second using the `proc.plugin` collector.
-- [IdleJitter](/collectors/idlejitter.plugin/README.md): Measure CPU latency and jitter on all operating systems.
-- [SoftIRQs](/collectors/proc.plugin/README.md): Collect metrics on SoftIRQs, both system-wide and per-core, using the
+- [Interrupts](https://github.com/netdata/netdata/blob/master/collectors/proc.plugin/README.md): Monitor interrupts per
+ second using the `proc.plugin` collector.
+- [IdleJitter](https://github.com/netdata/netdata/blob/master/collectors/idlejitter.plugin/README.md): Measure CPU
+ latency and jitter on all operating systems.
+- [SoftIRQs](https://github.com/netdata/netdata/blob/master/collectors/proc.plugin/README.md): Collect metrics on
+ SoftIRQs, both system-wide and per-core, using the
`proc.plugin` collector.
-- [SoftNet](/collectors/proc.plugin/README.md): Capture SoftNet events per second, both system-wide and per-core,
+- [SoftNet](https://github.com/netdata/netdata/blob/master/collectors/proc.plugin/README.md): Capture SoftNet events per
+ second, both system-wide and per-core,
using the `proc.plugin` collector.
### Users
-- [systemd-logind](/collectors/python.d.plugin/logind/README.md): Monitor active sessions, users, and seats tracked
+- [systemd-logind](https://github.com/netdata/go.d.plugin/blob/master/modules/logind/README.md): Monitor active
+ sessions, users, and seats tracked
by `systemd-logind` or `elogind`.
-- [User/group usage](/collectors/apps.plugin/README.md): Gather CPU, disk, memory, network, and other metrics per user
+- [User/group usage](https://github.com/netdata/netdata/blob/master/collectors/apps.plugin/README.md): Gather CPU, disk,
+ memory, network, and other metrics per user
and user group using the `apps.plugin` collector.
## Netdata collectors
@@ -473,13 +582,18 @@ These collectors are recursive in nature, in that they monitor some function of
collectors are described only in code and associated charts in Netdata dashboards.
- [ACLK (code only)](https://github.com/netdata/netdata/blob/master/aclk/legacy/aclk_stats.c): View whether a Netdata
- Agent is connected to Netdata Cloud via the [ACLK](/aclk/README.md), the volume of queries, process times, and more.
-- [Alarms](https://learn.netdata.cloud/docs/agent/collectors/python.d.plugin/alarms): This collector creates an
+ Agent is connected to Netdata Cloud via the [ACLK](https://github.com/netdata/netdata/blob/master/aclk/README.md), the
+ volume of queries, process times, and more.
+- [Alarms](https://github.com/netdata/netdata/blob/master/collectors/python.d.plugin/alarms/README.md): This collector
+ creates an
**Alarms** menu with one line plot showing the alarm states of a Netdata Agent over time.
-- [Anomalies](https://learn.netdata.cloud/docs/agent/collectors/python.d.plugin/anomalies): This collector uses the
+- [Anomalies](https://github.com/netdata/netdata/blob/master/collectors/python.d.plugin/anomalies/README.md): This
+ collector uses the
Python PyOD library to perform unsupervised anomaly detection on your Netdata charts and/or dimensions.
- [Exporting (code only)](https://github.com/netdata/netdata/blob/master/exporting/send_internal_metrics.c): Gather
- metrics on CPU utilization for the [exporting engine](/exporting/README.md), and specific metrics for each enabled
+ metrics on CPU utilization for
+ the [exporting engine](https://github.com/netdata/netdata/blob/master/exporting/README.md), and specific metrics for
+ each enabled
exporting connector.
- [Global statistics (code only)](https://github.com/netdata/netdata/blob/master/daemon/global_statistics.c): See
metrics on the CPU utilization, network traffic, volume of web clients, API responses, database engine usage, and
@@ -493,14 +607,54 @@ If you're interested in developing a new collector that you'd like to contribute
the `go.d.plugin`.
- [go.d.plugin](https://github.com/netdata/go.d.plugin): An orchestrator for data collection modules written in `go`.
-- [python.d.plugin](python.d.plugin/README.md): An orchestrator for data collection modules written in `python` v2/v3.
-- [charts.d.plugin](charts.d.plugin/README.md): An orchestrator for data collection modules written in `bash` v4+.
+- [python.d.plugin](https://github.com/netdata/netdata/blob/master/collectors/python.d.plugin/README.md): An
+ orchestrator for data collection modules written in `python` v2/v3.
+- [charts.d.plugin](https://github.com/netdata/netdata/blob/master/collectors/charts.d.plugin/README.md): An
+ orchestrator for data collection modules written in `bash` v4+.
## Third-party collectors
These collectors are developed and maintained by third parties and, unlike the other collectors, are not installed by
default. To use a third-party collector, visit their GitHub/documentation page and follow their installation procedures.
+<details>
+<summary>Typical third party Python collector installation instructions</summary>
+
+In general the below steps should be sufficient to use a third party collector.
+
+1. Download collector code file
+ into [folder expected by Netdata](https://github.com/netdata/netdata/blob/master/collectors/plugins.d/README.md#environment-variables).
+2. Download default collector configuration file
+ into [folder expected by Netdata](https://github.com/netdata/netdata/blob/master/collectors/plugins.d/README.md#environment-variables).
+3. [Edit configuration file](https://github.com/netdata/netdata/blob/master/docs/collect/enable-configure#configure-a-collector)
+ from step 2 if required.
+4. [Enable collector](https://github.com/netdata/netdata/blob/master/docs/collect/enable-configure#enable-a-collector-or-its-orchestrator).
+5. [Restart Netdata](https://github.com/netdata/netdata/blob/master/docs/configure/start-stop-restart.md)
+
+For example below are the steps to enable
+the [Python ClickHouse collector](https://github.com/netdata/community/tree/main/collectors/python.d.plugin/clickhouse).
+
+```bash
+# download python collector script to /usr/libexec/netdata/python.d/
+$ sudo wget https://raw.githubusercontent.com/netdata/community/main/collectors/python.d.plugin/clickhouse/clickhouse.chart.py -O /usr/libexec/netdata/python.d/clickhouse.chart.py
+
+# (optional) download default .conf to /etc/netdata/python.d/
+$ sudo wget https://raw.githubusercontent.com/netdata/community/main/collectors/python.d.plugin/clickhouse/clickhouse.conf -O /etc/netdata/python.d/clickhouse.conf
+
+# enable collector by adding line a new line with "clickhouse: yes" to /etc/netdata/python.d.conf file
+# this will append to the file if it already exists or create it if not
+$ sudo echo "clickhouse: yes" >> /etc/netdata/python.d.conf
+
+# (optional) edit clickhouse.conf if needed
+$ sudo vi /etc/netdata/python.d/clickhouse.conf
+
+# restart netdata
+# see docs for more information: https://learn.netdata.cloud/docs/configure/start-stop-restart
+$ sudo systemctl restart netdata
+```
+
+</details>
+
- [CyberPower UPS](https://github.com/HawtDogFlvrWtr/netdata_cyberpwrups_plugin): Polls CyberPower UPS data using
PowerPanel® Personal Linux.
- [Logged-in users](https://github.com/veksh/netdata-numsessions): Collect the number of currently logged-on users.
@@ -511,8 +665,12 @@ default. To use a third-party collector, visit their GitHub/documentation page a
- [Teamspeak 3](https://github.com/coraxx/netdata_ts3_plugin): Pulls active users and bandwidth from TeamSpeak 3
servers.
- [SSH](https://github.com/Yaser-Amiri/netdata-ssh-module): Monitor failed authentication requests of an SSH server.
+- [ClickHouse](https://github.com/netdata/community/tree/main/collectors/python.d.plugin/clickhouse):
+ Monitor [ClickHouse](https://clickhouse.com/) database.
## Etc
-- [charts.d example](charts.d.plugin/example/README.md): An example `charts.d` collector.
-- [python.d example](python.d.plugin/example/README.md): An example `python.d` collector.
+- [charts.d example](https://github.com/netdata/netdata/blob/master/collectors/charts.d.plugin/example/README.md): An
+ example `charts.d` collector.
+- [python.d example](https://github.com/netdata/netdata/blob/master/collectors/python.d.plugin/example/README.md): An
+ example `python.d` collector.
diff --git a/collectors/Makefile.am b/collectors/Makefile.am
index 9f8bf528..24e4c3f0 100644
--- a/collectors/Makefile.am
+++ b/collectors/Makefile.am
@@ -10,7 +10,6 @@ SUBDIRS = \
cups.plugin \
diskspace.plugin \
timex.plugin \
- fping.plugin \
ioping.plugin \
freebsd.plugin \
freeipmi.plugin \
diff --git a/collectors/README.md b/collectors/README.md
index de46a72a..91a4eeb4 100644
--- a/collectors/README.md
+++ b/collectors/README.md
@@ -1,48 +1,54 @@
<!--
title: "Collecting metrics"
-custom_edit_url: https://github.com/netdata/netdata/edit/master/collectors/README.md
+custom_edit_url: "https://github.com/netdata/netdata/edit/master/collectors/README.md"
id: "collectors-ref"
+sidebar_label: "Plugins Reference"
+learn_status: "Published"
+learn_topic_type: "Tasks"
+learn_rel_path: "References/Collectors"
-->
# Collecting metrics
Netdata can collect metrics from hundreds of different sources, be they internal data created by the system itself, or
-external data created by services or applications. To see _all_ of the sources Netdata collects from, view our [list of
-supported collectors](/collectors/COLLECTORS.md).
+external data created by services or applications. To see _all_ of the sources Netdata collects from, view our
+[list of supported collectors](https://github.com/netdata/netdata/blob/master/collectors/COLLECTORS.md).
There are two essential points to understand about how collecting metrics works in Netdata:
-- All collectors are **installed by default** with every installation of Netdata. You do not need to install
- collectors manually to collect metrics from new sources.
-- Upon startup, Netdata will **auto-detect** any application or service that has a
- [collector](/collectors/COLLECTORS.md), as long as both the collector and the app/service are configured correctly.
+- All collectors are **installed by default** with every installation of Netdata. You do not need to install
+ collectors manually to collect metrics from new sources.
+- Upon startup, Netdata will **auto-detect** any application or service that has a
+ [collector](https://github.com/netdata/netdata/blob/master/collectors/COLLECTORS.md), as long as both the collector
+ and the app/service are configured correctly.
Most users will want to enable a new Netdata collector for their app/service. For those details, see
-our [collectors' configuration reference](/collectors/REFERENCE.md).
+our [collectors' configuration reference](https://github.com/netdata/netdata/blob/master/collectors/REFERENCE.md).
## Take your next steps with collectors
-[Supported collectors list](/collectors/COLLECTORS.md)
+[Supported collectors list](https://github.com/netdata/netdata/blob/master/collectors/COLLECTORS.md)
-[Collectors configuration reference](/collectors/REFERENCE.md)
+[Collectors configuration reference](https://github.com/netdata/netdata/blob/master/collectors/REFERENCE.md)
## Guides
-[Monitor Nginx or Apache web server log files with Netdata](/docs/guides/collect-apache-nginx-web-logs.md)
+[Monitor Nginx or Apache web server log files with Netdata](https://github.com/netdata/netdata/blob/master/docs/guides/collect-apache-nginx-web-logs.md)
-[Monitor CockroachDB metrics with Netdata](/docs/guides/monitor-cockroachdb.md)
+[Monitor CockroachDB metrics with Netdata](https://github.com/netdata/netdata/blob/master/docs/guides/monitor-cockroachdb.md)
-[Monitor Unbound DNS servers with Netdata](/docs/guides/collect-unbound-metrics.md)
+[Monitor Unbound DNS servers with Netdata](https://github.com/netdata/netdata/blob/master/docs/guides/collect-unbound-metrics.md)
-[Monitor a Hadoop cluster with Netdata](/docs/guides/monitor-hadoop-cluster.md)
+[Monitor a Hadoop cluster with Netdata](https://github.com/netdata/netdata/blob/master/docs/guides/monitor-hadoop-cluster.md)
## Related features
-**[Dashboards](/web/README.md)**: Visualize your newly-collect metrics in real-time using Netdata's [built-in
-dashboard](/web/gui/README.md).
+**[Dashboards](https://github.com/netdata/netdata/blob/master/web/README.md)**: Visualize your newly-collect metrics in
+real-time using Netdata's [built-in dashboard](https://github.com/netdata/netdata/blob/master/web/gui/README.md).
-**[Exporting](/exporting/README.md)**: Extend our built-in [database engine](/database/engine/README.md), which supports
-long-term metrics storage, by archiving metrics to external databases like Graphite, Prometheus, MongoDB, TimescaleDB, and more.
-It can export metrics to multiple databases simultaneously.
+**[Exporting](https://github.com/netdata/netdata/blob/master/exporting/README.md)**: Extend our
+built-in [database engine](https://github.com/netdata/netdata/blob/master/database/engine/README.md), which supports
+long-term metrics storage, by archiving metrics to external databases like Graphite, Prometheus, MongoDB, TimescaleDB,
+and more. It can export metrics to multiple databases simultaneously.
diff --git a/collectors/REFERENCE.md b/collectors/REFERENCE.md
index 939b189e..270dded2 100644
--- a/collectors/REFERENCE.md
+++ b/collectors/REFERENCE.md
@@ -1,6 +1,10 @@
<!--
title: "Collectors configuration reference"
-custom_edit_url: https://github.com/netdata/netdata/edit/master/collectors/REFERENCE.md
+custom_edit_url: "https://github.com/netdata/netdata/edit/master/collectors/REFERENCE.md"
+sidebar_label: "Collectors configuration"
+learn_status: "Published"
+learn_topic_type: "Tasks"
+learn_rel_path: "Setup"
-->
# Collectors configuration reference
@@ -19,7 +23,7 @@ independent processes in a variety of programming languages based on their purpo
MySQL database, among many others.
For most users, enabling individual collectors for the application/service you're interested in is far more important
-than knowing which plugin it uses. See our [collectors list](/collectors/COLLECTORS.md) to see whether your favorite app/service has
+than knowing which plugin it uses. See our [collectors list](https://github.com/netdata/netdata/blob/master/collectors/COLLECTORS.md) to see whether your favorite app/service has
a collector, and then read the documentation for that specific collector to figure out how to enable it.
There are three types of plugins:
@@ -31,7 +35,7 @@ There are three types of plugins:
independent processes. They communicate with the daemon via pipes.
- **Plugin orchestrators**, which are external plugins that instead support a number of **modules**. Modules are a
type of collector. We have a few plugin orchestrators available for those who want to develop their own collectors,
- but focus most of our efforts on the [Go plugin](https://learn.netdata.cloud/docs/agent/collectors/go.d.plugin/).
+ but focus most of our efforts on the [Go plugin](https://github.com/netdata/go.d.plugin/blob/master/README.md).
## Enable, configure, and disable modules
@@ -57,7 +61,7 @@ sudo su -s /bin/bash netdata
The next step is based on the collector's orchestrator. You can figure out which orchestrator the collector uses by
uses either
-by viewing the [collectors list](COLLECTORS.md) and referencing the _configuration file_ field. For example, if that
+by viewing the [collectors list](https://github.com/netdata/netdata/blob/master/collectors/COLLECTORS.md) and referencing the _configuration file_ field. For example, if that
field contains `go.d`, that collector uses the Go orchestrator.
```bash
@@ -93,7 +97,6 @@ This section features a list of Netdata's plugins, with a boolean setting to ena
# enable running new plugins = yes
# check for new plugins every = 60
# slabinfo = no
- # fping = yes
# ioping = yes
# python.d = yes
# go.d = yes
@@ -105,7 +108,7 @@ This section features a list of Netdata's plugins, with a boolean setting to ena
By default, most plugins are enabled, so you don't need to enable them explicitly to use their collectors. To enable or
disable any specific plugin, remove the comment (`#`) and change the boolean setting to `yes` or `no`.
-All **external plugins** are managed by [plugins.d](plugins.d/README.md), which provides additional management options.
+All **external plugins** are managed by [plugins.d](https://github.com/netdata/netdata/blob/master/collectors/plugins.d/README.md), which provides additional management options.
## Internal plugins
@@ -162,9 +165,10 @@ through this, is to examine what other similar plugins do.
## External Plugins
-**External plugins** use the API and are managed by [plugins.d](plugins.d/README.md).
+**External plugins** use the API and are managed
+by [plugins.d](https://github.com/netdata/netdata/blob/master/collectors/plugins.d/README.md).
## Write a custom collector
-You can add custom collectors by following the [external plugins documentation](/collectors/plugins.d/README.md).
+You can add custom collectors by following the [external plugins documentation](https://github.com/netdata/netdata/blob/master/collectors/plugins.d/README.md).
diff --git a/collectors/all.h b/collectors/all.h
index 85a7ac8b..74fdde3f 100644
--- a/collectors/all.h
+++ b/collectors/all.h
@@ -363,5 +363,30 @@
#define NETDATA_CHART_PRIO_NETDATA_TIMEX 132030
#define NETDATA_CHART_PRIO_NETDATA_TC_TIME 1000100
+// NETDATA ML CHARTS
+
+// [ml] charts
+#define ML_CHART_PRIO_DIMENSIONS 39181
+#define ML_CHART_PRIO_ANOMALY_RATE 39182
+#define ML_CHART_PRIO_DETECTOR_EVENTS 39183
+
+// [netdata.ml] charts
+#define NETDATA_ML_CHART_PRIO_MACHINE_LEARNING_STATUS 890001
+#define NETDATA_ML_CHART_PRIO_METRIC_TYPES 890002
+#define NETDATA_ML_CHART_PRIO_TRAINING_STATUS 890003
+
+#define NETDATA_ML_CHART_PRIO_PREDICTION_USAGE 890004
+#define NETDATA_ML_CHART_PRIO_TRAINING_USAGE 890005
+
+#define NETDATA_ML_CHART_PRIO_QUEUE_STATS 890006
+#define NETDATA_ML_CHART_PRIO_TRAINING_TIME_STATS 890007
+#define NETDATA_ML_CHART_PRIO_TRAINING_RESULTS 890008
+
+#define NETDATA_ML_CHART_FAMILY "machine learning"
+#define NETDATA_ML_PLUGIN "ml.plugin"
+#define NETDATA_ML_MODULE_TRAINING "training"
+#define NETDATA_ML_MODULE_DETECTION "detection"
+#define NETDATA_ML_MODULE_PREDICTION "prediction"
+
#endif //NETDATA_ALL_H
diff --git a/collectors/apps.plugin/README.md b/collectors/apps.plugin/README.md
index 150889d4..ac0d349a 100644
--- a/collectors/apps.plugin/README.md
+++ b/collectors/apps.plugin/README.md
@@ -1,7 +1,10 @@
<!--
-title: "apps.plugin"
-sidebar_label: "Application monitoring (apps.plugin)"
-custom_edit_url: https://github.com/netdata/netdata/edit/master/collectors/apps.plugin/README.md
+title: "Application monitoring (apps.plugin)"
+sidebar_label: "Application monitoring "
+custom_edit_url: "https://github.com/netdata/netdata/edit/master/collectors/apps.plugin/README.md"
+learn_status: "Published"
+learn_topic_type: "References"
+learn_rel_path: "References/Collectors references/System metrics"
-->
# apps.plugin
@@ -63,8 +66,8 @@ Each of these sections provides the same number of charts:
- Network
- Sockets open (`apps.sockets`)
-In addition, if the [eBPF collector](/collectors/ebpf.plugin/README.md) is running, your dashboard will also show an
-additional [list of charts](/collectors/ebpf.plugin/README.md#integration-with-appsplugin) using low-level Linux
+In addition, if the [eBPF collector](https://github.com/netdata/netdata/blob/master/collectors/ebpf.plugin/README.md) is running, your dashboard will also show an
+additional [list of charts](https://github.com/netdata/netdata/blob/master/collectors/ebpf.plugin/README.md#integration-with-appsplugin) using low-level Linux
metrics.
The above are reported:
@@ -160,10 +163,10 @@ There are a few command line options you can pass to `apps.plugin`. The list of
### Integration with eBPF
If you don't see charts under the **eBPF syscall** or **eBPF net** sections, you should edit your
-[`ebpf.d.conf`](/collectors/ebpf.plugin/README.md#configure-the-ebpf-collector) file to ensure the eBPF program is enabled.
+[`ebpf.d.conf`](https://github.com/netdata/netdata/blob/master/collectors/ebpf.plugin/README.md#configure-the-ebpf-collector) file to ensure the eBPF program is enabled.
Also see our [guide on troubleshooting apps with eBPF
-metrics](/docs/guides/troubleshoot/monitor-debug-applications-ebpf.md) for ideas on how to interpret these charts in a
+metrics](https://github.com/netdata/netdata/blob/master/docs/guides/troubleshoot/monitor-debug-applications-ebpf.md) for ideas on how to interpret these charts in a
few scenarios.
## Permissions
@@ -234,7 +237,7 @@ Examples below for process group `sql`:
- Open Pipes ![image](https://registry.my-netdata.io/api/v1/badge.svg?chart=apps.pipes&dimensions=sql&value_color=green=0%7Cred)
- Open Sockets ![image](https://registry.my-netdata.io/api/v1/badge.svg?chart=apps.sockets&dimensions=sql&value_color=green%3E=3%7Cred)
-For more information about badges check [Generating Badges](/web/api/badges/README.md)
+For more information about badges check [Generating Badges](https://github.com/netdata/netdata/blob/master/web/api/badges/README.md)
## Comparison with console tools
diff --git a/collectors/apps.plugin/apps_groups.conf b/collectors/apps.plugin/apps_groups.conf
index dd45c5a5..fdb04860 100644
--- a/collectors/apps.plugin/apps_groups.conf
+++ b/collectors/apps.plugin/apps_groups.conf
@@ -170,6 +170,7 @@ google-agent: *google_guest_agent* *google_osconfig_agent*
nvidia-smi: nvidia-smi
htop: htop
watchdog: watchdog
+telegraf: telegraf
# -----------------------------------------------------------------------------
# storage, file systems and file servers
@@ -378,7 +379,7 @@ sidekiq: *sidekiq*
java: java
ipfs: ipfs
-node: node*
+node: node
factorio: factorio
p4: p4*
diff --git a/collectors/apps.plugin/apps_plugin.c b/collectors/apps.plugin/apps_plugin.c
index 89b83332..84506c8e 100644
--- a/collectors/apps.plugin/apps_plugin.c
+++ b/collectors/apps.plugin/apps_plugin.c
@@ -10,8 +10,10 @@
#include "libnetdata/libnetdata.h"
#include "libnetdata/required_dummies.h"
+#define APPS_PLUGIN_PROCESSES_FUNCTION_DESCRIPTION "Detailed information on the currently running processes."
+
#define APPS_PLUGIN_FUNCTIONS() do { \
- fprintf(stdout, PLUGINSD_KEYWORD_FUNCTION " \"processes\" 10 \"Detailed information on the currently running processes on this node\"\n"); \
+ fprintf(stdout, PLUGINSD_KEYWORD_FUNCTION " \"processes\" 10 \"%s\"\n", APPS_PLUGIN_PROCESSES_FUNCTION_DESCRIPTION); \
} while(0)
@@ -420,6 +422,7 @@ struct pid_stat {
int sortlist; // higher numbers = top on the process tree
// each process gets a unique number
+ bool matched_by_config;
struct target *target; // app_groups.conf targets
struct target *user_target; // uid based targets
struct target *group_target; // gid based targets
@@ -974,7 +977,7 @@ static inline struct pid_stat *get_pid_entry(pid_t pid) {
init_pid_fds(p, 0, p->fds_size);
p->pid = pid;
- DOUBLE_LINKED_LIST_APPEND_UNSAFE(root_of_pids, p, prev, next);
+ DOUBLE_LINKED_LIST_APPEND_ITEM_UNSAFE(root_of_pids, p, prev, next);
all_pids[pid] = p;
all_pids_count++;
@@ -992,7 +995,7 @@ static inline void del_pid_entry(pid_t pid) {
debug_log("process %d %s exited, deleting it.", pid, p->comm);
- DOUBLE_LINKED_LIST_REMOVE_UNSAFE(root_of_pids, p, prev, next);
+ DOUBLE_LINKED_LIST_REMOVE_ITEM_UNSAFE(root_of_pids, p, prev, next);
// free the filename
#ifndef __FreeBSD__
@@ -1103,6 +1106,7 @@ static inline void assign_target_to_pid(struct pid_stat *p) {
|| (proc_pid_cmdline_is_needed && w->starts_with && w->ends_with && p->cmdline && strstr(p->cmdline, w->compare))
))) {
+ p->matched_by_config = true;
if(w->target) p->target = w->target;
else p->target = w;
@@ -2832,11 +2836,11 @@ static void apply_apps_groups_targets_inheritance(void) {
}
// init goes always to default target
- if(all_pids[INIT_PID])
+ if(all_pids[INIT_PID] && !all_pids[INIT_PID]->matched_by_config)
all_pids[INIT_PID]->target = apps_groups_default_target;
// pid 0 goes always to default target
- if(all_pids[0])
+ if(all_pids[0] && !all_pids[INIT_PID]->matched_by_config)
all_pids[0]->target = apps_groups_default_target;
// give a default target on all top level processes
@@ -3359,7 +3363,7 @@ static void normalize_utilization(struct target *root) {
// here we try to eliminate them by disabling childs processing either for specific dimensions
// or entirely. Of course, either way, we disable it just a single iteration.
- kernel_uint_t max_time = processors * time_factor * RATES_DETAIL;
+ kernel_uint_t max_time = get_system_cpus() * time_factor * RATES_DETAIL;
kernel_uint_t utime = 0, cutime = 0, stime = 0, cstime = 0, gtime = 0, cgtime = 0, minflt = 0, cminflt = 0, majflt = 0, cmajflt = 0;
if(global_utime > max_time) global_utime = max_time;
@@ -3589,6 +3593,13 @@ static void send_collected_data_to_netdata(struct target *root, const char *type
}
send_END();
+ send_BEGIN(type, "rss", dt);
+ for (w = root; w ; w = w->next) {
+ if(unlikely(w->exposed && w->processes))
+ send_SET(w->name, w->status_vmrss);
+ }
+ send_END();
+
send_BEGIN(type, "vmem", dt);
for (w = root; w ; w = w->next) {
if(unlikely(w->exposed && w->processes))
@@ -3728,6 +3739,12 @@ static void send_charts_updates_to_netdata(struct target *root, const char *type
}
APPS_PLUGIN_FUNCTIONS();
+ fprintf(stdout, "CHART %s.rss '' '%s Resident Set Size (w/shared)' 'MiB' mem %s.rss stacked 20004 %d\n", type, title, type, update_every);
+ for (w = root; w ; w = w->next) {
+ if(unlikely(w->exposed))
+ fprintf(stdout, "DIMENSION %s '' absolute %ld %ld\n", w->name, 1L, 1024L);
+ }
+ APPS_PLUGIN_FUNCTIONS();
fprintf(stdout, "CHART %s.vmem '' '%s Virtual Memory Size' 'MiB' mem %s.vmem stacked 20005 %d\n", type, title, type, update_every);
for (w = root; w ; w = w->next) {
@@ -4271,7 +4288,7 @@ static void apps_plugin_function_processes_help(const char *transaction) {
pluginsd_function_result_end_to_stdout();
}
-#define add_table_field(wb, key, name, visible, type, units, max, sort, sortable, sticky, unique_key, pointer_to, summary) do { \
+#define add_table_field(wb, key, name, visible, type, visualization, transform, decimal_points, units, max, sort, sortable, sticky, unique_key, pointer_to, summary, range) do { \
if(fields_added) buffer_strcat(wb, ","); \
buffer_sprintf(wb, "\n \"%s\": {", key); \
buffer_sprintf(wb, "\n \"index\":%d,", fields_added); \
@@ -4281,6 +4298,13 @@ static void apps_plugin_function_processes_help(const char *transaction) {
buffer_sprintf(wb, "\n \"type\":\"%s\",", type); \
if(units) \
buffer_sprintf(wb, "\n \"units\":\"%s\",", (char*)(units)); \
+ buffer_sprintf(wb, "\n \"visualization\":\"%s\",", visualization); \
+ buffer_sprintf(wb, "\n \"value_options\":{"); \
+ if(units) \
+ buffer_sprintf(wb, "\n \"units\":\"%s\",", (char*)(units)); \
+ buffer_sprintf(wb, "\n \"transform\":\"%s\",", transform); \
+ buffer_sprintf(wb, "\n \"decimal_points\":%d", decimal_points); \
+ buffer_sprintf(wb, "\n },"); \
if(!isnan((NETDATA_DOUBLE)(max))) \
buffer_sprintf(wb, "\n \"max\":%f,", (NETDATA_DOUBLE)(max)); \
if(pointer_to) \
@@ -4288,7 +4312,8 @@ static void apps_plugin_function_processes_help(const char *transaction) {
buffer_sprintf(wb, "\n \"sort\":\"%s\",", sort); \
buffer_sprintf(wb, "\n \"sortable\":%s,", (sortable)?"true":"false"); \
buffer_sprintf(wb, "\n \"sticky\":%s,", (sticky)?"true":"false"); \
- buffer_sprintf(wb, "\n \"summary\":\"%s\"", summary); \
+ buffer_sprintf(wb, "\n \"summary\":\"%s\",", summary); \
+ buffer_sprintf(wb, "\n \"filter\":\"%s\"", (range)?"range":"multiselect"); \
buffer_sprintf(wb, "\n }"); \
fields_added++; \
} while(0)
@@ -4380,16 +4405,18 @@ static void apps_plugin_function_processes(const char *transaction, char *functi
unsigned int memory_divisor = 1024;
unsigned int io_divisor = 1024 * RATES_DETAIL;
- BUFFER *wb = buffer_create(PLUGINSD_LINE_MAX);
+ BUFFER *wb = buffer_create(PLUGINSD_LINE_MAX, NULL);
buffer_sprintf(wb,
"{"
"\n \"status\":%d"
",\n \"type\":\"table\""
",\n \"update_every\":%d"
+ ",\n \"help\":\"%s\""
",\n \"data\":["
"\n"
, HTTP_RESP_OK
, update_every
+ , APPS_PLUGIN_PROCESSES_FUNCTION_DESCRIPTION
);
NETDATA_DOUBLE
@@ -4404,7 +4431,7 @@ static void apps_plugin_function_processes(const char *transaction, char *functi
, RSS_max = 0.0
, Shared_max = 0.0
, Swap_max = 0.0
- , MemPcnt_max = 0.0
+ , Memory_max = 0.0
;
unsigned long long
@@ -4513,42 +4540,27 @@ static void apps_plugin_function_processes(const char *transaction, char *functi
// gid
buffer_fast_strcat(wb, ",", 1); buffer_print_llu(wb, p->gid);
- // procs
- add_value_field_llu_with_max(wb, Processes, p->children_count);
-
- // threads
- add_value_field_llu_with_max(wb, Threads, p->num_threads);
-
- // uptime
- add_value_field_llu_with_max(wb, Uptime, p->uptime);
-
- // minor page faults
- add_value_field_llu_with_max(wb, MinFlt, p->minflt / RATES_DETAIL);
- add_value_field_llu_with_max(wb, CMinFlt, p->cminflt / RATES_DETAIL);
- add_value_field_llu_with_max(wb, TMinFlt, (p->minflt + p->cminflt) / RATES_DETAIL);
-
- // major page faults
- add_value_field_llu_with_max(wb, MajFlt, p->majflt / RATES_DETAIL);
- add_value_field_llu_with_max(wb, CMajFlt, p->cmajflt / RATES_DETAIL);
- add_value_field_llu_with_max(wb, TMajFlt, (p->majflt + p->cmajflt) / RATES_DETAIL);
-
// CPU utilization %
+ add_value_field_ndd_with_max(wb, CPU, (NETDATA_DOUBLE)(p->utime + p->stime + p->gtime + p->cutime + p->cstime + p->cgtime) / cpu_divisor);
add_value_field_ndd_with_max(wb, UserCPU, (NETDATA_DOUBLE)(p->utime) / cpu_divisor);
add_value_field_ndd_with_max(wb, SysCPU, (NETDATA_DOUBLE)(p->stime) / cpu_divisor);
add_value_field_ndd_with_max(wb, GuestCPU, (NETDATA_DOUBLE)(p->gtime) / cpu_divisor);
add_value_field_ndd_with_max(wb, CUserCPU, (NETDATA_DOUBLE)(p->cutime) / cpu_divisor);
add_value_field_ndd_with_max(wb, CSysCPU, (NETDATA_DOUBLE)(p->cstime) / cpu_divisor);
add_value_field_ndd_with_max(wb, CGuestCPU, (NETDATA_DOUBLE)(p->cgtime) / cpu_divisor);
- add_value_field_ndd_with_max(wb, CPU, (NETDATA_DOUBLE)(p->utime + p->stime + p->gtime + p->cutime + p->cstime + p->cgtime) / cpu_divisor);
// memory MiB
- add_value_field_ndd_with_max(wb, VMSize, (NETDATA_DOUBLE)p->status_vmsize / memory_divisor);
+ if(MemTotal)
+ add_value_field_ndd_with_max(wb, Memory, (NETDATA_DOUBLE)p->status_vmrss * 100.0 / (NETDATA_DOUBLE)MemTotal);
+
add_value_field_ndd_with_max(wb, RSS, (NETDATA_DOUBLE)p->status_vmrss / memory_divisor);
add_value_field_ndd_with_max(wb, Shared, (NETDATA_DOUBLE)p->status_vmshared / memory_divisor);
+ add_value_field_ndd_with_max(wb, VMSize, (NETDATA_DOUBLE)p->status_vmsize / memory_divisor);
add_value_field_ndd_with_max(wb, Swap, (NETDATA_DOUBLE)p->status_vmswap / memory_divisor);
- if(MemTotal)
- add_value_field_ndd_with_max(wb, MemPcnt, (NETDATA_DOUBLE)p->status_vmrss * 100.0 / (NETDATA_DOUBLE)MemTotal);
+ // Physical I/O
+ add_value_field_llu_with_max(wb, PReads, p->io_storage_bytes_read / io_divisor);
+ add_value_field_llu_with_max(wb, PWrites, p->io_storage_bytes_written / io_divisor);
// Logical I/O
#ifndef __FreeBSD__
@@ -4556,15 +4568,22 @@ static void apps_plugin_function_processes(const char *transaction, char *functi
add_value_field_llu_with_max(wb, LWrites, p->io_logical_bytes_written / io_divisor);
#endif
- // Physical I/O
- add_value_field_llu_with_max(wb, PReads, p->io_storage_bytes_read / io_divisor);
- add_value_field_llu_with_max(wb, PWrites, p->io_storage_bytes_written / io_divisor);
-
// I/O calls
add_value_field_llu_with_max(wb, RCalls, p->io_read_calls / RATES_DETAIL);
add_value_field_llu_with_max(wb, WCalls, p->io_write_calls / RATES_DETAIL);
+ // minor page faults
+ add_value_field_llu_with_max(wb, MinFlt, p->minflt / RATES_DETAIL);
+ add_value_field_llu_with_max(wb, CMinFlt, p->cminflt / RATES_DETAIL);
+ add_value_field_llu_with_max(wb, TMinFlt, (p->minflt + p->cminflt) / RATES_DETAIL);
+
+ // major page faults
+ add_value_field_llu_with_max(wb, MajFlt, p->majflt / RATES_DETAIL);
+ add_value_field_llu_with_max(wb, CMajFlt, p->cmajflt / RATES_DETAIL);
+ add_value_field_llu_with_max(wb, TMajFlt, (p->majflt + p->cmajflt) / RATES_DETAIL);
+
// open file descriptors
+ add_value_field_llu_with_max(wb, FDs, p->openfds.files + p->openfds.pipes + p->openfds.sockets + p->openfds.inotifies + p->openfds.eventfds + p->openfds.timerfds + p->openfds.signalfds + p->openfds.eventpolls + p->openfds.other);
add_value_field_llu_with_max(wb, Files, p->openfds.files);
add_value_field_llu_with_max(wb, Pipes, p->openfds.pipes);
add_value_field_llu_with_max(wb, Sockets, p->openfds.sockets);
@@ -4574,7 +4593,11 @@ static void apps_plugin_function_processes(const char *transaction, char *functi
add_value_field_llu_with_max(wb, SigFDs, p->openfds.signalfds);
add_value_field_llu_with_max(wb, EvPollFDs, p->openfds.eventpolls);
add_value_field_llu_with_max(wb, OtherFDs, p->openfds.other);
- add_value_field_llu_with_max(wb, FDs, p->openfds.files + p->openfds.pipes + p->openfds.sockets + p->openfds.inotifies + p->openfds.eventfds + p->openfds.timerfds + p->openfds.signalfds + p->openfds.eventpolls + p->openfds.other);
+
+ // processes, threads, uptime
+ add_value_field_llu_with_max(wb, Processes, p->children_count);
+ add_value_field_llu_with_max(wb, Threads, p->num_threads);
+ add_value_field_llu_with_max(wb, Uptime, p->uptime);
buffer_fast_strcat(wb, "]", 1);
@@ -4590,75 +4613,77 @@ static void apps_plugin_function_processes(const char *transaction, char *functi
// IMPORTANT!
// THE ORDER SHOULD BE THE SAME WITH THE VALUES!
- add_table_field(wb, "Pid", "Process ID", true, "integer", NULL, NAN, "ascending", true, true, true, NULL, "count_unique");
- add_table_field(wb, "Cmd", "Process Name", true, "string", NULL, NAN, "ascending", true, true, false, NULL, "count_unique");
+ add_table_field(wb, "PID", "Process ID", true, "integer", "value", "number", 0, NULL, NAN, "ascending", true, true, true, NULL, "count_unique", false);
+ add_table_field(wb, "Cmd", "Process Name", true, "string", "value", "none", 0, NULL, NAN, "ascending", true, true, false, NULL, "count_unique", false);
#ifdef NETDATA_DEV_MODE
- add_table_field(wb, "CmdLine", "Command Line", false, "detail-string:Cmd", NULL, NAN, "ascending", true, false, false, NULL, "count_unique");
+ add_table_field(wb, "CmdLine", "Command Line", false, "detail-string:Cmd", "value", "none", 0, NULL, NAN, "ascending", true, false, false, NULL, "count_unique", false);
#endif
- add_table_field(wb, "PPid", "Parent Process ID", false, "integer", NULL, NAN, "ascending", true, false, false, "Pid", "count_unique");
- add_table_field(wb, "Category", "Category (apps_groups.conf)", true, "string", NULL, NAN, "ascending", true, true, false, NULL, "count_unique");
- add_table_field(wb, "User", "User Owner", true, "string", NULL, NAN, "ascending", true, false, false, NULL, "count_unique");
- add_table_field(wb, "Uid", "User ID", false, "integer", NULL, NAN, "ascending", true, false, false, NULL, "count_unique");
- add_table_field(wb, "Group", "Group Owner", false, "string", NULL, NAN, "ascending", true, false, false, NULL, "count_unique");
- add_table_field(wb, "Gid", "Group ID", false, "integer", NULL, NAN, "ascending", true, false, false, NULL, "count_unique");
- add_table_field(wb, "Processes", "Processes", true, "bar-with-integer", "processes", Processes_max, "descending", true, false, false, NULL, "sum");
- add_table_field(wb, "Threads", "Threads", true, "bar-with-integer", "threads", Threads_max, "descending", true, false, false, NULL, "sum");
- add_table_field(wb, "Uptime", "Uptime in seconds", true, "duration", "seconds", Uptime_max, "descending", true, false, false, NULL, "max");
-
- // minor page faults
- add_table_field(wb, "MinFlt", "Minor Page Faults/s", false, "bar", "pgflts/s", MinFlt_max, "descending", true, false, false, NULL, "sum");
- add_table_field(wb, "CMinFlt", "Children Minor Page Faults/s", false, "bar", "pgflts/s", CMinFlt_max, "descending", true, false, false, NULL, "sum");
- add_table_field(wb, "TMinFlt", "Total Minor Page Faults/s", false, "bar", "pgflts/s", TMinFlt_max, "descending", true, false, false, NULL, "sum");
-
- // major page faults
- add_table_field(wb, "MajFlt", "Major Page Faults/s", false, "bar", "pgflts/s", MajFlt_max, "descending", true, false, false, NULL, "sum");
- add_table_field(wb, "CMajFlt", "Children Major Page Faults/s", false, "bar", "pgflts/s", CMajFlt_max, "descending", true, false, false, NULL, "sum");
- add_table_field(wb, "TMajFlt", "Total Major Page Faults/s", true, "bar", "pgflts/s", TMajFlt_max, "descending", true, false, false, NULL, "sum");
+ add_table_field(wb, "PPID", "Parent Process ID", false, "integer", "value", "number", 0, NULL, NAN, "ascending", true, false, false, "PID", "count_unique", false);
+ add_table_field(wb, "Category", "Category (apps_groups.conf)", true, "string", "value", "none", 0, NULL, NAN, "ascending", true, true, false, NULL, "count_unique", false);
+ add_table_field(wb, "User", "User Owner", true, "string", "value", "none", 0, NULL, NAN, "ascending", true, false, false, NULL, "count_unique", false);
+ add_table_field(wb, "Uid", "User ID", false, "integer", "value", "number", 0, NULL, NAN, "ascending", true, false, false, NULL, "count_unique", false);
+ add_table_field(wb, "Group", "Group Owner", false, "string", "value", "none", 0, NULL, NAN, "ascending", true, false, false, NULL, "count_unique", false);
+ add_table_field(wb, "Gid", "Group ID", false, "integer", "value", "number", 0, NULL, NAN, "ascending", true, false, false, NULL, "count_unique", false);
// CPU utilization
- add_table_field(wb, "UserCPU", "User CPU time", false, "bar", "%", UserCPU_max, "descending", true, false, false, NULL, "sum");
- add_table_field(wb, "SysCPU", "System CPU Time", false, "bar", "%", SysCPU_max, "descending", true, false, false, NULL, "sum");
- add_table_field(wb, "GuestCPU", "Guest CPU Time", false, "bar", "%", GuestCPU_max, "descending", true, false, false, NULL, "sum");
- add_table_field(wb, "CUserCPU", "Children User CPU Time", false, "bar", "%", CUserCPU_max, "descending", true, false, false, NULL, "sum");
- add_table_field(wb, "CSysCPU", "Children System CPU Time", false, "bar", "%", CSysCPU_max, "descending", true, false, false, NULL, "sum");
- add_table_field(wb, "CGuestCPU", "Children Guest CPU Time", false, "bar", "%", CGuestCPU_max, "descending", true, false, false, NULL, "sum");
- add_table_field(wb, "CPU", "Total CPU Time", true, "bar", "%", CPU_max, "descending", true, false, false, NULL, "sum");
+ add_table_field(wb, "CPU", "Total CPU Time (100% = 1 core)", true, "bar-with-integer", "bar", "number", 2, "%", CPU_max, "descending", true, false, false, NULL, "sum", true);
+ add_table_field(wb, "UserCPU", "User CPU time (100% = 1 core)", false, "bar-with-integer", "bar", "number", 2, "%", UserCPU_max, "descending", true, false, false, NULL, "sum", true);
+ add_table_field(wb, "SysCPU", "System CPU Time (100% = 1 core)", false, "bar-with-integer", "bar", "number", 2, "%", SysCPU_max, "descending", true, false, false, NULL, "sum", true);
+ add_table_field(wb, "GuestCPU", "Guest CPU Time (100% = 1 core)", false, "bar-with-integer", "bar", "number", 2, "%", GuestCPU_max, "descending", true, false, false, NULL, "sum", true);
+ add_table_field(wb, "CUserCPU", "Children User CPU Time (100% = 1 core)", false, "bar-with-integer", "bar", "number", 2, "%", CUserCPU_max, "descending", true, false, false, NULL, "sum", true);
+ add_table_field(wb, "CSysCPU", "Children System CPU Time (100% = 1 core)", false, "bar-with-integer", "bar", "number", 2, "%", CSysCPU_max, "descending", true, false, false, NULL, "sum", true);
+ add_table_field(wb, "CGuestCPU", "Children Guest CPU Time (100% = 1 core)", false, "bar-with-integer", "bar", "number", 2, "%", CGuestCPU_max, "descending", true, false, false, NULL, "sum", true);
// memory
- add_table_field(wb, "VMSize", "Virtual Memory Size", false, "bar", "MiB", VMSize_max, "descending", true, false, false, NULL, "sum");
- add_table_field(wb, "RSS", "Resident Set Size", MemTotal ? false : true, "bar", "MiB", RSS_max, "descending", true, false, false, NULL, "sum");
- add_table_field(wb, "Shared", "Shared Pages", false, "bar", "MiB", Shared_max, "descending", true, false, false, NULL, "sum");
- add_table_field(wb, "Swap", "Swap Memory", false, "bar", "MiB", Swap_max, "descending", true, false, false, NULL, "sum");
-
if(MemTotal)
- add_table_field(wb, "MemPcnt", "Memory Percentage", true, "bar", "%", 100.0, "descending", true, false, false, NULL, "sum");
+ add_table_field(wb, "Memory", "Memory Percentage", true, "bar-with-integer", "bar", "number", 2, "%", 100.0, "descending", true, false, false, NULL, "sum", true);
- // Logical I/O
-#ifndef __FreeBSD__
- add_table_field(wb, "LReads", "Logical I/O Reads", false, "bar", "KiB/s", LReads_max, "descending", true, false, false, NULL, "sum");
- add_table_field(wb, "LWrites", "Logical I/O Writes", false, "bar", "KiB/s", LWrites_max, "descending", true, false, false, NULL, "sum");
-#endif
+ add_table_field(wb, "Resident", "Resident Set Size", true, "bar-with-integer", "bar", "number", 2, "MiB", RSS_max, "descending", true, false, false, NULL, "sum", true);
+ add_table_field(wb, "Shared", "Shared Pages", true, "bar-with-integer", "bar", "number", 2, "MiB", Shared_max, "descending", true, false, false, NULL, "sum", true);
+ add_table_field(wb, "Virtual", "Virtual Memory Size", true, "bar-with-integer", "bar", "number", 2, "MiB", VMSize_max, "descending", true, false, false, NULL, "sum", true);
+ add_table_field(wb, "Swap", "Swap Memory", false, "bar-with-integer", "bar", "number", 2, "MiB", Swap_max, "descending", true, false, false, NULL, "sum", true);
// Physical I/O
- add_table_field(wb, "PReads", "Physical I/O Reads", true, "bar", "KiB/s", PReads_max, "descending", true, false, false, NULL, "sum");
- add_table_field(wb, "PWrites", "Physical I/O Writes", true, "bar", "KiB/s", PWrites_max, "descending", true, false, false, NULL, "sum");
+ add_table_field(wb, "PReads", "Physical I/O Reads", true, "bar-with-integer", "bar", "number", 2, "KiB/s", PReads_max, "descending", true, false, false, NULL, "sum", true);
+ add_table_field(wb, "PWrites", "Physical I/O Writes", true, "bar-with-integer", "bar", "number", 2, "KiB/s", PWrites_max, "descending", true, false, false, NULL, "sum", true);
+
+ // Logical I/O
+#ifndef __FreeBSD__
+ add_table_field(wb, "LReads", "Logical I/O Reads", true, "bar-with-integer", "bar", "number", 2, "KiB/s", LReads_max, "descending", true, false, false, NULL, "sum", true);
+ add_table_field(wb, "LWrites", "Logical I/O Writes", true, "bar-with-integer", "bar", "number", 2, "KiB/s", LWrites_max, "descending", true, false, false, NULL, "sum", true);
+#endif
// I/O calls
- add_table_field(wb, "RCalls", "I/O Read Calls", false, "bar", "calls/s", RCalls_max, "descending", true, false, false, NULL, "sum");
- add_table_field(wb, "WCalls", "I/O Write Calls", false, "bar", "calls/s", WCalls_max, "descending", true, false, false, NULL, "sum");
+ add_table_field(wb, "RCalls", "I/O Read Calls", true, "bar-with-integer", "bar", "number", 2, "calls/s", RCalls_max, "descending", true, false, false, NULL, "sum", true);
+ add_table_field(wb, "WCalls", "I/O Write Calls", true, "bar-with-integer", "bar", "number", 2, "calls/s", WCalls_max, "descending", true, false, false, NULL, "sum", true);
+
+ // minor page faults
+ add_table_field(wb, "MinFlt", "Minor Page Faults/s", false, "bar-with-integer", "bar", "number", 2, "pgflts/s", MinFlt_max, "descending", true, false, false, NULL, "sum", true);
+ add_table_field(wb, "CMinFlt", "Children Minor Page Faults/s", false, "bar-with-integer", "bar", "number", 2, "pgflts/s", CMinFlt_max, "descending", true, false, false, NULL, "sum", true);
+ add_table_field(wb, "TMinFlt", "Total Minor Page Faults/s", false, "bar-with-integer", "bar", "number", 2, "pgflts/s", TMinFlt_max, "descending", true, false, false, NULL, "sum", true);
+
+ // major page faults
+ add_table_field(wb, "MajFlt", "Major Page Faults/s", false, "bar-with-integer", "bar", "number", 2, "pgflts/s", MajFlt_max, "descending", true, false, false, NULL, "sum", true);
+ add_table_field(wb, "CMajFlt", "Children Major Page Faults/s", false, "bar-with-integer", "bar", "number", 2, "pgflts/s", CMajFlt_max, "descending", true, false, false, NULL, "sum", true);
+ add_table_field(wb, "TMajFlt", "Total Major Page Faults/s", true, "bar-with-integer", "bar", "number", 2, "pgflts/s", TMajFlt_max, "descending", true, false, false, NULL, "sum", true);
// open file descriptors
- add_table_field(wb, "Files", "Open Files", false, "bar", "fds", Files_max, "descending", true, false, false, NULL, "sum");
- add_table_field(wb, "Pipes", "Open Pipes", false, "bar", "fds", Pipes_max, "descending", true, false, false, NULL, "sum");
- add_table_field(wb, "Sockets", "Open Sockets", false, "bar", "fds", Sockets_max, "descending", true, false, false, NULL, "sum");
- add_table_field(wb, "iNotiFDs", "Open iNotify Descriptors", false, "bar", "fds", iNotiFDs_max, "descending", true, false, false, NULL, "sum");
- add_table_field(wb, "EventFDs", "Open Event Descriptors", false, "bar", "fds", EventFDs_max, "descending", true, false, false, NULL, "sum");
- add_table_field(wb, "TimerFDs", "Open Timer Descriptors", false, "bar", "fds", TimerFDs_max, "descending", true, false, false, NULL, "sum");
- add_table_field(wb, "SigFDs", "Open Signal Descriptors", false, "bar", "fds", SigFDs_max, "descending", true, false, false, NULL, "sum");
- add_table_field(wb, "EvPollFDs", "Open Event Poll Descriptors", false, "bar", "fds", EvPollFDs_max, "descending", true, false, false, NULL, "sum");
- add_table_field(wb, "OtherFDs", "Other Open Descriptors", false, "bar", "fds", OtherFDs_max, "descending", true, false, false, NULL, "sum");
- add_table_field(wb, "FDs", "All Open File Descriptors", true, "bar", "fds", FDs_max, "descending", true, false, false, NULL, "sum");
+ add_table_field(wb, "FDs", "All Open File Descriptors", true, "bar-with-integer", "bar", "number", 0, "fds", FDs_max, "descending", true, false, false, NULL, "sum", true);
+ add_table_field(wb, "Files", "Open Files", true, "bar-with-integer", "bar", "number", 0, "fds", Files_max, "descending", true, false, false, NULL, "sum", true);
+ add_table_field(wb, "Pipes", "Open Pipes", true, "bar-with-integer", "bar", "number", 0, "fds", Pipes_max, "descending", true, false, false, NULL, "sum", true);
+ add_table_field(wb, "Sockets", "Open Sockets", true, "bar-with-integer", "bar", "number", 0, "fds", Sockets_max, "descending", true, false, false, NULL, "sum", true);
+ add_table_field(wb, "iNotiFDs", "Open iNotify Descriptors", false, "bar-with-integer", "bar", "number", 0, "fds", iNotiFDs_max, "descending", true, false, false, NULL, "sum", true);
+ add_table_field(wb, "EventFDs", "Open Event Descriptors", false, "bar-with-integer", "bar", "number", 0, "fds", EventFDs_max, "descending", true, false, false, NULL, "sum", true);
+ add_table_field(wb, "TimerFDs", "Open Timer Descriptors", false, "bar-with-integer", "bar", "number", 0, "fds", TimerFDs_max, "descending", true, false, false, NULL, "sum", true);
+ add_table_field(wb, "SigFDs", "Open Signal Descriptors", false, "bar-with-integer", "bar", "number", 0, "fds", SigFDs_max, "descending", true, false, false, NULL, "sum", true);
+ add_table_field(wb, "EvPollFDs", "Open Event Poll Descriptors", false, "bar-with-integer", "bar", "number", 0, "fds", EvPollFDs_max, "descending", true, false, false, NULL, "sum", true);
+ add_table_field(wb, "OtherFDs", "Other Open Descriptors", false, "bar-with-integer", "bar", "number", 0, "fds", OtherFDs_max, "descending", true, false, false, NULL, "sum", true);
+
+ // processes, threads, uptime
+ add_table_field(wb, "Processes", "Processes", true, "bar-with-integer", "bar", "number", 0, "processes", Processes_max, "descending", true, false, false, NULL, "sum", true);
+ add_table_field(wb, "Threads", "Threads", true, "bar-with-integer", "bar", "number", 0, "threads", Threads_max, "descending", true, false, false, NULL, "sum", true);
+ add_table_field(wb, "Uptime", "Uptime in seconds", true, "duration", "bar", "duration", 2, "seconds", Uptime_max, "descending", true, false, false, NULL, "max", true);
buffer_strcat(
wb,
@@ -4674,7 +4699,7 @@ static void apps_plugin_function_processes(const char *transaction, char *functi
"\n \"Memory\": {"
"\n \"name\":\"Memory\","
"\n \"type\":\"stacked-bar\","
- "\n \"columns\": [ \"VMSize\", \"RSS\", \"Shared\", \"Swap\" ]"
+ "\n \"columns\": [ \"Virtual\", \"Resident\", \"Shared\", \"Swap\" ]"
"\n },"
);
@@ -4685,7 +4710,7 @@ static void apps_plugin_function_processes(const char *transaction, char *functi
"\n \"MemoryPercent\": {"
"\n \"name\":\"Memory Percentage\","
"\n \"type\":\"stacked-bar\","
- "\n \"columns\": [ \"MemPcnt\" ]"
+ "\n \"columns\": [ \"Memory\" ]"
"\n },"
);
@@ -4747,19 +4772,19 @@ static void apps_plugin_function_processes(const char *transaction, char *functi
"\n \"group_by\": {"
"\n \"pid\": {"
"\n \"name\":\"Process Tree by PID\","
- "\n \"columns\":[ \"PPid\" ]"
+ "\n \"columns\":[ \"PPID\" ]"
"\n },"
"\n \"category\": {"
"\n \"name\":\"Process Tree by Category\","
- "\n \"columns\":[ \"Category\", \"PPid\" ]"
+ "\n \"columns\":[ \"Category\", \"PPID\" ]"
"\n },"
"\n \"user\": {"
"\n \"name\":\"Process Tree by User\","
- "\n \"columns\":[ \"User\", \"PPid\" ]"
+ "\n \"columns\":[ \"User\", \"PPID\" ]"
"\n },"
"\n \"group\": {"
"\n \"name\":\"Process Tree by Group\","
- "\n \"columns\":[ \"Group\", \"PPid\" ]"
+ "\n \"columns\":[ \"Group\", \"PPID\" ]"
"\n }"
"\n }"
);
@@ -4834,6 +4859,7 @@ void *reader_main(void *arg __maybe_unused) {
int main(int argc, char **argv) {
// debug_flags = D_PROCFILE;
+ stderror = stderr;
clocks_init();
@@ -4897,7 +4923,7 @@ int main(int argc, char **argv) {
#endif
get_system_pid_max();
- get_system_cpus();
+ get_system_cpus_uncached();
parse_args(argc, argv);
diff --git a/collectors/cgroups.plugin/README.md b/collectors/cgroups.plugin/README.md
index d0f822e6..e58f1ba0 100644
--- a/collectors/cgroups.plugin/README.md
+++ b/collectors/cgroups.plugin/README.md
@@ -1,6 +1,10 @@
<!--
-title: "cgroups.plugin"
-custom_edit_url: https://github.com/netdata/netdata/edit/master/collectors/cgroups.plugin/README.md
+title: "Monitor Cgroups (cgroups.plugin)"
+custom_edit_url: "https://github.com/netdata/netdata/edit/master/collectors/cgroups.plugin/README.md"
+sidebar_label: "Monitor Cgroups"
+learn_status: "Published"
+learn_topic_type: "References"
+learn_rel_path: "References/Collectors references/Virtualized environments/Containers"
-->
# cgroups.plugin
@@ -74,7 +78,7 @@ currently unsupported when using unified cgroups.
### enabled cgroups
To provide a sane default, Netdata uses the
-following [pattern list](https://learn.netdata.cloud/docs/agent/libnetdata/simple_pattern):
+following [pattern list](https://github.com/netdata/netdata/blob/master/libnetdata/simple_pattern/README.md):
- checks the pattern against the path of the cgroup
@@ -305,4 +309,4 @@ cannot find, but immediately:
- I/O full pressure
Network interfaces are monitored by means of
-the [proc plugin](/collectors/proc.plugin/README.md#monitored-network-interface-metrics).
+the [proc plugin](https://github.com/netdata/netdata/blob/master/collectors/proc.plugin/README.md#monitored-network-interface-metrics).
diff --git a/collectors/cgroups.plugin/cgroup-network.c b/collectors/cgroups.plugin/cgroup-network.c
index 0b66ea47..a490df39 100644
--- a/collectors/cgroups.plugin/cgroup-network.c
+++ b/collectors/cgroups.plugin/cgroup-network.c
@@ -43,7 +43,7 @@ unsigned int read_iface_iflink(const char *prefix, const char *iface) {
unsigned long long iflink = 0;
int ret = read_single_number_file(filename, &iflink);
- if(ret) error("Cannot read '%s'.", filename);
+ if(ret) collector_error("Cannot read '%s'.", filename);
return (unsigned int)iflink;
}
@@ -56,7 +56,7 @@ unsigned int read_iface_ifindex(const char *prefix, const char *iface) {
unsigned long long ifindex = 0;
int ret = read_single_number_file(filename, &ifindex);
- if(ret) error("Cannot read '%s'.", filename);
+ if(ret) collector_error("Cannot read '%s'.", filename);
return (unsigned int)ifindex;
}
@@ -70,18 +70,18 @@ struct iface *read_proc_net_dev(const char *scope __maybe_unused, const char *pr
snprintfz(filename, FILENAME_MAX, "%s%s", prefix, (*prefix)?"/proc/1/net/dev":"/proc/net/dev");
#ifdef NETDATA_INTERNAL_CHECKS
- info("parsing '%s'", filename);
+ collector_info("parsing '%s'", filename);
#endif
ff = procfile_open(filename, " \t,:|", PROCFILE_FLAG_DEFAULT);
if(unlikely(!ff)) {
- error("Cannot open file '%s'", filename);
+ collector_error("Cannot open file '%s'", filename);
return NULL;
}
ff = procfile_readall(ff);
if(unlikely(!ff)) {
- error("Cannot read file '%s'", filename);
+ collector_error("Cannot read file '%s'", filename);
return NULL;
}
@@ -99,7 +99,7 @@ struct iface *read_proc_net_dev(const char *scope __maybe_unused, const char *pr
root = t;
#ifdef NETDATA_INTERNAL_CHECKS
- info("added %s interface '%s', ifindex %u, iflink %u", scope, t->device, t->ifindex, t->iflink);
+ collector_info("added %s interface '%s', ifindex %u, iflink %u", scope, t->device, t->ifindex, t->iflink);
#endif
}
@@ -145,7 +145,7 @@ static void continue_as_child(void) {
pid_t ret;
if (child < 0)
- error("fork() failed");
+ collector_error("fork() failed");
/* Only the child returns */
if (child == 0)
@@ -180,7 +180,7 @@ int proc_pid_fd(const char *prefix, const char *ns, pid_t pid) {
int fd = open(filename, O_RDONLY);
if(fd == -1)
- error("Cannot open proc_pid_fd() file '%s'", filename);
+ collector_error("Cannot open proc_pid_fd() file '%s'", filename);
return fd;
}
@@ -230,7 +230,7 @@ int switch_namespace(const char *prefix, pid_t pid) {
if(setns(all_ns[i].fd, all_ns[i].nstype) == -1) {
if(pass == 1) {
all_ns[i].status = 0;
- error("Cannot switch to %s namespace of pid %d", all_ns[i].name, (int) pid);
+ collector_error("Cannot switch to %s namespace of pid %d", all_ns[i].name, (int) pid);
}
}
else
@@ -243,17 +243,17 @@ int switch_namespace(const char *prefix, pid_t pid) {
if(root_fd != -1) {
if(fchdir(root_fd) < 0)
- error("Cannot fchdir() to pid %d root directory", (int)pid);
+ collector_error("Cannot fchdir() to pid %d root directory", (int)pid);
if(chroot(".") < 0)
- error("Cannot chroot() to pid %d root directory", (int)pid);
+ collector_error("Cannot chroot() to pid %d root directory", (int)pid);
close(root_fd);
}
if(cwd_fd != -1) {
if(fchdir(cwd_fd) < 0)
- error("Cannot fchdir() to pid %d current working directory", (int)pid);
+ collector_error("Cannot fchdir() to pid %d current working directory", (int)pid);
close(cwd_fd);
}
@@ -277,7 +277,7 @@ int switch_namespace(const char *prefix, pid_t pid) {
#else
errno = ENOSYS;
- error("setns() is missing on this system.");
+ collector_error("setns() is missing on this system.");
return 1;
#endif
@@ -286,13 +286,13 @@ int switch_namespace(const char *prefix, pid_t pid) {
pid_t read_pid_from_cgroup_file(const char *filename) {
int fd = open(filename, procfile_open_flags);
if(fd == -1) {
- error("Cannot open pid_from_cgroup() file '%s'.", filename);
+ collector_error("Cannot open pid_from_cgroup() file '%s'.", filename);
return 0;
}
FILE *fp = fdopen(fd, "r");
if(!fp) {
- error("Cannot upgrade fd to fp for file '%s'.", filename);
+ collector_error("Cannot upgrade fd to fp for file '%s'.", filename);
return 0;
}
@@ -308,7 +308,7 @@ pid_t read_pid_from_cgroup_file(const char *filename) {
fclose(fp);
#ifdef NETDATA_INTERNAL_CHECKS
- if(pid > 0) info("found pid %d on file '%s'", pid, filename);
+ if(pid > 0) collector_info("found pid %d on file '%s'", pid, filename);
#endif
return pid;
@@ -331,7 +331,7 @@ pid_t read_pid_from_cgroup(const char *path) {
DIR *dir = opendir(path);
if (!dir) {
- error("cannot read directory '%s'", path);
+ collector_error("cannot read directory '%s'", path);
return 0;
}
@@ -369,7 +369,7 @@ struct found_device {
void add_device(const char *host, const char *guest) {
#ifdef NETDATA_INTERNAL_CHECKS
- info("adding device with host '%s', guest '%s'", host, guest);
+ collector_info("adding device with host '%s', guest '%s'", host, guest);
#endif
uint32_t hash = simple_hash(host);
@@ -422,36 +422,36 @@ void detect_veth_interfaces(pid_t pid) {
host = read_proc_net_dev("host", netdata_configured_host_prefix);
if(!host) {
errno = 0;
- error("cannot read host interface list.");
+ collector_error("cannot read host interface list.");
goto cleanup;
}
if(!eligible_ifaces(host)) {
errno = 0;
- info("there are no double-linked host interfaces available.");
+ collector_info("there are no double-linked host interfaces available.");
goto cleanup;
}
if(switch_namespace(netdata_configured_host_prefix, pid)) {
errno = 0;
- error("cannot switch to the namespace of pid %u", (unsigned int) pid);
+ collector_error("cannot switch to the namespace of pid %u", (unsigned int) pid);
goto cleanup;
}
#ifdef NETDATA_INTERNAL_CHECKS
- info("switched to namespaces of pid %d", pid);
+ collector_info("switched to namespaces of pid %d", pid);
#endif
cgroup = read_proc_net_dev("cgroup", NULL);
if(!cgroup) {
errno = 0;
- error("cannot read cgroup interface list.");
+ collector_error("cannot read cgroup interface list.");
goto cleanup;
}
if(!eligible_ifaces(cgroup)) {
errno = 0;
- error("there are not double-linked cgroup interfaces available.");
+ collector_error("there are not double-linked cgroup interfaces available.");
goto cleanup;
}
@@ -495,7 +495,7 @@ cleanup:
#define CGROUP_NETWORK_INTERFACE_MAX_LINE 2048
void call_the_helper(pid_t pid, const char *cgroup) {
if(setresuid(0, 0, 0) == -1)
- error("setresuid(0, 0, 0) failed.");
+ collector_error("setresuid(0, 0, 0) failed.");
char command[CGROUP_NETWORK_INTERFACE_MAX_LINE + 1];
if(cgroup)
@@ -503,7 +503,7 @@ void call_the_helper(pid_t pid, const char *cgroup) {
else
snprintfz(command, CGROUP_NETWORK_INTERFACE_MAX_LINE, "exec " PLUGINS_DIR "/cgroup-network-helper.sh --pid %d", pid);
- info("running: %s", command);
+ collector_info("running: %s", command);
pid_t cgroup_pid;
FILE *fp_child_input, *fp_child_output;
@@ -539,7 +539,7 @@ void call_the_helper(pid_t pid, const char *cgroup) {
netdata_pclose(fp_child_input, fp_child_output, cgroup_pid);
}
else
- error("cannot execute cgroup-network helper script: %s", command);
+ collector_error("cannot execute cgroup-network helper script: %s", command);
}
int is_valid_path_symbol(char c) {
@@ -570,33 +570,33 @@ int verify_path(const char *path) {
const char *s = path;
while((c = *s++)) {
if(!( isalnum(c) || is_valid_path_symbol(c) )) {
- error("invalid character in path '%s'", path);
+ collector_error("invalid character in path '%s'", path);
return -1;
}
}
if(strstr(path, "\\") && !strstr(path, "\\x")) {
- error("invalid escape sequence in path '%s'", path);
+ collector_error("invalid escape sequence in path '%s'", path);
return 1;
}
if(strstr(path, "/../")) {
- error("invalid parent path sequence detected in '%s'", path);
+ collector_error("invalid parent path sequence detected in '%s'", path);
return 1;
}
if(path[0] != '/') {
- error("only absolute path names are supported - invalid path '%s'", path);
+ collector_error("only absolute path names are supported - invalid path '%s'", path);
return -1;
}
if (stat(path, &sb) == -1) {
- error("cannot stat() path '%s'", path);
+ collector_error("cannot stat() path '%s'", path);
return -1;
}
if((sb.st_mode & S_IFMT) != S_IFDIR) {
- error("path '%s' is not a directory", path);
+ collector_error("path '%s' is not a directory", path);
return -1;
}
@@ -618,10 +618,10 @@ char *fix_path_variable(void) {
char *s = strsep(&ptr, ":");
if(s && *s) {
if(verify_path(s) == -1) {
- error("the PATH variable includes an invalid path '%s' - removed it.", s);
+ collector_error("the PATH variable includes an invalid path '%s' - removed it.", s);
}
else {
- info("the PATH variable includes a valid path '%s'.", s);
+ collector_info("the PATH variable includes a valid path '%s'.", s);
if(added) strcat(safe_path, ":");
strcat(safe_path, s);
added++;
@@ -629,8 +629,8 @@ char *fix_path_variable(void) {
}
}
- info("unsafe PATH: '%s'.", path);
- info(" safe PATH: '%s'.", safe_path);
+ collector_info("unsafe PATH: '%s'.", path);
+ collector_info(" safe PATH: '%s'.", safe_path);
freez(p);
return safe_path;
@@ -646,6 +646,7 @@ void usage(void) {
}
int main(int argc, char **argv) {
+ stderror = stderr;
pid_t pid = 0;
program_name = argv[0];
@@ -690,7 +691,7 @@ int main(int argc, char **argv) {
if(pid <= 0) {
errno = 0;
- error("Invalid pid %d given", (int) pid);
+ collector_error("Invalid pid %d given", (int) pid);
return 2;
}
@@ -699,7 +700,7 @@ int main(int argc, char **argv) {
else if(!strcmp(argv[arg], "--cgroup")) {
char *cgroup = argv[arg+1];
if(verify_path(cgroup) == -1) {
- error("cgroup '%s' does not exist or is not valid.", cgroup);
+ collector_error("cgroup '%s' does not exist or is not valid.", cgroup);
return 1;
}
@@ -708,7 +709,7 @@ int main(int argc, char **argv) {
if(pid <= 0 && !detected_devices) {
errno = 0;
- error("Cannot find a cgroup PID from cgroup '%s'", cgroup);
+ collector_error("Cannot find a cgroup PID from cgroup '%s'", cgroup);
}
}
else
diff --git a/collectors/cgroups.plugin/sys_fs_cgroup.c b/collectors/cgroups.plugin/sys_fs_cgroup.c
index 8f754828..66db0b72 100644
--- a/collectors/cgroups.plugin/sys_fs_cgroup.c
+++ b/collectors/cgroups.plugin/sys_fs_cgroup.c
@@ -174,9 +174,9 @@ static enum cgroups_systemd_setting cgroups_detect_systemd(const char *exec)
}
if (ret == -1) {
- error("Failed to get the output of \"%s\"", exec);
+ collector_error("Failed to get the output of \"%s\"", exec);
} else if (ret == 0) {
- info("Cannot get the output of \"%s\" within %"PRId64" seconds", exec, (int64_t)timeout.tv_sec);
+ collector_info("Cannot get the output of \"%s\" within %"PRId64" seconds", exec, (int64_t)timeout.tv_sec);
} else {
while (fgets(buf, MAXSIZE_PROC_CMDLINE, fp_child_output) != NULL) {
if ((begin = strstr(buf, SYSTEMD_HIERARCHY_STRING))) {
@@ -214,7 +214,7 @@ static enum cgroups_type cgroups_try_detect_version()
FILE *fp_child_input;
FILE *fp_child_output = netdata_popen("grep cgroup /proc/filesystems", &command_pid, &fp_child_input);
if (!fp_child_output) {
- error("popen failed");
+ collector_error("popen failed");
return CGROUPS_AUTODETECT_FAIL;
}
while (fgets(buf, MAXSIZE_PROC_CMDLINE, fp_child_output) != NULL) {
@@ -258,12 +258,12 @@ static enum cgroups_type cgroups_try_detect_version()
// check kernel command line flag that can override that setting
FILE *fp = fopen("/proc/cmdline", "r");
if (!fp) {
- error("Error reading kernel boot commandline parameters");
+ collector_error("Error reading kernel boot commandline parameters");
return CGROUPS_AUTODETECT_FAIL;
}
if (!fgets(buf, MAXSIZE_PROC_CMDLINE, fp)) {
- error("couldn't read all cmdline params into buffer");
+ collector_error("couldn't read all cmdline params into buffer");
fclose(fp);
return CGROUPS_AUTODETECT_FAIL;
}
@@ -271,7 +271,7 @@ static enum cgroups_type cgroups_try_detect_version()
fclose(fp);
if (strstr(buf, "systemd.unified_cgroup_hierarchy=0")) {
- info("cgroups v2 (unified cgroups) is available but are disabled on this system.");
+ collector_info("cgroups v2 (unified cgroups) is available but are disabled on this system.");
return CGROUPS_V1;
}
return CGROUPS_V2;
@@ -311,7 +311,7 @@ void read_cgroup_plugin_configuration() {
if(cgroup_use_unified_cgroups == CONFIG_BOOLEAN_AUTO)
cgroup_use_unified_cgroups = (cgroups_try_detect_version() == CGROUPS_V2);
- info("use unified cgroups %s", cgroup_use_unified_cgroups ? "true" : "false");
+ collector_info("use unified cgroups %s", cgroup_use_unified_cgroups ? "true" : "false");
cgroup_containers_chart_priority = (int)config_get_number("plugin:cgroups", "containers priority", cgroup_containers_chart_priority);
if(cgroup_containers_chart_priority < 1)
@@ -361,7 +361,7 @@ void read_cgroup_plugin_configuration() {
mi = mountinfo_find_by_filesystem_super_option(root, "cgroup", "cpuacct");
if(!mi) mi = mountinfo_find_by_filesystem_mount_source(root, "cgroup", "cpuacct");
if(!mi) {
- error("CGROUP: cannot find cpuacct mountinfo. Assuming default: /sys/fs/cgroup/cpuacct");
+ collector_error("CGROUP: cannot find cpuacct mountinfo. Assuming default: /sys/fs/cgroup/cpuacct");
s = "/sys/fs/cgroup/cpuacct";
}
else s = mi->mount_point;
@@ -371,7 +371,7 @@ void read_cgroup_plugin_configuration() {
mi = mountinfo_find_by_filesystem_super_option(root, "cgroup", "cpuset");
if(!mi) mi = mountinfo_find_by_filesystem_mount_source(root, "cgroup", "cpuset");
if(!mi) {
- error("CGROUP: cannot find cpuset mountinfo. Assuming default: /sys/fs/cgroup/cpuset");
+ collector_error("CGROUP: cannot find cpuset mountinfo. Assuming default: /sys/fs/cgroup/cpuset");
s = "/sys/fs/cgroup/cpuset";
}
else s = mi->mount_point;
@@ -381,7 +381,7 @@ void read_cgroup_plugin_configuration() {
mi = mountinfo_find_by_filesystem_super_option(root, "cgroup", "blkio");
if(!mi) mi = mountinfo_find_by_filesystem_mount_source(root, "cgroup", "blkio");
if(!mi) {
- error("CGROUP: cannot find blkio mountinfo. Assuming default: /sys/fs/cgroup/blkio");
+ collector_error("CGROUP: cannot find blkio mountinfo. Assuming default: /sys/fs/cgroup/blkio");
s = "/sys/fs/cgroup/blkio";
}
else s = mi->mount_point;
@@ -391,7 +391,7 @@ void read_cgroup_plugin_configuration() {
mi = mountinfo_find_by_filesystem_super_option(root, "cgroup", "memory");
if(!mi) mi = mountinfo_find_by_filesystem_mount_source(root, "cgroup", "memory");
if(!mi) {
- error("CGROUP: cannot find memory mountinfo. Assuming default: /sys/fs/cgroup/memory");
+ collector_error("CGROUP: cannot find memory mountinfo. Assuming default: /sys/fs/cgroup/memory");
s = "/sys/fs/cgroup/memory";
}
else s = mi->mount_point;
@@ -401,7 +401,7 @@ void read_cgroup_plugin_configuration() {
mi = mountinfo_find_by_filesystem_super_option(root, "cgroup", "devices");
if(!mi) mi = mountinfo_find_by_filesystem_mount_source(root, "cgroup", "devices");
if(!mi) {
- error("CGROUP: cannot find devices mountinfo. Assuming default: /sys/fs/cgroup/devices");
+ collector_error("CGROUP: cannot find devices mountinfo. Assuming default: /sys/fs/cgroup/devices");
s = "/sys/fs/cgroup/devices";
}
else s = mi->mount_point;
@@ -433,7 +433,7 @@ void read_cgroup_plugin_configuration() {
if(mi) debug(D_CGROUP, "found unified cgroup root using mountsource info, with path: '%s'", mi->mount_point);
}
if(!mi) {
- error("CGROUP: cannot find cgroup2 mountinfo. Assuming default: /sys/fs/cgroup");
+ collector_error("CGROUP: cannot find cgroup2 mountinfo. Assuming default: /sys/fs/cgroup");
s = "/sys/fs/cgroup";
}
else s = mi->mount_point;
@@ -575,13 +575,13 @@ void netdata_cgroup_ebpf_initialize_shm()
{
shm_fd_cgroup_ebpf = shm_open(NETDATA_SHARED_MEMORY_EBPF_CGROUP_NAME, O_CREAT | O_RDWR, 0660);
if (shm_fd_cgroup_ebpf < 0) {
- error("Cannot initialize shared memory used by cgroup and eBPF, integration won't happen.");
+ collector_error("Cannot initialize shared memory used by cgroup and eBPF, integration won't happen.");
return;
}
size_t length = sizeof(netdata_ebpf_cgroup_shm_header_t) + cgroup_root_max * sizeof(netdata_ebpf_cgroup_shm_body_t);
if (ftruncate(shm_fd_cgroup_ebpf, length)) {
- error("Cannot set size for shared memory.");
+ collector_error("Cannot set size for shared memory.");
goto end_init_shm;
}
@@ -590,7 +590,7 @@ void netdata_cgroup_ebpf_initialize_shm()
shm_fd_cgroup_ebpf, 0);
if (!shm_cgroup_ebpf.header) {
- error("Cannot map shared memory used between cgroup and eBPF, integration won't happen");
+ collector_error("Cannot map shared memory used between cgroup and eBPF, integration won't happen");
goto end_init_shm;
}
shm_cgroup_ebpf.body = (netdata_ebpf_cgroup_shm_body_t *) ((char *)shm_cgroup_ebpf.header +
@@ -604,7 +604,7 @@ void netdata_cgroup_ebpf_initialize_shm()
return;
}
- error("Cannot create semaphore, integration between eBPF and cgroup won't happen");
+ collector_error("Cannot create semaphore, integration between eBPF and cgroup won't happen");
munmap(shm_cgroup_ebpf.header, length);
end_init_shm:
@@ -1077,7 +1077,7 @@ static inline void cgroup_read_cpuacct_stat(struct cpuacct_stat *cp) {
unsigned long i, lines = procfile_lines(ff);
if(unlikely(lines < 1)) {
- error("CGROUP: file '%s' should have 1+ lines.", cp->filename);
+ collector_error("CGROUP: file '%s' should have 1+ lines.", cp->filename);
cp->updated = 0;
return;
}
@@ -1123,7 +1123,7 @@ static inline void cgroup_read_cpuacct_cpu_stat(struct cpuacct_cpu_throttling *c
unsigned long lines = procfile_lines(ff);
if (unlikely(lines < 3)) {
- error("CGROUP: file '%s' should have 3 lines.", cp->filename);
+ collector_error("CGROUP: file '%s' should have 3 lines.", cp->filename);
cp->updated = 0;
return;
}
@@ -1180,7 +1180,7 @@ static inline void cgroup2_read_cpuacct_cpu_stat(struct cpuacct_stat *cp, struct
unsigned long lines = procfile_lines(ff);
if (unlikely(lines < 3)) {
- error("CGROUP: file '%s' should have at least 3 lines.", cp->filename);
+ collector_error("CGROUP: file '%s' should have at least 3 lines.", cp->filename);
cp->updated = 0;
return;
}
@@ -1261,7 +1261,7 @@ static inline void cgroup_read_cpuacct_usage(struct cpuacct_usage *ca) {
}
if(unlikely(procfile_lines(ff) < 1)) {
- error("CGROUP: file '%s' should have 1+ lines but has %zu.", ca->filename, procfile_lines(ff));
+ collector_error("CGROUP: file '%s' should have 1+ lines but has %zu.", ca->filename, procfile_lines(ff));
ca->updated = 0;
return;
}
@@ -1326,7 +1326,7 @@ static inline void cgroup_read_blkio(struct blkio *io) {
unsigned long i, lines = procfile_lines(ff);
if(unlikely(lines < 1)) {
- error("CGROUP: file '%s' should have 1+ lines.", io->filename);
+ collector_error("CGROUP: file '%s' should have 1+ lines.", io->filename);
io->updated = 0;
return;
}
@@ -1398,7 +1398,7 @@ static inline void cgroup2_read_blkio(struct blkio *io, unsigned int word_offset
unsigned long i, lines = procfile_lines(ff);
if (unlikely(lines < 1)) {
- error("CGROUP: file '%s' should have 1+ lines.", io->filename);
+ collector_error("CGROUP: file '%s' should have 1+ lines.", io->filename);
io->updated = 0;
return;
}
@@ -1442,7 +1442,7 @@ static inline void cgroup2_read_pressure(struct pressure *res) {
size_t lines = procfile_lines(ff);
if (lines < 1) {
- error("CGROUP: file '%s' should have 1+ lines.", res->filename);
+ collector_error("CGROUP: file '%s' should have 1+ lines.", res->filename);
res->updated = 0;
return;
}
@@ -1456,7 +1456,7 @@ static inline void cgroup2_read_pressure(struct pressure *res) {
res->full.share_time.value10 = strtod(procfile_lineword(ff, 1, 2), NULL);
res->full.share_time.value60 = strtod(procfile_lineword(ff, 1, 4), NULL);
res->full.share_time.value300 = strtod(procfile_lineword(ff, 1, 6), NULL);
- res->full.total_time.value_total = str2ull(procfile_lineword(ff, 0, 8)) / 1000; // us->ms
+ res->full.total_time.value_total = str2ull(procfile_lineword(ff, 1, 8)) / 1000; // us->ms
}
res->updated = 1;
@@ -1499,7 +1499,7 @@ static inline void cgroup_read_memory(struct memory *mem, char parent_cg_is_unif
unsigned long i, lines = procfile_lines(ff);
if(unlikely(lines < 1)) {
- error("CGROUP: file '%s' should have 1+ lines.", mem->filename_detailed);
+ collector_error("CGROUP: file '%s' should have 1+ lines.", mem->filename_detailed);
mem->updated_detailed = 0;
goto memory_next;
}
@@ -1669,7 +1669,7 @@ static inline void read_cgroup_network_interfaces(struct cgroup *cg) {
FILE *fp_child_input, *fp_child_output;
(void)netdata_popen_raw_default_flags_and_environment(&cgroup_pid, &fp_child_input, &fp_child_output, cgroups_network_interface_script, "--cgroup", cgroup_identifier);
if(!fp_child_output) {
- error("CGROUP: cannot popen(%s --cgroup \"%s\", \"r\").", cgroups_network_interface_script, cgroup_identifier);
+ collector_error("CGROUP: cannot popen(%s --cgroup \"%s\", \"r\").", cgroups_network_interface_script, cgroup_identifier);
return;
}
@@ -1687,12 +1687,12 @@ static inline void read_cgroup_network_interfaces(struct cgroup *cg) {
}
if(!*s) {
- error("CGROUP: empty host interface returned by script");
+ collector_error("CGROUP: empty host interface returned by script");
continue;
}
if(!*t) {
- error("CGROUP: empty guest interface returned by script");
+ collector_error("CGROUP: empty guest interface returned by script");
continue;
}
@@ -1702,7 +1702,7 @@ static inline void read_cgroup_network_interfaces(struct cgroup *cg) {
i->next = cg->interfaces;
cg->interfaces = i;
- info("CGROUP: cgroup '%s' has network interface '%s' as '%s'", cg->id, i->host_device, i->container_device);
+ collector_info("CGROUP: cgroup '%s' has network interface '%s' as '%s'", cg->id, i->host_device, i->container_device);
// register a device rename to proc_net_dev.c
netdev_rename_device_add(
@@ -1875,7 +1875,7 @@ static inline void discovery_rename_cgroup(struct cgroup *cg) {
FILE *fp_child_input, *fp_child_output;
(void)netdata_popen_raw_default_flags_and_environment(&cgroup_pid, &fp_child_input, &fp_child_output, cgroups_rename_script, cg->id, cg->intermediate_id);
if (!fp_child_output) {
- error("CGROUP: cannot popen(%s \"%s\", \"r\").", cgroups_rename_script, cg->intermediate_id);
+ collector_error("CGROUP: cannot popen(%s \"%s\", \"r\").", cgroups_rename_script, cg->intermediate_id);
cg->pending_renames = 0;
cg->processed = 1;
return;
@@ -2034,14 +2034,14 @@ static inline void discovery_find_cgroup_in_dir_callback(const char *dir) {
}
if (cgroup_root_count >= cgroup_root_max) {
- info("CGROUP: maximum number of cgroups reached (%d). Not adding cgroup '%s'", cgroup_root_count, dir);
+ collector_info("CGROUP: maximum number of cgroups reached (%d). Not adding cgroup '%s'", cgroup_root_count, dir);
return;
}
if (cgroup_max_depth > 0) {
int depth = calc_cgroup_depth(dir);
if (depth > cgroup_max_depth) {
- info("CGROUP: '%s' is too deep (%d, while max is %d)", dir, depth, cgroup_max_depth);
+ collector_info("CGROUP: '%s' is too deep (%d, while max is %d)", dir, depth, cgroup_max_depth);
return;
}
}
@@ -2066,7 +2066,7 @@ static inline int discovery_find_dir_in_subdirs(const char *base, const char *th
DIR *dir = opendir(this);
if(!dir) {
- error("CGROUP: cannot read directory '%s'", base);
+ collector_error("CGROUP: cannot read directory '%s'", base);
return ret;
}
ret = 1;
@@ -2550,7 +2550,7 @@ static inline void discovery_find_all_cgroups_v1() {
if (cgroup_enable_cpuacct_stat || cgroup_enable_cpuacct_usage) {
if (discovery_find_dir_in_subdirs(cgroup_cpuacct_base, NULL, discovery_find_cgroup_in_dir_callback) == -1) {
cgroup_enable_cpuacct_stat = cgroup_enable_cpuacct_usage = CONFIG_BOOLEAN_NO;
- error("CGROUP: disabled cpu statistics.");
+ collector_error("CGROUP: disabled cpu statistics.");
}
}
@@ -2560,7 +2560,7 @@ static inline void discovery_find_all_cgroups_v1() {
cgroup_enable_blkio_io = cgroup_enable_blkio_ops = cgroup_enable_blkio_throttle_io =
cgroup_enable_blkio_throttle_ops = cgroup_enable_blkio_merged_ops = cgroup_enable_blkio_queued_ops =
CONFIG_BOOLEAN_NO;
- error("CGROUP: disabled blkio statistics.");
+ collector_error("CGROUP: disabled blkio statistics.");
}
}
@@ -2568,14 +2568,14 @@ static inline void discovery_find_all_cgroups_v1() {
if (discovery_find_dir_in_subdirs(cgroup_memory_base, NULL, discovery_find_cgroup_in_dir_callback) == -1) {
cgroup_enable_memory = cgroup_enable_detailed_memory = cgroup_enable_swap = cgroup_enable_memory_failcnt =
CONFIG_BOOLEAN_NO;
- error("CGROUP: disabled memory statistics.");
+ collector_error("CGROUP: disabled memory statistics.");
}
}
if (cgroup_search_in_devices) {
if (discovery_find_dir_in_subdirs(cgroup_devices_base, NULL, discovery_find_cgroup_in_dir_callback) == -1) {
cgroup_search_in_devices = 0;
- error("CGROUP: disabled devices statistics.");
+ collector_error("CGROUP: disabled devices statistics.");
}
}
}
@@ -2583,7 +2583,7 @@ static inline void discovery_find_all_cgroups_v1() {
static inline void discovery_find_all_cgroups_v2() {
if (discovery_find_dir_in_subdirs(cgroup_unified_base, NULL, discovery_find_cgroup_in_dir_callback) == -1) {
cgroup_unified_exist = CONFIG_BOOLEAN_NO;
- error("CGROUP: disabled unified cgroups statistics.");
+ collector_error("CGROUP: disabled unified cgroups statistics.");
}
}
@@ -2651,7 +2651,7 @@ static int discovery_is_cgroup_duplicate(struct cgroup *cg) {
struct cgroup *c;
for (c = discovered_cgroup_root; c; c = c->discovered_next) {
if (c != cg && c->enabled && c->hash_chart == cg->hash_chart && !strcmp(c->chart_id, cg->chart_id)) {
- error("CGROUP: chart id '%s' already exists with id '%s' and is enabled and available. Disabling cgroup with id '%s'.", cg->chart_id, c->id, cg->id);
+ collector_error("CGROUP: chart id '%s' already exists with id '%s' and is enabled and available. Disabling cgroup with id '%s'.", cg->chart_id, c->id, cg->id);
return 1;
}
}
@@ -2686,7 +2686,7 @@ static inline void discovery_process_cgroup(struct cgroup *cg) {
cg->processed = 1;
if ((strlen(cg->chart_id) + strlen(cgroup_chart_id_prefix)) >= RRD_ID_LENGTH_MAX) {
- info("cgroup '%s' (chart id '%s') disabled because chart_id exceeds the limit (RRD_ID_LENGTH_MAX)", cg->id, cg->chart_id);
+ collector_info("cgroup '%s' (chart id '%s') disabled because chart_id exceeds the limit (RRD_ID_LENGTH_MAX)", cg->id, cg->chart_id);
return;
}
@@ -2754,10 +2754,20 @@ static inline void discovery_find_all_cgroups() {
debug(D_CGROUP, "done searching for cgroups");
}
+static void cgroup_discovery_cleanup(void *ptr) {
+ UNUSED(ptr);
+
+ discovery_thread.exited = 1;
+ worker_unregister();
+ service_exits();
+}
+
void cgroup_discovery_worker(void *ptr)
{
UNUSED(ptr);
+ netdata_thread_cleanup_push(cgroup_discovery_cleanup, ptr);
+
worker_register("CGROUPSDISC");
worker_register_job_name(WORKER_DISCOVERY_INIT, "init");
worker_register_job_name(WORKER_DISCOVERY_FIND, "find");
@@ -2777,24 +2787,23 @@ void cgroup_discovery_worker(void *ptr)
NULL,
SIMPLE_PATTERN_EXACT);
- while (!netdata_exit) {
+ while (service_running(SERVICE_COLLECTORS)) {
worker_is_idle();
uv_mutex_lock(&discovery_thread.mutex);
- while (!discovery_thread.start_discovery)
+ while (!discovery_thread.start_discovery && service_running(SERVICE_COLLECTORS))
uv_cond_wait(&discovery_thread.cond_var, &discovery_thread.mutex);
discovery_thread.start_discovery = 0;
uv_mutex_unlock(&discovery_thread.mutex);
- if (unlikely(netdata_exit))
+ if (unlikely(!service_running(SERVICE_COLLECTORS)))
break;
discovery_find_all_cgroups();
}
- discovery_thread.exited = 1;
- worker_unregister();
-}
+ netdata_thread_cleanup_pop(1);
+}
// ----------------------------------------------------------------------------
// generate charts
@@ -3507,52 +3516,15 @@ static inline char *cgroup_chart_type(char *buffer, const char *id, size_t len)
return buffer;
}
-static inline unsigned long long cpuset_str2ull(char **s) {
- unsigned long long n = 0;
- char c;
- for(c = **s; c >= '0' && c <= '9' ; c = *(++*s)) {
- n *= 10;
- n += c - '0';
- }
- return n;
-}
-
static inline void update_cpu_limits(char **filename, unsigned long long *value, struct cgroup *cg) {
if(*filename) {
int ret = -1;
if(value == &cg->cpuset_cpus) {
- static char *buf = NULL;
- static size_t buf_size = 0;
-
- if(!buf) {
- buf_size = 100U + 6 * get_system_cpus(); // taken from kernel/cgroup/cpuset.c
- buf = mallocz(buf_size + 1);
- }
-
- ret = read_file(*filename, buf, buf_size);
-
- if(!ret) {
- char *s = buf;
- unsigned long long ncpus = 0;
-
- // parse the cpuset string and calculate the number of cpus the cgroup is allowed to use
- while(*s) {
- unsigned long long n = cpuset_str2ull(&s);
- ncpus++;
- if(*s == ',') {
- s++;
- continue;
- }
- if(*s == '-') {
- s++;
- unsigned long long m = cpuset_str2ull(&s);
- ncpus += m - n; // calculate the number of cpus in the region
- }
- s++;
- }
-
- if(likely(ncpus)) *value = ncpus;
+ unsigned long ncpus = read_cpuset_cpus(*filename, get_system_cpus());
+ if(ncpus) {
+ *value = ncpus;
+ ret = 0;
}
}
else if(value == &cg->cpu_cfs_period) {
@@ -3564,7 +3536,7 @@ static inline void update_cpu_limits(char **filename, unsigned long long *value,
else ret = -1;
if(ret) {
- error("Cannot refresh cgroup %s cpu limit by reading '%s'. Will not update its limit anymore.", cg->id, *filename);
+ collector_error("Cannot refresh cgroup %s cpu limit by reading '%s'. Will not update its limit anymore.", cg->id, *filename);
freez(*filename);
*filename = NULL;
}
@@ -3588,7 +3560,7 @@ static inline void update_cpu_limits2(struct cgroup *cg) {
unsigned long lines = procfile_lines(ff);
if (unlikely(lines < 1)) {
- error("CGROUP: file '%s' should have 1 lines.", cg->filename_cpu_cfs_quota);
+ collector_error("CGROUP: file '%s' should have 1 lines.", cg->filename_cpu_cfs_quota);
return;
}
@@ -3605,7 +3577,7 @@ static inline void update_cpu_limits2(struct cgroup *cg) {
return;
cpu_limits2_err:
- error("Cannot refresh cgroup %s cpu limit by reading '%s'. Will not update its limit anymore.", cg->id, cg->filename_cpu_cfs_quota);
+ collector_error("Cannot refresh cgroup %s cpu limit by reading '%s'. Will not update its limit anymore.", cg->id, cg->filename_cpu_cfs_quota);
freez(cg->filename_cpu_cfs_quota);
cg->filename_cpu_cfs_quota = NULL;
@@ -3617,7 +3589,7 @@ static inline int update_memory_limits(char **filename, const RRDSETVAR_ACQUIRED
if(unlikely(!*chart_var)) {
*chart_var = rrdsetvar_custom_chart_variable_add_and_acquire(cg->st_mem_usage, chart_var_name);
if(!*chart_var) {
- error("Cannot create cgroup %s chart variable '%s'. Will not update its limit anymore.", cg->id, chart_var_name);
+ collector_error("Cannot create cgroup %s chart variable '%s'. Will not update its limit anymore.", cg->id, chart_var_name);
freez(*filename);
*filename = NULL;
}
@@ -3626,7 +3598,7 @@ static inline int update_memory_limits(char **filename, const RRDSETVAR_ACQUIRED
if(*filename && *chart_var) {
if(!(cg->options & CGROUP_OPTIONS_IS_UNIFIED)) {
if(read_single_number_file(*filename, value)) {
- error("Cannot refresh cgroup %s memory limit by reading '%s'. Will not update its limit anymore.", cg->id, *filename);
+ collector_error("Cannot refresh cgroup %s memory limit by reading '%s'. Will not update its limit anymore.", cg->id, *filename);
freez(*filename);
*filename = NULL;
}
@@ -3638,7 +3610,7 @@ static inline int update_memory_limits(char **filename, const RRDSETVAR_ACQUIRED
char buffer[30 + 1];
int ret = read_file(*filename, buffer, 30);
if(ret) {
- error("Cannot refresh cgroup %s memory limit by reading '%s'. Will not update its limit anymore.", cg->id, *filename);
+ collector_error("Cannot refresh cgroup %s memory limit by reading '%s'. Will not update its limit anymore.", cg->id, *filename);
freez(*filename);
*filename = NULL;
return 0;
@@ -3747,7 +3719,7 @@ void update_cgroup_charts(int update_every) {
if(unlikely(!cg->chart_var_cpu_limit)) {
cg->chart_var_cpu_limit = rrdsetvar_custom_chart_variable_add_and_acquire(cg->st_cpu, "cpu_limit");
if(!cg->chart_var_cpu_limit) {
- error("Cannot create cgroup %s chart variable 'cpu_limit'. Will not update its limit anymore.", cg->id);
+ collector_error("Cannot create cgroup %s chart variable 'cpu_limit'. Will not update its limit anymore.", cg->id);
if(cg->filename_cpuset_cpus) freez(cg->filename_cpuset_cpus);
cg->filename_cpuset_cpus = NULL;
if(cg->filename_cpu_cfs_period) freez(cg->filename_cpu_cfs_period);
@@ -4139,7 +4111,7 @@ void update_cgroup_charts(int update_every) {
if(likely(ff && procfile_lines(ff) && !strncmp(procfile_word(ff, 0), "MemTotal", 8)))
ram_total = str2ull(procfile_word(ff, 1)) * 1024;
else {
- error("Cannot read file %s. Will not update cgroup %s RAM limit anymore.", filename, cg->id);
+ collector_error("Cannot read file %s. Will not update cgroup %s RAM limit anymore.", filename, cg->id);
freez(cg->filename_memory_limit);
cg->filename_memory_limit = NULL;
}
@@ -4773,19 +4745,19 @@ static void cgroup_main_cleanup(void *ptr) {
struct netdata_static_thread *static_thread = (struct netdata_static_thread *)ptr;
static_thread->enabled = NETDATA_MAIN_THREAD_EXITING;
- info("cleaning up...");
+ collector_info("cleaning up...");
usec_t max = 2 * USEC_PER_SEC, step = 50000;
if (!discovery_thread.exited) {
- info("stopping discovery thread worker");
+ collector_info("stopping discovery thread worker");
uv_mutex_lock(&discovery_thread.mutex);
discovery_thread.start_discovery = 1;
uv_cond_signal(&discovery_thread.cond_var);
uv_mutex_unlock(&discovery_thread.mutex);
}
- info("waiting for discovery thread to finish...");
+ collector_info("waiting for discovery thread to finish...");
while (!discovery_thread.exited && max > 0) {
max -= step;
@@ -4824,7 +4796,7 @@ void *cgroups_main(void *ptr) {
netdata_cgroup_ebpf_initialize_shm();
if (uv_mutex_init(&cgroup_root_mutex)) {
- error("CGROUP: cannot initialize mutex for the main cgroup list");
+ collector_error("CGROUP: cannot initialize mutex for the main cgroup list");
goto exit;
}
@@ -4833,17 +4805,17 @@ void *cgroups_main(void *ptr) {
discovery_thread.exited = 0;
if (uv_mutex_init(&discovery_thread.mutex)) {
- error("CGROUP: cannot initialize mutex for discovery thread");
+ collector_error("CGROUP: cannot initialize mutex for discovery thread");
goto exit;
}
if (uv_cond_init(&discovery_thread.cond_var)) {
- error("CGROUP: cannot initialize conditional variable for discovery thread");
+ collector_error("CGROUP: cannot initialize conditional variable for discovery thread");
goto exit;
}
int error = uv_thread_create(&discovery_thread.thread, cgroup_discovery_worker, NULL);
if (error) {
- error("CGROUP: cannot create thread worker. uv_thread_create(): %s", uv_strerror(error));
+ collector_error("CGROUP: cannot create thread worker. uv_thread_create(): %s", uv_strerror(error));
goto exit;
}
uv_thread_set_name_np(discovery_thread.thread, "PLUGIN[cgroups]");
@@ -4853,11 +4825,11 @@ void *cgroups_main(void *ptr) {
usec_t step = cgroup_update_every * USEC_PER_SEC;
usec_t find_every = cgroup_check_for_new_every * USEC_PER_SEC, find_dt = 0;
- while(!netdata_exit) {
+ while(service_running(SERVICE_COLLECTORS)) {
worker_is_idle();
usec_t hb_dt = heartbeat_next(&hb, step);
- if(unlikely(netdata_exit)) break;
+ if(unlikely(!service_running(SERVICE_COLLECTORS))) break;
find_dt += hb_dt;
if (unlikely(find_dt >= find_every || (!is_inside_k8s && cgroups_check))) {
@@ -4872,9 +4844,11 @@ void *cgroups_main(void *ptr) {
worker_is_busy(WORKER_CGROUPS_READ);
read_all_discovered_cgroups(cgroup_root);
+ if(unlikely(!service_running(SERVICE_COLLECTORS))) break;
worker_is_busy(WORKER_CGROUPS_CHART);
update_cgroup_charts(cgroup_update_every);
+ if(unlikely(!service_running(SERVICE_COLLECTORS))) break;
worker_is_idle();
uv_mutex_unlock(&cgroup_root_mutex);
diff --git a/collectors/charts.d.plugin/README.md b/collectors/charts.d.plugin/README.md
index 06f4af1e..092a3f02 100644
--- a/collectors/charts.d.plugin/README.md
+++ b/collectors/charts.d.plugin/README.md
@@ -1,6 +1,10 @@
<!--
title: "charts.d.plugin"
-custom_edit_url: https://github.com/netdata/netdata/edit/master/collectors/charts.d.plugin/README.md
+custom_edit_url: "https://github.com/netdata/netdata/edit/master/collectors/charts.d.plugin/README.md"
+sidebar_label: "charts.d.plugin"
+learn_status: "Published"
+learn_topic_type: "Tasks"
+learn_rel_path: "Developers/Collectors"
-->
# charts.d.plugin
@@ -60,11 +64,11 @@ For a module called `X`, the following criteria must be met:
the collector cannot be used).
- `X_create()` - creates the Netdata charts, following the standard Netdata plugin guides as described in
- **[External Plugins](/collectors/plugins.d/README.md)** (commands `CHART` and `DIMENSION`).
+ **[External Plugins](https://github.com/netdata/netdata/blob/master/collectors/plugins.d/README.md)** (commands `CHART` and `DIMENSION`).
The return value does matter: 0 = OK, 1 = FAILED.
- `X_update()` - collects the values for the defined charts, following the standard Netdata plugin guides
- as described in **[External Plugins](/collectors/plugins.d/README.md)** (commands `BEGIN`, `SET`, `END`).
+ as described in **[External Plugins](https://github.com/netdata/netdata/blob/master/collectors/plugins.d/README.md)** (commands `BEGIN`, `SET`, `END`).
The return value also matters: 0 = OK, 1 = FAILED.
5. The following global variables are available to be set:
@@ -72,7 +76,7 @@ For a module called `X`, the following criteria must be met:
The module script may use more functions or variables. But all of them must begin with `X_`.
-The standard Netdata plugin variables are also available (check **[External Plugins](/collectors/plugins.d/README.md)**).
+The standard Netdata plugin variables are also available (check **[External Plugins](https://github.com/netdata/netdata/blob/master/collectors/plugins.d/README.md)**).
### X_check()
@@ -86,7 +90,7 @@ connect to a local mysql database to find out if it can read the values it needs
### X_create()
The purpose of the BASH function `X_create()` is to create the charts and dimensions using the standard Netdata
-plugin guides (**[External Plugins](/collectors/plugins.d/README.md)**).
+plugin guides (**[External Plugins](https://github.com/netdata/netdata/blob/master/collectors/plugins.d/README.md)**).
`X_create()` will be called just once and only after `X_check()` was successful.
You can however call it yourself when there is need for it (for example to add a new dimension to an existing chart).
@@ -96,7 +100,7 @@ A non-zero return value will disable the collector.
### X_update()
`X_update()` will be called repeatedly every `X_update_every` seconds, to collect new values and send them to Netdata,
-following the Netdata plugin guides (**[External Plugins](/collectors/plugins.d/README.md)**).
+following the Netdata plugin guides (**[External Plugins](https://github.com/netdata/netdata/blob/master/collectors/plugins.d/README.md)**).
The function will be called with one parameter: microseconds since the last time it was run. This value should be
appended to the `BEGIN` statement of every chart updated by the collector script.
diff --git a/collectors/charts.d.plugin/ap/README.md b/collectors/charts.d.plugin/ap/README.md
index a7953a54..03ab6d13 100644
--- a/collectors/charts.d.plugin/ap/README.md
+++ b/collectors/charts.d.plugin/ap/README.md
@@ -1,7 +1,10 @@
<!--
title: "Access point monitoring with Netdata"
-custom_edit_url: https://github.com/netdata/netdata/edit/master/collectors/charts.d.plugin/ap/README.md
+custom_edit_url: "https://github.com/netdata/netdata/edit/master/collectors/charts.d.plugin/ap/README.md"
sidebar_label: "Access points"
+learn_status: "Published"
+learn_topic_type: "References"
+learn_rel_path: "References/Collectors references/Remotes/Devices"
-->
# Access point monitoring with Netdata
@@ -83,7 +86,7 @@ Station 40:b8:37:5a:ed:5e (on wlan0)
## Configuration
Edit the `charts.d/ap.conf` configuration file using `edit-config` from the Netdata [config
-directory](/docs/configure/nodes.md), which is typically at `/etc/netdata`.
+directory](https://github.com/netdata/netdata/blob/master/docs/configure/nodes.md), which is typically at `/etc/netdata`.
```bash
cd /etc/netdata # Replace this path with your Netdata config directory, if different
diff --git a/collectors/charts.d.plugin/apcupsd/README.md b/collectors/charts.d.plugin/apcupsd/README.md
index f1aebf97..602977be 100644
--- a/collectors/charts.d.plugin/apcupsd/README.md
+++ b/collectors/charts.d.plugin/apcupsd/README.md
@@ -1,7 +1,10 @@
<!--
title: "APC UPS monitoring with Netdata"
-custom_edit_url: https://github.com/netdata/netdata/edit/master/collectors/charts.d.plugin/apcupsd/README.md
+custom_edit_url: "https://github.com/netdata/netdata/edit/master/collectors/charts.d.plugin/apcupsd/README.md"
sidebar_label: "APC UPS"
+learn_status: "Published"
+learn_topic_type: "References"
+learn_rel_path: "References/Collectors references/Remotes/Devices"
-->
# APC UPS monitoring with Netdata
@@ -11,7 +14,7 @@ Monitors different APC UPS models and retrieves status information using `apcacc
## Configuration
Edit the `charts.d/apcupsd.conf` configuration file using `edit-config` from the Netdata [config
-directory](/docs/configure/nodes.md), which is typically at `/etc/netdata`.
+directory](https://github.com/netdata/netdata/blob/master/docs/configure/nodes.md), which is typically at `/etc/netdata`.
```bash
cd /etc/netdata # Replace this path with your Netdata config directory, if different
diff --git a/collectors/charts.d.plugin/example/README.md b/collectors/charts.d.plugin/example/README.md
index 77446b27..d5faaabf 100644
--- a/collectors/charts.d.plugin/example/README.md
+++ b/collectors/charts.d.plugin/example/README.md
@@ -1,6 +1,10 @@
<!--
title: "Example"
-custom_edit_url: https://github.com/netdata/netdata/edit/master/collectors/charts.d.plugin/example/README.md
+custom_edit_url: "https://github.com/netdata/netdata/edit/master/collectors/charts.d.plugin/example/README.md"
+sidebar_label: "example-charts.d.plugin"
+learn_status: "Published"
+learn_topic_type: "References"
+learn_rel_path: "References/Collectors references/Mock Collectors"
-->
# Example
diff --git a/collectors/charts.d.plugin/libreswan/README.md b/collectors/charts.d.plugin/libreswan/README.md
index 41c4e24c..7c4eabcf 100644
--- a/collectors/charts.d.plugin/libreswan/README.md
+++ b/collectors/charts.d.plugin/libreswan/README.md
@@ -1,7 +1,10 @@
<!--
title: "Libreswan IPSec tunnel monitoring with Netdata"
-custom_edit_url: https://github.com/netdata/netdata/edit/master/collectors/charts.d.plugin/libreswan/README.md
+custom_edit_url: "https://github.com/netdata/netdata/edit/master/collectors/charts.d.plugin/libreswan/README.md"
sidebar_label: "Libreswan IPSec tunnels"
+learn_status: "Published"
+learn_topic_type: "References"
+learn_rel_path: "References/Collectors references/Networking"
-->
# Libreswan IPSec tunnel monitoring with Netdata
@@ -22,7 +25,7 @@ The following charts are created, **per tunnel**:
## Configuration
Edit the `charts.d/libreswan.conf` configuration file using `edit-config` from the Netdata [config
-directory](/docs/configure/nodes.md), which is typically at `/etc/netdata`.
+directory](https://github.com/netdata/netdata/blob/master/docs/configure/nodes.md), which is typically at `/etc/netdata`.
```bash
cd /etc/netdata # Replace this path with your Netdata config directory, if different
diff --git a/collectors/charts.d.plugin/nut/README.md b/collectors/charts.d.plugin/nut/README.md
index 69d7622c..7bb8a550 100644
--- a/collectors/charts.d.plugin/nut/README.md
+++ b/collectors/charts.d.plugin/nut/README.md
@@ -1,7 +1,10 @@
<!--
title: "UPS/PDU monitoring with Netdata"
-custom_edit_url: https://github.com/netdata/netdata/edit/master/collectors/charts.d.plugin/nut/README.md
+custom_edit_url: "https://github.com/netdata/netdata/edit/master/collectors/charts.d.plugin/nut/README.md"
sidebar_label: "UPS/PDU"
+learn_status: "Published"
+learn_topic_type: "References"
+learn_rel_path: "References/Collectors references/Remotes/Devices"
-->
# UPS/PDU monitoring with Netdata
@@ -51,7 +54,7 @@ The following charts will be created:
## Configuration
Edit the `charts.d/nut.conf` configuration file using `edit-config` from the Netdata [config
-directory](/docs/configure/nodes.md), which is typically at `/etc/netdata`.
+directory](https://github.com/netdata/netdata/blob/master/docs/configure/nodes.md), which is typically at `/etc/netdata`.
```bash
cd /etc/netdata # Replace this path with your Netdata config directory, if different
diff --git a/collectors/charts.d.plugin/nut/nut.chart.sh b/collectors/charts.d.plugin/nut/nut.chart.sh
index 2f7e3f33..7c32b6dd 100644
--- a/collectors/charts.d.plugin/nut/nut.chart.sh
+++ b/collectors/charts.d.plugin/nut/nut.chart.sh
@@ -81,43 +81,46 @@ nut_create() {
for x in "${nut_ids[@]}"; do
cat << EOF
-CHART nut_$x.charge '' "UPS Charge" "percentage" ups nut.charge area $((nut_priority + 1)) $nut_update_every
+CHART nut_$x.charge '' "UPS Charge" "percentage" ups nut.charge area $((nut_priority + 2)) $nut_update_every
DIMENSION battery_charge charge absolute 1 100
-CHART nut_$x.runtime '' "UPS Runtime" "seconds" ups nut.runtime area $((nut_priority + 2)) $nut_update_every
+CHART nut_$x.runtime '' "UPS Runtime" "seconds" ups nut.runtime area $((nut_priority + 3)) $nut_update_every
DIMENSION battery_runtime runtime absolute 1 100
-CHART nut_$x.battery_voltage '' "UPS Battery Voltage" "Volts" ups nut.battery.voltage line $((nut_priority + 3)) $nut_update_every
+CHART nut_$x.battery_voltage '' "UPS Battery Voltage" "Volts" ups nut.battery.voltage line $((nut_priority + 4)) $nut_update_every
DIMENSION battery_voltage voltage absolute 1 100
DIMENSION battery_voltage_high high absolute 1 100
DIMENSION battery_voltage_low low absolute 1 100
DIMENSION battery_voltage_nominal nominal absolute 1 100
-CHART nut_$x.input_voltage '' "UPS Input Voltage" "Volts" input nut.input.voltage line $((nut_priority + 4)) $nut_update_every
+CHART nut_$x.input_voltage '' "UPS Input Voltage" "Volts" input nut.input.voltage line $((nut_priority + 5)) $nut_update_every
DIMENSION input_voltage voltage absolute 1 100
DIMENSION input_voltage_fault fault absolute 1 100
DIMENSION input_voltage_nominal nominal absolute 1 100
-CHART nut_$x.input_current '' "UPS Input Current" "Ampere" input nut.input.current line $((nut_priority + 5)) $nut_update_every
+CHART nut_$x.input_current '' "UPS Input Current" "Ampere" input nut.input.current line $((nut_priority + 6)) $nut_update_every
DIMENSION input_current_nominal nominal absolute 1 100
-CHART nut_$x.input_frequency '' "UPS Input Frequency" "Hz" input nut.input.frequency line $((nut_priority + 6)) $nut_update_every
+CHART nut_$x.input_frequency '' "UPS Input Frequency" "Hz" input nut.input.frequency line $((nut_priority + 7)) $nut_update_every
DIMENSION input_frequency frequency absolute 1 100
DIMENSION input_frequency_nominal nominal absolute 1 100
-CHART nut_$x.output_voltage '' "UPS Output Voltage" "Volts" output nut.output.voltage line $((nut_priority + 7)) $nut_update_every
+CHART nut_$x.output_voltage '' "UPS Output Voltage" "Volts" output nut.output.voltage line $((nut_priority + 8)) $nut_update_every
DIMENSION output_voltage voltage absolute 1 100
CHART nut_$x.load '' "UPS Load" "percentage" ups nut.load area $((nut_priority)) $nut_update_every
DIMENSION load load absolute 1 100
-CHART nut_$x.temp '' "UPS Temperature" "temperature" ups nut.temperature line $((nut_priority + 8)) $nut_update_every
+CHART nut_$x.load_usage '' "UPS Load Usage" "Watts" ups nut.load_usage area $((nut_priority + 1)) $nut_update_every
+DIMENSION load_usage load_usage absolute 1 100
+
+CHART nut_$x.temp '' "UPS Temperature" "temperature" ups nut.temperature line $((nut_priority + 9)) $nut_update_every
DIMENSION temp temp absolute 1 100
EOF
if [ "${nut_clients_chart}" = "1" ]; then
cat << EOF2
-CHART nut_$x.clients '' "UPS Connected Clients" "clients" ups nut.clients area $((nut_priority + 9)) $nut_update_every
+CHART nut_$x.clients '' "UPS Connected Clients" "clients" ups nut.clients area $((nut_priority + 10)) $nut_update_every
DIMENSION clients '' absolute 1 1
EOF2
fi
@@ -154,6 +157,8 @@ BEGIN {
input_frequency_nominal = 0;
output_voltage = 0;
load = 0;
+ load_usage = 0;
+ nompower = 0;
temp = 0;
client = 0;
do_clients = ${nut_clients_chart};
@@ -172,16 +177,19 @@ BEGIN {
/^input.frequency.nominal: .*/ { input_frequency_nominal = \$2 * 100 };
/^output.voltage: .*/ { output_voltage = \$2 * 100 };
/^ups.load: .*/ { load = \$2 * 100 };
+/^ups.realpower.nominal: .*/ { nompower = \$2 };
/^ups.temperature: .*/ { temp = \$2 * 100 };
/^ups.connected_clients: .*/ { clients = \$2 };
END {
+ { load_usage = nompower * load / 100 };
+
print \"BEGIN nut_$x.charge $1\";
print \"SET battery_charge = \" battery_charge;
print \"END\"
- print \"BEGIN nut_$x.runtime $1\";
- print \"SET battery_runtime = \" battery_runtime;
- print \"END\"
+ print \"BEGIN nut_$x.runtime $1\";
+ print \"SET battery_runtime = \" battery_runtime;
+ print \"END\"
print \"BEGIN nut_$x.battery_voltage $1\";
print \"SET battery_voltage = \" battery_voltage;
@@ -213,6 +221,10 @@ END {
print \"SET load = \" load;
print \"END\"
+ print \"BEGIN nut_$x.load_usage $1\";
+ print \"SET load_usage = \" load_usage;
+ print \"END\"
+
print \"BEGIN nut_$x.temp $1\";
print \"SET temp = \" temp;
print \"END\"
diff --git a/collectors/charts.d.plugin/opensips/README.md b/collectors/charts.d.plugin/opensips/README.md
index b08d1923..74624c7f 100644
--- a/collectors/charts.d.plugin/opensips/README.md
+++ b/collectors/charts.d.plugin/opensips/README.md
@@ -1,7 +1,10 @@
<!--
title: "OpenSIPS monitoring with Netdata"
-custom_edit_url: https://github.com/netdata/netdata/edit/master/collectors/charts.d.plugin/opensips/README.md
+custom_edit_url: "https://github.com/netdata/netdata/edit/master/collectors/charts.d.plugin/opensips/README.md"
sidebar_label: "OpenSIPS"
+learn_status: "Published"
+learn_topic_type: "References"
+learn_rel_path: "References/Collectors references/Networking"
-->
# OpenSIPS monitoring with Netdata
@@ -9,7 +12,7 @@ sidebar_label: "OpenSIPS"
## Configuration
Edit the `charts.d/opensips.conf` configuration file using `edit-config` from the Netdata [config
-directory](/docs/configure/nodes.md), which is typically at `/etc/netdata`.
+directory](https://github.com/netdata/netdata/blob/master/docs/configure/nodes.md), which is typically at `/etc/netdata`.
```bash
cd /etc/netdata # Replace this path with your Netdata config directory, if different
diff --git a/collectors/charts.d.plugin/sensors/README.md b/collectors/charts.d.plugin/sensors/README.md
index 1b98b1a7..142ae14a 100644
--- a/collectors/charts.d.plugin/sensors/README.md
+++ b/collectors/charts.d.plugin/sensors/README.md
@@ -1,6 +1,10 @@
<!--
title: "Linux machine sensors monitoring with Netdata"
-custom_edit_url: https://github.com/netdata/netdata/edit/master/collectors/charts.d.plugin/sensors/README.md
+custom_edit_url: "https://github.com/netdata/netdata/edit/master/collectors/charts.d.plugin/sensors/README.md"
+sidebar_label: "lm-sensors"
+learn_status: "Published"
+learn_topic_type: "References"
+learn_rel_path: "References/Collectors references/Devices"
-->
# Linux machine sensors monitoring with Netdata
@@ -27,7 +31,7 @@ One chart for every sensor chip found and each of the above will be created.
## Enable the collector
The `sensors` collector is disabled by default. To enable it, edit the `charts.d.conf` file using `edit-config` from the
-Netdata [config directory](/docs/configure/nodes.md), which is typically at `/etc/netdata`.
+Netdata [config directory](https://github.com/netdata/netdata/blob/master/docs/configure/nodes.md), which is typically at `/etc/netdata`.
```bash
cd /etc/netdata # Replace this path with your Netdata config directory, if different
@@ -44,7 +48,7 @@ sensors=force
## Configuration
Edit the `charts.d/sensors.conf` configuration file using `edit-config` from the
-Netdata [config directory](/docs/configure/nodes.md), which is typically at `/etc/netdata`.
+Netdata [config directory](https://github.com/netdata/netdata/blob/master/docs/configure/nodes.md), which is typically at `/etc/netdata`.
```bash
cd /etc/netdata # Replace this path with your Netdata config directory, if different
diff --git a/collectors/checks.plugin/README.md b/collectors/checks.plugin/README.md
new file mode 100644
index 00000000..801f2775
--- /dev/null
+++ b/collectors/checks.plugin/README.md
@@ -0,0 +1,12 @@
+<!--
+title: "checks.plugin"
+custom_edit_url: "https://github.com/netdata/netdata/edit/master/collectors/checks.plugin/README.md"
+sidebar_label: "checks.plugin"
+learn_status: "Unpublished"
+-->
+
+# checks.plugin
+
+A debugging plugin (by default it is disabled)
+
+
diff --git a/collectors/cups.plugin/README.md b/collectors/cups.plugin/README.md
index f3b2a28d..0658cc8b 100644
--- a/collectors/cups.plugin/README.md
+++ b/collectors/cups.plugin/README.md
@@ -1,6 +1,10 @@
<!--
-title: "cups.plugin"
-custom_edit_url: https://github.com/netdata/netdata/edit/master/collectors/cups.plugin/README.md
+title: "Printers (cups.plugin)"
+custom_edit_url: "https://github.com/netdata/netdata/edit/master/collectors/cups.plugin/README.md"
+sidebar_label: "cups.plugin"
+learn_status: "Published"
+learn_topic_type: "References"
+learn_rel_path: "References/Collectors references/Remotes/Devices"
-->
# cups.plugin
@@ -11,7 +15,7 @@ custom_edit_url: https://github.com/netdata/netdata/edit/master/collectors/cups.
This plugin needs a running local CUPS daemon (`cupsd`). This plugin does not need any configuration. Supports cups since version 1.7.
-If you installed Netdata using our native packages, you will have to additionaly install `netdata-plugin-cups` to use this plugin for data collection. It is not installed by default due to the large number of dependencies it requires.
+If you installed Netdata using our native packages, you will have to additionally install `netdata-plugin-cups` to use this plugin for data collection. It is not installed by default due to the large number of dependencies it requires.
## Charts
diff --git a/collectors/cups.plugin/cups_plugin.c b/collectors/cups.plugin/cups_plugin.c
index 9a200c31..b9d91c85 100644
--- a/collectors/cups.plugin/cups_plugin.c
+++ b/collectors/cups.plugin/cups_plugin.c
@@ -222,6 +222,7 @@ void reset_metrics() {
}
int main(int argc, char **argv) {
+ stderror = stderr;
clocks_init();
// ------------------------------------------------------------------------
diff --git a/collectors/diskspace.plugin/README.md b/collectors/diskspace.plugin/README.md
index c037a0b1..6d1ec7ca 100644
--- a/collectors/diskspace.plugin/README.md
+++ b/collectors/diskspace.plugin/README.md
@@ -1,7 +1,11 @@
<!--
-title: "diskspace.plugin"
+title: "Monitor disk (diskspace.plugin)"
description: "Monitor the disk usage space of mounted disks in real-time with the Netdata Agent, plus preconfigured alarms for disks at risk of filling up."
-custom_edit_url: https://github.com/netdata/netdata/edit/master/collectors/diskspace.plugin/README.md
+custom_edit_url: "https://github.com/netdata/netdata/edit/master/collectors/diskspace.plugin/README.md"
+sidebar_label: "Disks"
+learn_status: "Published"
+learn_topic_type: "References"
+learn_rel_path: "References/Collectors references/System metrics"
-->
# diskspace.plugin
@@ -38,6 +42,6 @@ Charts can be enabled/disabled for every mount separately:
# inodes usage = auto
```
-> for disks performance monitoring, see the `proc` plugin, [here](/collectors/proc.plugin/README.md#monitoring-disks)
+> for disks performance monitoring, see the `proc` plugin, [here](https://github.com/netdata/netdata/blob/master/collectors/proc.plugin/README.md#monitoring-disks)
diff --git a/collectors/diskspace.plugin/plugin_diskspace.c b/collectors/diskspace.plugin/plugin_diskspace.c
index e806a336..743612ff 100644
--- a/collectors/diskspace.plugin/plugin_diskspace.c
+++ b/collectors/diskspace.plugin/plugin_diskspace.c
@@ -3,7 +3,6 @@
#include "../proc.plugin/plugin_proc.h"
#define PLUGIN_DISKSPACE_NAME "diskspace.plugin"
-#define THREAD_DISKSPACE_SLOW_NAME "PLUGIN[diskspace slow]"
#define DEFAULT_EXCLUDED_PATHS "/proc/* /sys/* /var/run/user/* /run/user/* /snap/* /var/lib/docker/*"
#define DEFAULT_EXCLUDED_FILESYSTEMS "*gvfs *gluster* *s3fs *ipfs *davfs2 *httpfs *sshfs *gdfs *moosefs fusectl autofs"
@@ -182,7 +181,7 @@ static void calculate_values_and_show_charts(
#ifdef NETDATA_INTERNAL_CHECKS
if(unlikely(btotal != bavail + breserved_root + bused))
- error("DISKSPACE: disk block statistics for '%s' (disk '%s') do not sum up: total = %llu, available = %llu, reserved = %llu, used = %llu", mi->mount_point, disk, (unsigned long long)btotal, (unsigned long long)bavail, (unsigned long long)breserved_root, (unsigned long long)bused);
+ collector_error("DISKSPACE: disk block statistics for '%s' (disk '%s') do not sum up: total = %llu, available = %llu, reserved = %llu, used = %llu", mi->mount_point, disk, (unsigned long long)btotal, (unsigned long long)bavail, (unsigned long long)breserved_root, (unsigned long long)bused);
#endif
// --------------------------------------------------------------------------
@@ -201,7 +200,7 @@ static void calculate_values_and_show_charts(
#ifdef NETDATA_INTERNAL_CHECKS
if(unlikely(btotal != bavail + breserved_root + bused))
- error("DISKSPACE: disk inode statistics for '%s' (disk '%s') do not sum up: total = %llu, available = %llu, reserved = %llu, used = %llu", mi->mount_point, disk, (unsigned long long)ftotal, (unsigned long long)favail, (unsigned long long)freserved_root, (unsigned long long)fused);
+ collector_error("DISKSPACE: disk inode statistics for '%s' (disk '%s') do not sum up: total = %llu, available = %llu, reserved = %llu, used = %llu", mi->mount_point, disk, (unsigned long long)ftotal, (unsigned long long)favail, (unsigned long long)freserved_root, (unsigned long long)fused);
#endif
int rendered = 0;
@@ -320,7 +319,7 @@ static inline void do_disk_space_stats(struct mountinfo *mi, int update_every) {
, SIMPLE_PATTERN_EXACT
);
- dict_mountpoints = dictionary_create(DICT_OPTION_NONE);
+ dict_mountpoints = dictionary_create_advanced(DICT_OPTION_NONE, &dictionary_stats_category_collectors, 0);
}
struct mount_point_metadata *m = dictionary_get(dict_mountpoints, mi->mount_point);
@@ -349,23 +348,23 @@ static inline void do_disk_space_stats(struct mountinfo *mi, int update_every) {
struct stat bs;
if(stat(mi->mount_point, &bs) == -1) {
- error("DISKSPACE: Cannot stat() mount point '%s' (disk '%s', filesystem '%s', root '%s')."
- , mi->mount_point
- , disk
- , mi->filesystem?mi->filesystem:""
- , mi->root?mi->root:""
- );
+ collector_error("DISKSPACE: Cannot stat() mount point '%s' (disk '%s', filesystem '%s', root '%s')."
+ , mi->mount_point
+ , disk
+ , mi->filesystem?mi->filesystem:""
+ , mi->root?mi->root:""
+ );
def_space = CONFIG_BOOLEAN_NO;
def_inodes = CONFIG_BOOLEAN_NO;
}
else {
if((bs.st_mode & S_IFMT) != S_IFDIR) {
- error("DISKSPACE: Mount point '%s' (disk '%s', filesystem '%s', root '%s') is not a directory."
- , mi->mount_point
- , disk
- , mi->filesystem?mi->filesystem:""
- , mi->root?mi->root:""
- );
+ collector_error("DISKSPACE: Mount point '%s' (disk '%s', filesystem '%s', root '%s') is not a directory."
+ , mi->mount_point
+ , disk
+ , mi->filesystem?mi->filesystem:""
+ , mi->root?mi->root:""
+ );
def_space = CONFIG_BOOLEAN_NO;
def_inodes = CONFIG_BOOLEAN_NO;
}
@@ -431,12 +430,12 @@ static inline void do_disk_space_stats(struct mountinfo *mi, int update_every) {
if (statvfs(mi->mount_point, &buff_statvfs) < 0) {
if(!m->shown_error) {
- error("DISKSPACE: failed to statvfs() mount point '%s' (disk '%s', filesystem '%s', root '%s')"
- , mi->mount_point
- , disk
- , mi->filesystem?mi->filesystem:""
- , mi->root?mi->root:""
- );
+ collector_error("DISKSPACE: failed to statvfs() mount point '%s' (disk '%s', filesystem '%s', root '%s')"
+ , mi->mount_point
+ , disk
+ , mi->filesystem?mi->filesystem:""
+ , mi->root?mi->root:""
+ );
m->shown_error = 1;
}
return;
@@ -464,12 +463,12 @@ static inline void do_slow_disk_space_stats(struct basic_mountinfo *mi, int upda
struct statvfs buff_statvfs;
if (statvfs(mi->mount_point, &buff_statvfs) < 0) {
if(!m->shown_error) {
- error("DISKSPACE: failed to statvfs() mount point '%s' (disk '%s', filesystem '%s', root '%s')"
- , mi->mount_point
- , mi->persistent_id
- , mi->filesystem?mi->filesystem:""
- , mi->root?mi->root:""
- );
+ collector_error("DISKSPACE: failed to statvfs() mount point '%s' (disk '%s', filesystem '%s', root '%s')"
+ , mi->mount_point
+ , mi->persistent_id
+ , mi->filesystem?mi->filesystem:""
+ , mi->root?mi->root:""
+ );
m->shown_error = 1;
}
return;
@@ -483,7 +482,7 @@ static void diskspace_slow_worker_cleanup(void *ptr)
{
UNUSED(ptr);
- info("cleaning up...");
+ collector_info("cleaning up...");
worker_unregister();
}
@@ -515,7 +514,7 @@ void *diskspace_slow_worker(void *ptr)
heartbeat_t hb;
heartbeat_init(&hb);
- while(!netdata_exit) {
+ while(service_running(SERVICE_COLLECTORS)) {
worker_is_idle();
heartbeat_next(&hb, USEC_PER_SEC);
@@ -530,7 +529,7 @@ void *diskspace_slow_worker(void *ptr)
if (!dict_mountpoints)
continue;
- if(unlikely(netdata_exit)) break;
+ if(unlikely(!service_running(SERVICE_COLLECTORS))) break;
// --------------------------------------------------------------------------
// disk space metrics
@@ -547,10 +546,10 @@ void *diskspace_slow_worker(void *ptr)
for(bmi = slow_mountinfo_root; bmi; bmi = bmi->next) {
do_slow_disk_space_stats(bmi, slow_update_every);
- if(unlikely(netdata_exit)) break;
+ if(unlikely(!service_running(SERVICE_COLLECTORS))) break;
}
- if(unlikely(netdata_exit)) break;
+ if(unlikely(!service_running(SERVICE_COLLECTORS))) break;
worker_is_busy(WORKER_JOB_SLOW_CLEANUP);
@@ -584,7 +583,7 @@ static void diskspace_main_cleanup(void *ptr) {
struct netdata_static_thread *static_thread = (struct netdata_static_thread *)ptr;
static_thread->enabled = NETDATA_MAIN_THREAD_EXITING;
- info("cleaning up...");
+ collector_info("cleaning up...");
if (diskspace_slow_thread) {
netdata_thread_join(*diskspace_slow_thread, NULL);
@@ -632,7 +631,7 @@ void *diskspace_main(void *ptr) {
netdata_thread_create(
diskspace_slow_thread,
- THREAD_DISKSPACE_SLOW_NAME,
+ "P[diskspace slow]",
NETDATA_THREAD_OPTION_JOINABLE,
diskspace_slow_worker,
&slow_worker_data);
@@ -640,11 +639,11 @@ void *diskspace_main(void *ptr) {
usec_t step = update_every * USEC_PER_SEC;
heartbeat_t hb;
heartbeat_init(&hb);
- while(!netdata_exit) {
+ while(service_running(SERVICE_COLLECTORS)) {
worker_is_idle();
/* usec_t hb_dt = */ heartbeat_next(&hb, step);
- if(unlikely(netdata_exit)) break;
+ if(unlikely(!service_running(SERVICE_COLLECTORS))) break;
// --------------------------------------------------------------------------
// this is smart enough not to reload it every time
@@ -671,11 +670,11 @@ void *diskspace_main(void *ptr) {
worker_is_busy(WORKER_JOB_MOUNTPOINT);
do_disk_space_stats(mi, update_every);
- if(unlikely(netdata_exit)) break;
+ if(unlikely(!service_running(SERVICE_COLLECTORS))) break;
}
netdata_mutex_unlock(&slow_mountinfo_mutex);
- if(unlikely(netdata_exit)) break;
+ if(unlikely(!service_running(SERVICE_COLLECTORS))) break;
if(dict_mountpoints) {
worker_is_busy(WORKER_JOB_CLEANUP);
diff --git a/collectors/ebpf.plugin/README.md b/collectors/ebpf.plugin/README.md
index 7762ed34..deedf4d7 100644
--- a/collectors/ebpf.plugin/README.md
+++ b/collectors/ebpf.plugin/README.md
@@ -1,9 +1,11 @@
<!--
-title: "eBPF monitoring with Netdata"
-description: "Use Netdata's extended Berkeley Packet Filter (eBPF) collector to monitor kernel-level metrics about your
-complex applications with per-second granularity."
-custom_edit_url: https://github.com/netdata/netdata/edit/master/collectors/ebpf.plugin/README.md
-sidebar_label: "eBPF"
+title: "Kernel traces/metrics (eBPF) monitoring with Netdata"
+description: "Use Netdata's extended Berkeley Packet Filter (eBPF) collector to monitor kernel-level metrics about yourcomplex applications with per-second granularity."
+custom_edit_url: "https://github.com/netdata/netdata/edit/master/collectors/ebpf.plugin/README.md"
+sidebar_label: "Kernel traces/metrics (eBPF)"
+learn_status: "Published"
+learn_topic_type: "References"
+learn_rel_path: "References/Collectors references/System metrics"
-->
# eBPF monitoring with Netdata
@@ -13,7 +15,7 @@ The Netdata Agent provides many [eBPF](https://ebpf.io/what-is-ebpf/) programs t
> ❗ eBPF monitoring only works on Linux systems and with specific Linux kernels, including all kernels newer than `4.11.0`, and all kernels on CentOS 7.6 or later. For kernels older than `4.11.0`, improved support is in active development.
This document provides comprehensive details about the `ebpf.plugin`.
-For hands-on configuration and troubleshooting tips see our [tutorial on troubleshooting apps with eBPF metrics](/docs/guides/troubleshoot/monitor-debug-applications-ebpf.md).
+For hands-on configuration and troubleshooting tips see our [tutorial on troubleshooting apps with eBPF metrics](https://github.com/netdata/netdata/blob/master/docs/guides/troubleshoot/monitor-debug-applications-ebpf.md).
<figure>
<img src="https://user-images.githubusercontent.com/1153921/74746434-ad6a1e00-5222-11ea-858a-a7882617ae02.png" alt="An example of VFS charts, made possible by the eBPF collector plugin" />
@@ -42,12 +44,12 @@ If your Agent is v1.22 or older, you may to enable the collector yourself.
To enable or disable the entire eBPF collector:
-1. Navigate to the [Netdata config directory](/docs/configure/nodes.md#the-netdata-config-directory).
+1. Navigate to the [Netdata config directory](https://github.com/netdata/netdata/blob/master/docs/configure/nodes.md#the-netdata-config-directory).
```bash
cd /etc/netdata
```
-2. Use the [`edit-config`](/docs/configure/nodes.md#use-edit-config-to-edit-configuration-files) script to edit `netdata.conf`.
+2. Use the [`edit-config`](https://github.com/netdata/netdata/blob/master/docs/configure/nodes.md#use-edit-config-to-edit-configuration-files) script to edit `netdata.conf`.
```bash
./edit-config netdata.conf
@@ -67,11 +69,11 @@ You can configure the eBPF collector's behavior to fine-tune which metrics you r
To edit the `ebpf.d.conf`:
-1. Navigate to the [Netdata config directory](/docs/configure/nodes.md#the-netdata-config-directory).
+1. Navigate to the [Netdata config directory](https://github.com/netdata/netdata/blob/master/docs/configure/nodes.md#the-netdata-config-directory).
```bash
cd /etc/netdata
```
-2. Use the [`edit-config`](/docs/configure/nodes.md#use-edit-config-to-edit-configuration-files) script to edit [`ebpf.d.conf`](https://github.com/netdata/netdata/blob/master/collectors/ebpf.plugin/ebpf.d.conf).
+2. Use the [`edit-config`](https://github.com/netdata/netdata/blob/master/docs/configure/nodes.md#use-edit-config-to-edit-configuration-files) script to edit [`ebpf.d.conf`](https://github.com/netdata/netdata/blob/master/collectors/ebpf.plugin/ebpf.d.conf).
```bash
./edit-config ebpf.d.conf
@@ -103,11 +105,10 @@ accepts the following values:
#### Integration with `apps.plugin`
The eBPF collector also creates charts for each running application through an integration with the
-[`apps.plugin`](/collectors/apps.plugin/README.md). This integration helps you understand how specific applications
+[`apps.plugin`](https://github.com/netdata/netdata/blob/master/collectors/apps.plugin/README.md). This integration helps you understand how specific applications
interact with the Linux kernel.
-If you want to _disable_ the integration with `apps.plugin` along with the above charts, change the setting `apps` to
-`no`.
+If you want to enable `apps.plugin` integration, change the "apps" setting to "yes".
```conf
[global]
@@ -122,7 +123,7 @@ it runs.
#### Integration with `cgroups.plugin`
The eBPF collector also creates charts for each cgroup through an integration with the
-[`cgroups.plugin`](/collectors/cgroups.plugin/README.md). This integration helps you understand how a specific cgroup
+[`cgroups.plugin`](https://github.com/netdata/netdata/blob/master/collectors/cgroups.plugin/README.md). This integration helps you understand how a specific cgroup
interacts with the Linux kernel.
The integration with `cgroups.plugin` is disabled by default to avoid creating overhead on your system. If you want to
@@ -244,7 +245,7 @@ The eBPF collector enables and runs the following eBPF programs by default:
You can also enable the following eBPF programs:
- `cachestat`: Netdata's eBPF data collector creates charts about the memory page cache. When the integration with
- [`apps.plugin`](/collectors/apps.plugin/README.md) is enabled, this collector creates charts for the whole host _and_
+ [`apps.plugin`](https://github.com/netdata/netdata/blob/master/collectors/apps.plugin/README.md) is enabled, this collector creates charts for the whole host _and_
for each application.
- `dcstat` : This eBPF program creates charts that show information about file access using directory cache. It appends
`kprobes` for `lookup_fast()` and `d_lookup()` to identify if files are inside directory cache, outside and files are
@@ -261,11 +262,11 @@ You can configure each thread of the eBPF data collector. This allows you to ove
To configure an eBPF thread:
-1. Navigate to the [Netdata config directory](/docs/configure/nodes.md#the-netdata-config-directory).
+1. Navigate to the [Netdata config directory](https://github.com/netdata/netdata/blob/master/docs/configure/nodes.md#the-netdata-config-directory).
```bash
cd /etc/netdata
```
-2. Use the [`edit-config`](/docs/configure/nodes.md#use-edit-config-to-edit-configuration-files) script to edit a thread configuration file. The following configuration files are available:
+2. Use the [`edit-config`](https://github.com/netdata/netdata/blob/master/docs/configure/nodes.md#use-edit-config-to-edit-configuration-files) script to edit a thread configuration file. The following configuration files are available:
- `network.conf`: Configuration for the [`network` thread](#network-configuration). This config file overwrites the global options and also
lets you specify which network the eBPF collector monitors.
@@ -304,7 +305,7 @@ You can configure the information shown on `outbound` and `inbound` charts with
When you define a `ports` setting, Netdata will collect network metrics for that specific port. For example, if you
write `ports = 19999`, Netdata will collect only connections for itself. The `hostnames` setting accepts
-[simple patterns](/libnetdata/simple_pattern/README.md). The `ports`, and `ips` settings accept negation (`!`) to deny
+[simple patterns](https://github.com/netdata/netdata/blob/master/libnetdata/simple_pattern/README.md). The `ports`, and `ips` settings accept negation (`!`) to deny
specific values or asterisk alone to define all values.
In the above example, Netdata will collect metrics for all ports between 1 and 443, with the exception of 53 (domain)
@@ -881,7 +882,7 @@ significantly increases kernel memory usage by several hundred MB.
If your node is experiencing high memory usage and there is no obvious culprit to be found in the `apps.mem` chart,
consider testing for high kernel memory usage by [disabling eBPF monitoring](#configuring-ebpfplugin). Next,
-[restart Netdata](/docs/configure/start-stop-restart.md) with `sudo systemctl restart netdata` to see if system memory
+[restart Netdata](https://github.com/netdata/netdata/blob/master/docs/configure/start-stop-restart.md) with `sudo systemctl restart netdata` to see if system memory
usage (see the `system.ram` chart) has dropped significantly.
Beginning with `v1.31`, kernel memory usage is configurable via the [`pid table size` setting](#ebpf-load-mode)
diff --git a/collectors/ebpf.plugin/ebpf.c b/collectors/ebpf.plugin/ebpf.c
index 00b53a57..67fe477c 100644
--- a/collectors/ebpf.plugin/ebpf.c
+++ b/collectors/ebpf.plugin/ebpf.c
@@ -483,6 +483,16 @@ static void ebpf_exit()
if (unlink(filename))
error("Cannot remove PID file %s", filename);
+#ifdef NETDATA_INTERNAL_CHECKS
+ error("Good bye world! I was PID %d", main_thread_id);
+#endif
+ printf("DISABLE\n");
+
+ if (shm_ebpf_cgroup.header) {
+ munmap(shm_ebpf_cgroup.header, shm_ebpf_cgroup.header->body_length);
+ shm_unlink(NETDATA_SHARED_MEMORY_EBPF_CGROUP_NAME);
+ }
+
exit(0);
}
@@ -534,7 +544,7 @@ static void ebpf_stop_threads(int sig)
pthread_mutex_unlock(&ebpf_exit_cleanup);
ebpf_exit_plugin = 1;
- usec_t max = 3 * USEC_PER_SEC, step = 100000;
+ usec_t max = USEC_PER_SEC, step = 100000;
while (i && max) {
max -= step;
sleep_usec(step);
@@ -548,32 +558,35 @@ static void ebpf_stop_threads(int sig)
pthread_mutex_unlock(&ebpf_exit_cleanup);
}
- //Unload threads(except sync and filesystem)
- pthread_mutex_lock(&ebpf_exit_cleanup);
- for (i = 0; ebpf_threads[i].name != NULL; i++) {
- if (ebpf_threads[i].enabled == NETDATA_THREAD_EBPF_STOPPED && i != EBPF_MODULE_FILESYSTEM_IDX &&
- i != EBPF_MODULE_SYNC_IDX)
- ebpf_unload_legacy_code(ebpf_modules[i].objects, ebpf_modules[i].probe_links);
- }
- pthread_mutex_unlock(&ebpf_exit_cleanup);
+ if (!i) {
+ //Unload threads(except sync and filesystem)
+ pthread_mutex_lock(&ebpf_exit_cleanup);
+ for (i = 0; ebpf_threads[i].name != NULL; i++) {
+ if (ebpf_threads[i].enabled == NETDATA_THREAD_EBPF_STOPPED && i != EBPF_MODULE_FILESYSTEM_IDX &&
+ i != EBPF_MODULE_SYNC_IDX)
+ ebpf_unload_legacy_code(ebpf_modules[i].objects, ebpf_modules[i].probe_links);
+ }
+ pthread_mutex_unlock(&ebpf_exit_cleanup);
- //Unload filesystem
- pthread_mutex_lock(&ebpf_exit_cleanup);
- if (ebpf_threads[EBPF_MODULE_FILESYSTEM_IDX].enabled == NETDATA_THREAD_EBPF_STOPPED) {
- for (i = 0; localfs[i].filesystem != NULL; i++) {
- ebpf_unload_legacy_code(localfs[i].objects, localfs[i].probe_links);
+ //Unload filesystem
+ pthread_mutex_lock(&ebpf_exit_cleanup);
+ if (ebpf_threads[EBPF_MODULE_FILESYSTEM_IDX].enabled == NETDATA_THREAD_EBPF_STOPPED) {
+ for (i = 0; localfs[i].filesystem != NULL; i++) {
+ ebpf_unload_legacy_code(localfs[i].objects, localfs[i].probe_links);
+ }
}
- }
- pthread_mutex_unlock(&ebpf_exit_cleanup);
+ pthread_mutex_unlock(&ebpf_exit_cleanup);
- //Unload Sync
- pthread_mutex_lock(&ebpf_exit_cleanup);
- if (ebpf_threads[EBPF_MODULE_SYNC_IDX].enabled == NETDATA_THREAD_EBPF_STOPPED) {
- for (i = 0; local_syscalls[i].syscall != NULL; i++) {
- ebpf_unload_legacy_code(local_syscalls[i].objects, local_syscalls[i].probe_links);
+ //Unload Sync
+ pthread_mutex_lock(&ebpf_exit_cleanup);
+ if (ebpf_threads[EBPF_MODULE_SYNC_IDX].enabled == NETDATA_THREAD_EBPF_STOPPED) {
+ for (i = 0; local_syscalls[i].syscall != NULL; i++) {
+ ebpf_unload_legacy_code(local_syscalls[i].objects, local_syscalls[i].probe_links);
+ }
}
+ pthread_mutex_unlock(&ebpf_exit_cleanup);
+
}
- pthread_mutex_unlock(&ebpf_exit_cleanup);
ebpf_exit();
}
@@ -1317,7 +1330,7 @@ static void read_local_addresses()
}
}
- fill_ip_list((family == AF_INET)?&network_viewer_opt.ipv4_local_ip:&network_viewer_opt.ipv6_local_ip,
+ ebpf_fill_ip_list((family == AF_INET)?&network_viewer_opt.ipv4_local_ip:&network_viewer_opt.ipv6_local_ip,
w,
"selector");
}
@@ -1520,13 +1533,8 @@ static void read_collector_values(int *disable_apps, int *disable_cgroups,
enabled = appconfig_get_boolean(&collector_config, EBPF_PROGRAMS_SECTION,
ebpf_modules[EBPF_MODULE_SOCKET_IDX].config_name,
CONFIG_BOOLEAN_NO);
-
if (enabled) {
ebpf_enable_chart(EBPF_MODULE_SOCKET_IDX, *disable_apps, *disable_cgroups);
- // Read network viewer section if network viewer is enabled
- // This is kept here to keep backward compatibility
- parse_network_viewer_section(&collector_config);
- parse_service_name_section(&collector_config);
started++;
}
@@ -1536,7 +1544,17 @@ static void read_collector_values(int *disable_apps, int *disable_cgroups,
if (!enabled)
enabled = appconfig_get_boolean(&collector_config, EBPF_PROGRAMS_SECTION, "network connections",
CONFIG_BOOLEAN_NO);
- ebpf_modules[EBPF_MODULE_SOCKET_IDX].optional = (int)enabled;
+ network_viewer_opt.enabled = enabled;
+ if (enabled) {
+ if (!ebpf_modules[EBPF_MODULE_SOCKET_IDX].enabled)
+ ebpf_enable_chart(EBPF_MODULE_SOCKET_IDX, *disable_apps, *disable_cgroups);
+
+ // Read network viewer section if network viewer is enabled
+ // This is kept here to keep backward compatibility
+ parse_network_viewer_section(&collector_config);
+ parse_service_name_section(&collector_config);
+ started++;
+ }
enabled = appconfig_get_boolean(&collector_config, EBPF_PROGRAMS_SECTION, "cachestat",
CONFIG_BOOLEAN_NO);
@@ -1642,8 +1660,10 @@ static void read_collector_values(int *disable_apps, int *disable_cgroups,
ebpf_enable_all_charts(*disable_apps, *disable_cgroups);
// Read network viewer section
// This is kept here to keep backward compatibility
- parse_network_viewer_section(&collector_config);
- parse_service_name_section(&collector_config);
+ if (network_viewer_opt.enabled) {
+ parse_network_viewer_section(&collector_config);
+ parse_service_name_section(&collector_config);
+ }
}
}
@@ -2158,6 +2178,7 @@ static void ebpf_manage_pid(pid_t pid)
*/
int main(int argc, char **argv)
{
+ stderror = stderr;
clocks_init();
main_thread_id = gettid();
@@ -2237,13 +2258,26 @@ int main(int argc, char **argv)
}
}
- usec_t step = EBPF_DEFAULT_UPDATE_EVERY * USEC_PER_SEC;
+ usec_t step = USEC_PER_SEC;
+ int counter = NETDATA_EBPF_CGROUP_UPDATE - 1;
heartbeat_t hb;
heartbeat_init(&hb);
//Plugin will be killed when it receives a signal
while (!ebpf_exit_plugin) {
(void)heartbeat_next(&hb, step);
+
+ // We are using a small heartbeat time to wake up thread,
+ // but we should not update so frequently the shared memory data
+ if (++counter >= NETDATA_EBPF_CGROUP_UPDATE) {
+ counter = 0;
+ if (!shm_ebpf_cgroup.header)
+ ebpf_map_cgroup_shared_memory();
+
+ ebpf_parse_cgroup_shm_data();
+ }
}
+ ebpf_stop_threads(0);
+
return 0;
}
diff --git a/collectors/ebpf.plugin/ebpf.d.conf b/collectors/ebpf.plugin/ebpf.d.conf
index cf5c740f..112df275 100644
--- a/collectors/ebpf.plugin/ebpf.d.conf
+++ b/collectors/ebpf.plugin/ebpf.d.conf
@@ -17,7 +17,7 @@
#
[global]
ebpf load mode = entry
- apps = yes
+ apps = no
cgroups = no
update every = 5
pid table size = 32768
@@ -50,7 +50,7 @@
# When plugin detects that system has support to BTF, it enables integration with apps.plugin.
#
[ebpf programs]
- cachestat = no
+ cachestat = yes
dcstat = no
disk = no
fd = yes
@@ -60,10 +60,10 @@
mount = yes
oomkill = yes
process = yes
- shm = no
- socket = yes
+ shm = yes
+ socket = no
softirq = yes
sync = yes
- swap = no
- vfs = yes
+ swap = yes
+ vfs = no
network connections = no
diff --git a/collectors/ebpf.plugin/ebpf.h b/collectors/ebpf.plugin/ebpf.h
index 28b04ce4..16e62498 100644
--- a/collectors/ebpf.plugin/ebpf.h
+++ b/collectors/ebpf.plugin/ebpf.h
@@ -123,6 +123,9 @@ enum ebpf_threads_status {
#endif
#endif
+// Messages
+#define NETDATA_EBPF_DEFAULT_FNT_NOT_FOUND "Cannot find the necessary functions to monitor"
+
// Chart definitions
#define NETDATA_EBPF_FAMILY "ebpf"
#define NETDATA_EBPF_IP_FAMILY "ip"
diff --git a/collectors/ebpf.plugin/ebpf_cachestat.c b/collectors/ebpf.plugin/ebpf_cachestat.c
index 4c410647..b21cc610 100644
--- a/collectors/ebpf.plugin/ebpf_cachestat.c
+++ b/collectors/ebpf.plugin/ebpf_cachestat.c
@@ -15,15 +15,6 @@ netdata_cachestat_pid_t *cachestat_vector = NULL;
static netdata_idx_t cachestat_hash_values[NETDATA_CACHESTAT_END];
static netdata_idx_t *cachestat_values = NULL;
-struct netdata_static_thread cachestat_threads = {.name = "CACHESTAT KERNEL",
- .config_section = NULL,
- .config_name = NULL,
- .env_name = NULL,
- .enabled = 1,
- .thread = NULL,
- .init_routine = NULL,
- .start_routine = NULL};
-
ebpf_local_maps_t cachestat_maps[] = {{.name = "cstat_global", .internal_input = NETDATA_CACHESTAT_END,
.user_input = 0, .type = NETDATA_EBPF_MAP_STATIC,
.map_fd = ND_EBPF_MAP_FD_NOT_INITIALIZED},
@@ -51,6 +42,9 @@ netdata_ebpf_targets_t cachestat_targets[] = { {.name = "add_to_page_cache_lru",
{.name = "mark_buffer_dirty", .mode = EBPF_LOAD_TRAMPOLINE},
{.name = NULL, .mode = EBPF_LOAD_TRAMPOLINE}};
+static char *account_page[NETDATA_CACHESTAT_ACCOUNT_DIRTY_END] ={ "account_page_dirtied",
+ "__set_page_dirty", "__folio_mark_dirty" };
+
#ifdef LIBBPF_MAJOR_VERSION
#include "includes/cachestat.skel.h" // BTF code
@@ -83,10 +77,12 @@ static void ebpf_cachestat_disable_probe(struct cachestat_bpf *obj)
*/
static void ebpf_cachestat_disable_specific_probe(struct cachestat_bpf *obj)
{
- if (running_on_kernel >= NETDATA_EBPF_KERNEL_5_16) {
+ if (!strcmp(cachestat_targets[NETDATA_KEY_CALLS_ACCOUNT_PAGE_DIRTIED].name,
+ account_page[NETDATA_CACHESTAT_FOLIO_DIRTY])) {
bpf_program__set_autoload(obj->progs.netdata_account_page_dirtied_kprobe, false);
bpf_program__set_autoload(obj->progs.netdata_set_page_dirty_kprobe, false);
- } else if (running_on_kernel >= NETDATA_EBPF_KERNEL_5_15) {
+ } else if (!strcmp(cachestat_targets[NETDATA_KEY_CALLS_ACCOUNT_PAGE_DIRTIED].name,
+ account_page[NETDATA_CACHESTAT_SET_PAGE_DIRTY])) {
bpf_program__set_autoload(obj->progs.netdata_folio_mark_dirty_kprobe, false);
bpf_program__set_autoload(obj->progs.netdata_account_page_dirtied_kprobe, false);
} else {
@@ -122,10 +118,12 @@ static void ebpf_cachestat_disable_trampoline(struct cachestat_bpf *obj)
*/
static void ebpf_cachestat_disable_specific_trampoline(struct cachestat_bpf *obj)
{
- if (running_on_kernel >= NETDATA_EBPF_KERNEL_5_16) {
+ if (!strcmp(cachestat_targets[NETDATA_KEY_CALLS_ACCOUNT_PAGE_DIRTIED].name,
+ account_page[NETDATA_CACHESTAT_FOLIO_DIRTY])) {
bpf_program__set_autoload(obj->progs.netdata_account_page_dirtied_fentry, false);
bpf_program__set_autoload(obj->progs.netdata_set_page_dirty_fentry, false);
- } else if (running_on_kernel >= NETDATA_EBPF_KERNEL_5_15) {
+ } else if (!strcmp(cachestat_targets[NETDATA_KEY_CALLS_ACCOUNT_PAGE_DIRTIED].name,
+ account_page[NETDATA_CACHESTAT_SET_PAGE_DIRTY])) {
bpf_program__set_autoload(obj->progs.netdata_folio_mark_dirty_fentry, false);
bpf_program__set_autoload(obj->progs.netdata_account_page_dirtied_fentry, false);
} else {
@@ -149,10 +147,12 @@ static inline void netdata_set_trampoline_target(struct cachestat_bpf *obj)
bpf_program__set_attach_target(obj->progs.netdata_mark_page_accessed_fentry, 0,
cachestat_targets[NETDATA_KEY_CALLS_MARK_PAGE_ACCESSED].name);
- if (running_on_kernel >= NETDATA_EBPF_KERNEL_5_16) {
+ if (!strcmp(cachestat_targets[NETDATA_KEY_CALLS_ACCOUNT_PAGE_DIRTIED].name,
+ account_page[NETDATA_CACHESTAT_FOLIO_DIRTY])) {
bpf_program__set_attach_target(obj->progs.netdata_folio_mark_dirty_fentry, 0,
cachestat_targets[NETDATA_KEY_CALLS_ACCOUNT_PAGE_DIRTIED].name);
- } else if (running_on_kernel >= NETDATA_EBPF_KERNEL_5_15) {
+ } else if (!strcmp(cachestat_targets[NETDATA_KEY_CALLS_ACCOUNT_PAGE_DIRTIED].name,
+ account_page[NETDATA_CACHESTAT_SET_PAGE_DIRTY])) {
bpf_program__set_attach_target(obj->progs.netdata_set_page_dirty_fentry, 0,
cachestat_targets[NETDATA_KEY_CALLS_ACCOUNT_PAGE_DIRTIED].name);
} else {
@@ -192,12 +192,14 @@ static int ebpf_cachestat_attach_probe(struct cachestat_bpf *obj)
if (ret)
return -1;
- if (running_on_kernel >= NETDATA_EBPF_KERNEL_5_16) {
+ if (!strcmp(cachestat_targets[NETDATA_KEY_CALLS_ACCOUNT_PAGE_DIRTIED].name,
+ account_page[NETDATA_CACHESTAT_FOLIO_DIRTY])) {
obj->links.netdata_folio_mark_dirty_kprobe = bpf_program__attach_kprobe(obj->progs.netdata_folio_mark_dirty_kprobe,
false,
cachestat_targets[NETDATA_KEY_CALLS_ACCOUNT_PAGE_DIRTIED].name);
ret = libbpf_get_error(obj->links.netdata_folio_mark_dirty_kprobe);
- } else if (running_on_kernel >= NETDATA_EBPF_KERNEL_5_15) {
+ } else if (!strcmp(cachestat_targets[NETDATA_KEY_CALLS_ACCOUNT_PAGE_DIRTIED].name,
+ account_page[NETDATA_CACHESTAT_SET_PAGE_DIRTY])) {
obj->links.netdata_set_page_dirty_kprobe = bpf_program__attach_kprobe(obj->progs.netdata_set_page_dirty_kprobe,
false,
cachestat_targets[NETDATA_KEY_CALLS_ACCOUNT_PAGE_DIRTIED].name);
@@ -278,7 +280,7 @@ static void ebpf_cachestat_disable_release_task(struct cachestat_bpf *obj)
* @param obj is the main structure for bpf objects.
* @param em structure with configuration
*
- * @return it returns 0 on succes and -1 otherwise
+ * @return it returns 0 on success and -1 otherwise
*/
static inline int ebpf_cachestat_load_and_attach(struct cachestat_bpf *obj, ebpf_module_t *em)
{
@@ -331,18 +333,13 @@ static inline int ebpf_cachestat_load_and_attach(struct cachestat_bpf *obj, ebpf
static void ebpf_cachestat_free(ebpf_module_t *em)
{
pthread_mutex_lock(&ebpf_exit_cleanup);
- if (em->thread->enabled == NETDATA_THREAD_EBPF_RUNNING) {
- em->thread->enabled = NETDATA_THREAD_EBPF_STOPPING;
- pthread_mutex_unlock(&ebpf_exit_cleanup);
- return;
- }
+ em->thread->enabled = NETDATA_THREAD_EBPF_STOPPING;
pthread_mutex_unlock(&ebpf_exit_cleanup);
ebpf_cleanup_publish_syscall(cachestat_counter_publish_aggregated);
freez(cachestat_vector);
freez(cachestat_values);
- freez(cachestat_threads.thread);
#ifdef LIBBPF_MAJOR_VERSION
if (bpf_obj)
@@ -363,20 +360,7 @@ static void ebpf_cachestat_free(ebpf_module_t *em)
static void ebpf_cachestat_exit(void *ptr)
{
ebpf_module_t *em = (ebpf_module_t *)ptr;
- netdata_thread_cancel(*cachestat_threads.thread);
- ebpf_cachestat_free(em);
-}
-/**
- * Cachestat cleanup
- *
- * Clean up allocated addresses.
- *
- * @param ptr thread data.
- */
-static void ebpf_cachestat_cleanup(void *ptr)
-{
- ebpf_module_t *em = (ebpf_module_t *)ptr;
ebpf_cachestat_free(em);
}
@@ -656,7 +640,7 @@ void ebpf_cachestat_create_apps_charts(struct ebpf_module *em, void *ptr)
*
* Read the table with number of calls for all functions
*/
-static void read_global_table()
+static void ebpf_cachestat_read_global_table()
{
uint32_t idx;
netdata_idx_t *val = cachestat_hash_values;
@@ -677,35 +661,6 @@ static void read_global_table()
}
/**
- * Socket read hash
- *
- * This is the thread callback.
- * This thread is necessary, because we cannot freeze the whole plugin to read the data on very busy socket.
- *
- * @param ptr It is a NULL value for this thread.
- *
- * @return It always returns NULL.
- */
-void *ebpf_cachestat_read_hash(void *ptr)
-{
- netdata_thread_cleanup_push(ebpf_cachestat_cleanup, ptr);
- heartbeat_t hb;
- heartbeat_init(&hb);
-
- ebpf_module_t *em = (ebpf_module_t *)ptr;
-
- usec_t step = NETDATA_LATENCY_CACHESTAT_SLEEP_MS * em->update_every;
- while (!ebpf_exit_plugin) {
- (void)heartbeat_next(&hb, step);
-
- read_global_table();
- }
-
- netdata_thread_cleanup_pop(1);
- return NULL;
-}
-
-/**
* Send global
*
* Send global charts to Netdata
@@ -1106,26 +1061,23 @@ void ebpf_cachestat_send_cgroup_data(int update_every)
*/
static void cachestat_collector(ebpf_module_t *em)
{
- cachestat_threads.thread = callocz(1, sizeof(netdata_thread_t));
- cachestat_threads.start_routine = ebpf_cachestat_read_hash;
-
- netdata_thread_create(cachestat_threads.thread, cachestat_threads.name, NETDATA_THREAD_OPTION_DEFAULT,
- ebpf_cachestat_read_hash, em);
-
netdata_publish_cachestat_t publish;
memset(&publish, 0, sizeof(publish));
int cgroups = em->cgroup_charts;
int update_every = em->update_every;
heartbeat_t hb;
heartbeat_init(&hb);
- usec_t step = update_every * USEC_PER_SEC;
+ int counter = update_every - 1;
//This will be cancelled by its parent
while (!ebpf_exit_plugin) {
- (void)heartbeat_next(&hb, step);
- if (ebpf_exit_plugin)
- break;
+ (void)heartbeat_next(&hb, USEC_PER_SEC);
+
+ if (ebpf_exit_plugin || ++counter != update_every)
+ continue;
+ counter = 0;
netdata_apps_integration_flags_t apps = em->apps_charts;
+ ebpf_cachestat_read_global_table();
pthread_mutex_lock(&collect_data_mutex);
if (apps)
read_apps_table();
@@ -1237,16 +1189,28 @@ static void ebpf_cachestat_allocate_global_vectors(int apps)
* Update Internal value
*
* Update values used during runtime.
+ *
+ * @return It returns 0 when one of the functions is present and -1 otherwise.
*/
-static void ebpf_cachestat_set_internal_value()
+static int ebpf_cachestat_set_internal_value()
{
- static char *account_page[] = { "account_page_dirtied", "__set_page_dirty", "__folio_mark_dirty" };
- if (running_on_kernel >= NETDATA_EBPF_KERNEL_5_16)
- cachestat_targets[NETDATA_KEY_CALLS_ACCOUNT_PAGE_DIRTIED].name = account_page[NETDATA_CACHESTAT_FOLIO_DIRTY];
- else if (running_on_kernel >= NETDATA_EBPF_KERNEL_5_15)
- cachestat_targets[NETDATA_KEY_CALLS_ACCOUNT_PAGE_DIRTIED].name = account_page[NETDATA_CACHESTAT_SET_PAGE_DIRTY];
- else
- cachestat_targets[NETDATA_KEY_CALLS_ACCOUNT_PAGE_DIRTIED].name = account_page[NETDATA_CACHESTAT_ACCOUNT_PAGE_DIRTY];
+ ebpf_addresses_t address = {.function = NULL, .hash = 0, .addr = 0};
+ int i;
+ for (i = 0; i < NETDATA_CACHESTAT_ACCOUNT_DIRTY_END ; i++) {
+ address.function = account_page[i];
+ ebpf_load_addresses(&address, -1);
+ if (address.addr)
+ break;
+ }
+
+ if (!address.addr) {
+ error("%s cachestat.", NETDATA_EBPF_DEFAULT_FNT_NOT_FOUND);
+ return -1;
+ }
+
+ cachestat_targets[NETDATA_KEY_CALLS_ACCOUNT_PAGE_DIRTIED].name = address.function;
+
+ return 0;
}
/*
@@ -1300,7 +1264,10 @@ void *ebpf_cachestat_thread(void *ptr)
ebpf_update_pid_table(&cachestat_maps[NETDATA_CACHESTAT_PID_STATS], em);
- ebpf_cachestat_set_internal_value();
+ if (ebpf_cachestat_set_internal_value()) {
+ em->thread->enabled = NETDATA_THREAD_EBPF_STOPPED;
+ goto endcachestat;
+ }
#ifdef LIBBPF_MAJOR_VERSION
ebpf_adjust_thread_load(em, default_btf);
diff --git a/collectors/ebpf.plugin/ebpf_cachestat.h b/collectors/ebpf.plugin/ebpf_cachestat.h
index 07f0745d..15b06511 100644
--- a/collectors/ebpf.plugin/ebpf_cachestat.h
+++ b/collectors/ebpf.plugin/ebpf_cachestat.h
@@ -19,8 +19,6 @@
#define EBPF_CACHESTAT_DIMENSION_HITS "hits/s"
#define EBPF_CACHESTAT_DIMENSION_MISSES "misses/s"
-#define NETDATA_LATENCY_CACHESTAT_SLEEP_MS 600000ULL
-
// configuration file
#define NETDATA_CACHESTAT_CONFIG_FILE "cachestat.conf"
@@ -48,7 +46,9 @@ enum cachestat_counters {
enum cachestat_account_dirty_pages {
NETDATA_CACHESTAT_ACCOUNT_PAGE_DIRTY,
NETDATA_CACHESTAT_SET_PAGE_DIRTY,
- NETDATA_CACHESTAT_FOLIO_DIRTY
+ NETDATA_CACHESTAT_FOLIO_DIRTY,
+
+ NETDATA_CACHESTAT_ACCOUNT_DIRTY_END
};
enum cachestat_indexes {
diff --git a/collectors/ebpf.plugin/ebpf_dcstat.c b/collectors/ebpf.plugin/ebpf_dcstat.c
index 71169e15..75e83214 100644
--- a/collectors/ebpf.plugin/ebpf_dcstat.c
+++ b/collectors/ebpf.plugin/ebpf_dcstat.c
@@ -19,15 +19,6 @@ struct config dcstat_config = { .first_section = NULL,
.index = { .avl_tree = { .root = NULL, .compar = appconfig_section_compare },
.rwlock = AVL_LOCK_INITIALIZER } };
-struct netdata_static_thread dcstat_threads = {"DCSTAT KERNEL",
- .config_section = NULL,
- .config_name = NULL,
- .env_name = NULL,
- .enabled = 1,
- .thread = NULL,
- .init_routine = NULL,
- .start_routine = NULL};
-
ebpf_local_maps_t dcstat_maps[] = {{.name = "dcstat_global", .internal_input = NETDATA_DIRECTORY_CACHE_END,
.user_input = 0, .type = NETDATA_EBPF_MAP_STATIC,
.map_fd = ND_EBPF_MAP_FD_NOT_INITIALIZED},
@@ -216,7 +207,7 @@ static void ebpf_dc_disable_release_task(struct dc_bpf *obj)
* @param obj is the main structure for bpf objects.
* @param em structure with configuration
*
- * @return it returns 0 on succes and -1 otherwise
+ * @return it returns 0 on success and -1 otherwise
*/
static inline int ebpf_dc_load_and_attach(struct dc_bpf *obj, ebpf_module_t *em)
{
@@ -303,16 +294,11 @@ void ebpf_dcstat_clean_names()
static void ebpf_dcstat_free(ebpf_module_t *em )
{
pthread_mutex_lock(&ebpf_exit_cleanup);
- if (em->thread->enabled == NETDATA_THREAD_EBPF_RUNNING) {
- em->thread->enabled = NETDATA_THREAD_EBPF_STOPPING;
- pthread_mutex_unlock(&ebpf_exit_cleanup);
- return;
- }
+ em->thread->enabled = NETDATA_THREAD_EBPF_STOPPING;
pthread_mutex_unlock(&ebpf_exit_cleanup);
freez(dcstat_vector);
freez(dcstat_values);
- freez(dcstat_threads.thread);
ebpf_cleanup_publish_syscall(dcstat_counter_publish_aggregated);
@@ -338,18 +324,6 @@ static void ebpf_dcstat_free(ebpf_module_t *em )
static void ebpf_dcstat_exit(void *ptr)
{
ebpf_module_t *em = (ebpf_module_t *)ptr;
- netdata_thread_cancel(*dcstat_threads.thread);
- ebpf_dcstat_free(em);
-}
-
-/**
- * Clean up the main thread.
- *
- * @param ptr thread data.
- */
-static void ebpf_dcstat_cleanup(void *ptr)
-{
- ebpf_module_t *em = (ebpf_module_t *)ptr;
ebpf_dcstat_free(em);
}
@@ -538,7 +512,7 @@ static void ebpf_update_dc_cgroup()
*
* Read the table with number of calls for all functions
*/
-static void read_global_table()
+static void ebpf_dc_read_global_table()
{
uint32_t idx;
netdata_idx_t *val = dcstat_hash_values;
@@ -559,35 +533,6 @@ static void read_global_table()
}
/**
- * DCstat read hash
- *
- * This is the thread callback.
- * This thread is necessary, because we cannot freeze the whole plugin to read the data.
- *
- * @param ptr It is a NULL value for this thread.
- *
- * @return It always returns NULL.
- */
-void *ebpf_dcstat_read_hash(void *ptr)
-{
- netdata_thread_cleanup_push(ebpf_dcstat_cleanup, ptr);
- heartbeat_t hb;
- heartbeat_init(&hb);
-
- ebpf_module_t *em = (ebpf_module_t *)ptr;
-
- usec_t step = NETDATA_LATENCY_DCSTAT_SLEEP_MS * em->update_every;
- while (!ebpf_exit_plugin) {
- (void)heartbeat_next(&hb, step);
-
- read_global_table();
- }
-
- netdata_thread_cleanup_pop(1);
- return NULL;
-}
-
-/**
* Cachestat sum PIDs
*
* Sum values for all PIDs associated to a group
@@ -1034,25 +979,22 @@ void ebpf_dc_send_cgroup_data(int update_every)
*/
static void dcstat_collector(ebpf_module_t *em)
{
- dcstat_threads.thread = mallocz(sizeof(netdata_thread_t));
- dcstat_threads.start_routine = ebpf_dcstat_read_hash;
-
- netdata_thread_create(dcstat_threads.thread, dcstat_threads.name, NETDATA_THREAD_OPTION_DEFAULT,
- ebpf_dcstat_read_hash, em);
-
netdata_publish_dcstat_t publish;
memset(&publish, 0, sizeof(publish));
int cgroups = em->cgroup_charts;
int update_every = em->update_every;
heartbeat_t hb;
heartbeat_init(&hb);
- usec_t step = update_every * USEC_PER_SEC;
+ int counter = update_every - 1;
while (!ebpf_exit_plugin) {
- (void)heartbeat_next(&hb, step);
- if (ebpf_exit_plugin)
- break;
+ (void)heartbeat_next(&hb, USEC_PER_SEC);
+
+ if (ebpf_exit_plugin || ++counter != update_every)
+ continue;
+ counter = 0;
netdata_apps_integration_flags_t apps = em->apps_charts;
+ ebpf_dc_read_global_table();
pthread_mutex_lock(&collect_data_mutex);
if (apps)
read_apps_table();
diff --git a/collectors/ebpf.plugin/ebpf_dcstat.h b/collectors/ebpf.plugin/ebpf_dcstat.h
index d8687f96..201fc8a0 100644
--- a/collectors/ebpf.plugin/ebpf_dcstat.h
+++ b/collectors/ebpf.plugin/ebpf_dcstat.h
@@ -28,8 +28,6 @@
#define NETDATA_SYSTEMD_DC_NOT_CACHE_CONTEXT "services.dc_not_cache"
#define NETDATA_SYSTEMD_DC_NOT_FOUND_CONTEXT "services.dc_not_found"
-#define NETDATA_LATENCY_DCSTAT_SLEEP_MS 700000ULL
-
enum directory_cache_indexes {
NETDATA_DCSTAT_IDX_RATIO,
NETDATA_DCSTAT_IDX_REFERENCE,
diff --git a/collectors/ebpf.plugin/ebpf_disk.c b/collectors/ebpf.plugin/ebpf_disk.c
index a27bd81e..5e7e2599 100644
--- a/collectors/ebpf.plugin/ebpf_disk.c
+++ b/collectors/ebpf.plugin/ebpf_disk.c
@@ -33,16 +33,6 @@ static netdata_syscall_stat_t disk_aggregated_data[NETDATA_EBPF_HIST_MAX_BINS];
static netdata_publish_syscall_t disk_publish_aggregated[NETDATA_EBPF_HIST_MAX_BINS];
static netdata_idx_t *disk_hash_values = NULL;
-static struct netdata_static_thread disk_threads = {
- .name = "DISK KERNEL",
- .config_section = NULL,
- .config_name = NULL,
- .env_name = NULL,
- .enabled = 1,
- .thread = NULL,
- .init_routine = NULL,
- .start_routine = NULL
-};
ebpf_publish_disk_t *plot_disks = NULL;
pthread_mutex_t plot_mutex;
@@ -439,11 +429,7 @@ static void ebpf_cleanup_disk_list()
static void ebpf_disk_free(ebpf_module_t *em)
{
pthread_mutex_lock(&ebpf_exit_cleanup);
- if (em->thread->enabled == NETDATA_THREAD_EBPF_RUNNING) {
- em->thread->enabled = NETDATA_THREAD_EBPF_STOPPING;
- pthread_mutex_unlock(&ebpf_exit_cleanup);
- return;
- }
+ em->thread->enabled = NETDATA_THREAD_EBPF_STOPPING;
pthread_mutex_unlock(&ebpf_exit_cleanup);
ebpf_disk_disable_tracepoints();
@@ -452,7 +438,6 @@ static void ebpf_disk_free(ebpf_module_t *em)
ebpf_histogram_dimension_cleanup(dimensions, NETDATA_EBPF_HIST_MAX_BINS);
freez(disk_hash_values);
- freez(disk_threads.thread);
pthread_mutex_destroy(&plot_mutex);
ebpf_cleanup_plot_disks();
@@ -473,20 +458,6 @@ static void ebpf_disk_free(ebpf_module_t *em)
static void ebpf_disk_exit(void *ptr)
{
ebpf_module_t *em = (ebpf_module_t *)ptr;
- netdata_thread_cancel(*disk_threads.thread);
- ebpf_disk_free(em);
-}
-
-/**
- * Disk Cleanup
- *
- * Clean up allocated memory.
- *
- * @param ptr thread data.
- */
-static void ebpf_disk_cleanup(void *ptr)
-{
- ebpf_module_t *em = (ebpf_module_t *)ptr;
ebpf_disk_free(em);
}
@@ -592,35 +563,6 @@ static void read_hard_disk_tables(int table)
}
/**
- * Disk read hash
- *
- * This is the thread callback.
- * This thread is necessary, because we cannot freeze the whole plugin to read the data on very busy socket.
- *
- * @param ptr It is a NULL value for this thread.
- *
- * @return It always returns NULL.
- */
-void *ebpf_disk_read_hash(void *ptr)
-{
- netdata_thread_cleanup_push(ebpf_disk_cleanup, ptr);
- heartbeat_t hb;
- heartbeat_init(&hb);
-
- ebpf_module_t *em = (ebpf_module_t *)ptr;
-
- usec_t step = NETDATA_LATENCY_DISK_SLEEP_MS * em->update_every;
- while (!ebpf_exit_plugin) {
- (void)heartbeat_next(&hb, step);
-
- read_hard_disk_tables(disk_maps[NETDATA_DISK_READ].map_fd);
- }
-
- netdata_thread_cleanup_pop(1);
- return NULL;
-}
-
-/**
* Obsolete Hard Disk charts
*
* Make Hard disk charts and fill chart name
@@ -743,21 +685,19 @@ static void ebpf_latency_send_hd_data(int update_every)
static void disk_collector(ebpf_module_t *em)
{
disk_hash_values = callocz(ebpf_nprocs, sizeof(netdata_idx_t));
- disk_threads.thread = mallocz(sizeof(netdata_thread_t));
- disk_threads.start_routine = ebpf_disk_read_hash;
-
- netdata_thread_create(disk_threads.thread, disk_threads.name, NETDATA_THREAD_OPTION_DEFAULT,
- ebpf_disk_read_hash, em);
int update_every = em->update_every;
heartbeat_t hb;
heartbeat_init(&hb);
- usec_t step = update_every * USEC_PER_SEC;
+ int counter = update_every - 1;
while (!ebpf_exit_plugin) {
- (void)heartbeat_next(&hb, step);
- if (ebpf_exit_plugin)
- break;
+ (void)heartbeat_next(&hb, USEC_PER_SEC);
+
+ if (ebpf_exit_plugin || ++counter != update_every)
+ continue;
+ counter = 0;
+ read_hard_disk_tables(disk_maps[NETDATA_DISK_READ].map_fd);
pthread_mutex_lock(&lock);
ebpf_remove_pointer_from_plot_disk(em);
ebpf_latency_send_hd_data(update_every);
diff --git a/collectors/ebpf.plugin/ebpf_disk.h b/collectors/ebpf.plugin/ebpf_disk.h
index c14b887f..c606d659 100644
--- a/collectors/ebpf.plugin/ebpf_disk.h
+++ b/collectors/ebpf.plugin/ebpf_disk.h
@@ -11,8 +11,6 @@
#define NETDATA_EBPF_PROC_PARTITIONS "/proc/partitions"
-#define NETDATA_LATENCY_DISK_SLEEP_MS 650000ULL
-
// Process configuration name
#define NETDATA_DISK_CONFIG_FILE "disk.conf"
diff --git a/collectors/ebpf.plugin/ebpf_fd.c b/collectors/ebpf.plugin/ebpf_fd.c
index 30b7f22c..79537066 100644
--- a/collectors/ebpf.plugin/ebpf_fd.c
+++ b/collectors/ebpf.plugin/ebpf_fd.c
@@ -6,6 +6,9 @@
static char *fd_dimension_names[NETDATA_FD_SYSCALL_END] = { "open", "close" };
static char *fd_id_names[NETDATA_FD_SYSCALL_END] = { "do_sys_open", "__close_fd" };
+static char *close_targets[NETDATA_EBPF_MAX_FD_TARGETS] = {"close_fd", "__close_fd"};
+static char *open_targets[NETDATA_EBPF_MAX_FD_TARGETS] = {"do_sys_openat2", "do_sys_open"};
+
static netdata_syscall_stat_t fd_aggregated_data[NETDATA_FD_SYSCALL_END];
static netdata_publish_syscall_t fd_publish_aggregated[NETDATA_FD_SYSCALL_END];
@@ -29,15 +32,6 @@ struct config fd_config = { .first_section = NULL, .last_section = NULL, .mutex
.index = {.avl_tree = { .root = NULL, .compar = appconfig_section_compare },
.rwlock = AVL_LOCK_INITIALIZER } };
-struct netdata_static_thread fd_thread = {"FD KERNEL",
- .config_section = NULL,
- .config_name = NULL,
- .env_name = NULL,
- .enabled = 1,
- .thread = NULL,
- .init_routine = NULL,
- .start_routine = NULL};
-
static netdata_idx_t fd_hash_values[NETDATA_FD_COUNTER];
static netdata_idx_t *fd_values = NULL;
@@ -65,7 +59,7 @@ static inline void ebpf_fd_disable_probes(struct fd_bpf *obj)
bpf_program__set_autoload(obj->progs.netdata_sys_open_kprobe, false);
bpf_program__set_autoload(obj->progs.netdata_sys_open_kretprobe, false);
bpf_program__set_autoload(obj->progs.netdata_release_task_fd_kprobe, false);
- if (running_on_kernel >= NETDATA_EBPF_KERNEL_5_11) {
+ if (!strcmp(fd_targets[NETDATA_FD_SYSCALL_CLOSE].name, close_targets[NETDATA_FD_CLOSE_FD])) {
bpf_program__set_autoload(obj->progs.netdata___close_fd_kretprobe, false);
bpf_program__set_autoload(obj->progs.netdata___close_fd_kprobe, false);
bpf_program__set_autoload(obj->progs.netdata_close_fd_kprobe, false);
@@ -85,7 +79,7 @@ static inline void ebpf_fd_disable_probes(struct fd_bpf *obj)
*/
static inline void ebpf_disable_specific_probes(struct fd_bpf *obj)
{
- if (running_on_kernel >= NETDATA_EBPF_KERNEL_5_11) {
+ if (!strcmp(fd_targets[NETDATA_FD_SYSCALL_CLOSE].name, close_targets[NETDATA_FD_CLOSE_FD])) {
bpf_program__set_autoload(obj->progs.netdata___close_fd_kretprobe, false);
bpf_program__set_autoload(obj->progs.netdata___close_fd_kprobe, false);
} else {
@@ -121,7 +115,7 @@ static inline void ebpf_disable_trampoline(struct fd_bpf *obj)
*/
static inline void ebpf_disable_specific_trampoline(struct fd_bpf *obj)
{
- if (running_on_kernel >= NETDATA_EBPF_KERNEL_5_11) {
+ if (!strcmp(fd_targets[NETDATA_FD_SYSCALL_CLOSE].name, close_targets[NETDATA_FD_CLOSE_FD])) {
bpf_program__set_autoload(obj->progs.netdata___close_fd_fentry, false);
bpf_program__set_autoload(obj->progs.netdata___close_fd_fexit, false);
} else {
@@ -143,7 +137,7 @@ static void ebpf_set_trampoline_target(struct fd_bpf *obj)
bpf_program__set_attach_target(obj->progs.netdata_sys_open_fexit, 0, fd_targets[NETDATA_FD_SYSCALL_OPEN].name);
bpf_program__set_attach_target(obj->progs.netdata_release_task_fd_fentry, 0, EBPF_COMMON_FNCT_CLEAN_UP);
- if (running_on_kernel >= NETDATA_EBPF_KERNEL_5_11) {
+ if (!strcmp(fd_targets[NETDATA_FD_SYSCALL_CLOSE].name, close_targets[NETDATA_FD_CLOSE_FD])) {
bpf_program__set_attach_target(
obj->progs.netdata_close_fd_fentry, 0, fd_targets[NETDATA_FD_SYSCALL_CLOSE].name);
bpf_program__set_attach_target(obj->progs.netdata_close_fd_fexit, 0, fd_targets[NETDATA_FD_SYSCALL_CLOSE].name);
@@ -185,7 +179,7 @@ static int ebpf_fd_attach_probe(struct fd_bpf *obj)
if (ret)
return -1;
- if (running_on_kernel >= NETDATA_EBPF_KERNEL_5_11) {
+ if (!strcmp(fd_targets[NETDATA_FD_SYSCALL_CLOSE].name, close_targets[NETDATA_FD_CLOSE_FD])) {
obj->links.netdata_close_fd_kretprobe = bpf_program__attach_kprobe(obj->progs.netdata_close_fd_kretprobe, true,
fd_targets[NETDATA_FD_SYSCALL_CLOSE].name);
ret = libbpf_get_error(obj->links.netdata_close_fd_kretprobe);
@@ -217,24 +211,50 @@ static int ebpf_fd_attach_probe(struct fd_bpf *obj)
}
/**
- * Set target values
+ * FD Fill Address
*
- * Set pointers used to laod data.
+ * Fill address value used to load probes/trampoline.
*/
-static void ebpf_fd_set_target_values()
+static inline void ebpf_fd_fill_address(ebpf_addresses_t *address, char **targets)
{
- static char *close_targets[] = {"close_fd", "__close_fd"};
- static char *open_targets[] = {"do_sys_openat2", "do_sys_open"};
- if (running_on_kernel >= NETDATA_EBPF_KERNEL_5_11) {
- fd_targets[NETDATA_FD_SYSCALL_OPEN].name = open_targets[0];
- fd_targets[NETDATA_FD_SYSCALL_CLOSE].name = close_targets[0];
- } else {
- fd_targets[NETDATA_FD_SYSCALL_OPEN].name = open_targets[1];
- fd_targets[NETDATA_FD_SYSCALL_CLOSE].name = close_targets[1];
+ int i;
+ for (i = 0; i < NETDATA_EBPF_MAX_FD_TARGETS; i++) {
+ address->function = targets[i];
+ ebpf_load_addresses(address, -1);
+ if (address->addr)
+ break;
}
}
/**
+ * Set target values
+ *
+ * Set pointers used to load data.
+ *
+ * @return It returns 0 on success and -1 otherwise.
+ */
+static int ebpf_fd_set_target_values()
+{
+ ebpf_addresses_t address = {.function = NULL, .hash = 0, .addr = 0};
+ ebpf_fd_fill_address(&address, close_targets);
+
+ if (!address.addr)
+ return -1;
+
+ fd_targets[NETDATA_FD_SYSCALL_CLOSE].name = address.function;
+
+ address.addr = 0;
+ ebpf_fd_fill_address(&address, open_targets);
+
+ if (!address.addr)
+ return -1;
+
+ fd_targets[NETDATA_FD_SYSCALL_OPEN].name = address.function;
+
+ return 0;
+}
+
+/**
* Set hash tables
*
* Set the values for maps according the value given by kernel.
@@ -283,14 +303,18 @@ static void ebpf_fd_disable_release_task(struct fd_bpf *obj)
* @param obj is the main structure for bpf objects.
* @param em structure with configuration
*
- * @return it returns 0 on succes and -1 otherwise
+ * @return it returns 0 on success and -1 otherwise
*/
static inline int ebpf_fd_load_and_attach(struct fd_bpf *obj, ebpf_module_t *em)
{
netdata_ebpf_targets_t *mt = em->targets;
netdata_ebpf_program_loaded_t test = mt[NETDATA_FD_SYSCALL_OPEN].mode;
- ebpf_fd_set_target_values();
+ if (ebpf_fd_set_target_values()) {
+ error("%s file descriptor.", NETDATA_EBPF_DEFAULT_FNT_NOT_FOUND);
+ return -1;
+ }
+
if (test == EBPF_LOAD_TRAMPOLINE) {
ebpf_fd_disable_probes(obj);
ebpf_disable_specific_trampoline(obj);
@@ -340,15 +364,10 @@ static inline int ebpf_fd_load_and_attach(struct fd_bpf *obj, ebpf_module_t *em)
static void ebpf_fd_free(ebpf_module_t *em)
{
pthread_mutex_lock(&ebpf_exit_cleanup);
- if (em->thread->enabled == NETDATA_THREAD_EBPF_RUNNING) {
- em->thread->enabled = NETDATA_THREAD_EBPF_STOPPING;
- pthread_mutex_unlock(&ebpf_exit_cleanup);
- return;
- }
+ em->thread->enabled = NETDATA_THREAD_EBPF_STOPPING;
pthread_mutex_unlock(&ebpf_exit_cleanup);
ebpf_cleanup_publish_syscall(fd_publish_aggregated);
- freez(fd_thread.thread);
freez(fd_values);
freez(fd_vector);
@@ -372,18 +391,6 @@ static void ebpf_fd_free(ebpf_module_t *em)
static void ebpf_fd_exit(void *ptr)
{
ebpf_module_t *em = (ebpf_module_t *)ptr;
- netdata_thread_cancel(*fd_thread.thread);
- ebpf_fd_free(em);
-}
-
-/**
- * Clean up the main thread.
- *
- * @param ptr thread data.
- */
-static void ebpf_fd_cleanup(void *ptr)
-{
- ebpf_module_t *em = (ebpf_module_t *)ptr;
ebpf_fd_free(em);
}
@@ -420,7 +427,7 @@ static void ebpf_fd_send_data(ebpf_module_t *em)
*
* Read the table with number of calls for all functions
*/
-static void read_global_table()
+static void ebpf_fd_read_global_table()
{
uint32_t idx;
netdata_idx_t *val = fd_hash_values;
@@ -441,34 +448,6 @@ static void read_global_table()
}
/**
- * File descriptor read hash
- *
- * This is the thread callback.
- * This thread is necessary, because we cannot freeze the whole plugin to read the data.
- *
- * @param ptr It is a NULL value for this thread.
- *
- * @return It always returns NULL.
- */
-void *ebpf_fd_read_hash(void *ptr)
-{
- netdata_thread_cleanup_push(ebpf_fd_cleanup, ptr);
- heartbeat_t hb;
- heartbeat_init(&hb);
-
- ebpf_module_t *em = (ebpf_module_t *)ptr;
- usec_t step = NETDATA_FD_SLEEP_MS * em->update_every;
- while (!ebpf_exit_plugin) {
- (void)heartbeat_next(&hb, step);
-
- read_global_table();
- }
-
- netdata_thread_cleanup_pop(1);
- return NULL;
-}
-
-/**
* Apps Accumulator
*
* Sum all values read from kernel and store in the first address.
@@ -942,22 +921,20 @@ static void ebpf_fd_send_cgroup_data(ebpf_module_t *em)
*/
static void fd_collector(ebpf_module_t *em)
{
- fd_thread.thread = mallocz(sizeof(netdata_thread_t));
- fd_thread.start_routine = ebpf_fd_read_hash;
-
- netdata_thread_create(fd_thread.thread, fd_thread.name, NETDATA_THREAD_OPTION_DEFAULT,
- ebpf_fd_read_hash, em);
-
int cgroups = em->cgroup_charts;
heartbeat_t hb;
heartbeat_init(&hb);
- usec_t step = em->update_every * USEC_PER_SEC;
+ int update_every = em->update_every;
+ int counter = update_every - 1;
while (!ebpf_exit_plugin) {
- (void)heartbeat_next(&hb, step);
- if (ebpf_exit_plugin)
- break;
+ (void)heartbeat_next(&hb, USEC_PER_SEC);
+
+ if (ebpf_exit_plugin || ++counter != update_every)
+ continue;
+ counter = 0;
netdata_apps_integration_flags_t apps = em->apps_charts;
+ ebpf_fd_read_global_table();
pthread_mutex_lock(&collect_data_mutex);
if (apps)
read_apps_table();
diff --git a/collectors/ebpf.plugin/ebpf_fd.h b/collectors/ebpf.plugin/ebpf_fd.h
index 914a34b9..e6545d79 100644
--- a/collectors/ebpf.plugin/ebpf_fd.h
+++ b/collectors/ebpf.plugin/ebpf_fd.h
@@ -6,8 +6,6 @@
// Module name
#define NETDATA_EBPF_MODULE_NAME_FD "filedescriptor"
-#define NETDATA_FD_SLEEP_MS 850000ULL
-
// Menu group
#define NETDATA_FILE_GROUP "file_access"
@@ -36,9 +34,6 @@
#define NETDATA_SYSTEMD_FD_CLOSE_ERR_CONTEXT "services.fd_close_error"
typedef struct netdata_fd_stat {
- uint64_t pid_tgid; // Unique identifier
- uint32_t pid; // Process ID
-
uint32_t open_call; // Open syscalls (open and openat)
uint32_t close_call; // Close syscall (close)
@@ -74,6 +69,14 @@ enum fd_syscalls {
NETDATA_FD_SYSCALL_END
};
+enum fd_close_syscall {
+ NETDATA_FD_CLOSE_FD,
+ NETDATA_FD___CLOSE_FD,
+
+ NETDATA_FD_CLOSE_END
+};
+
+#define NETDATA_EBPF_MAX_FD_TARGETS 2
void *ebpf_fd_thread(void *ptr);
void ebpf_fd_create_apps_charts(struct ebpf_module *em, void *ptr);
diff --git a/collectors/ebpf.plugin/ebpf_filesystem.c b/collectors/ebpf.plugin/ebpf_filesystem.c
index 7dbec741..5250ed8a 100644
--- a/collectors/ebpf.plugin/ebpf_filesystem.c
+++ b/collectors/ebpf.plugin/ebpf_filesystem.c
@@ -30,17 +30,6 @@ static ebpf_local_maps_t fs_maps[] = {{.name = "tbl_ext4", .internal_input = NET
.type = NETDATA_EBPF_MAP_CONTROLLER,
.map_fd = ND_EBPF_MAP_FD_NOT_INITIALIZED}};
-struct netdata_static_thread filesystem_threads = {
- .name = "EBPF FS READ",
- .config_section = NULL,
- .config_name = NULL,
- .env_name = NULL,
- .enabled = 1,
- .thread = NULL,
- .init_routine = NULL,
- .start_routine = NULL
-};
-
static netdata_syscall_stat_t filesystem_aggregated_data[NETDATA_EBPF_HIST_MAX_BINS];
static netdata_publish_syscall_t filesystem_publish_aggregated[NETDATA_EBPF_HIST_MAX_BINS];
@@ -337,14 +326,9 @@ void ebpf_filesystem_cleanup_ebpf_data()
static void ebpf_filesystem_free(ebpf_module_t *em)
{
pthread_mutex_lock(&ebpf_exit_cleanup);
- if (em->thread->enabled == NETDATA_THREAD_EBPF_RUNNING) {
- em->thread->enabled = NETDATA_THREAD_EBPF_STOPPING;
- pthread_mutex_unlock(&ebpf_exit_cleanup);
- return;
- }
+ em->thread->enabled = NETDATA_THREAD_EBPF_STOPPING;
pthread_mutex_unlock(&ebpf_exit_cleanup);
- freez(filesystem_threads.thread);
ebpf_cleanup_publish_syscall(filesystem_publish_aggregated);
ebpf_filesystem_cleanup_ebpf_data();
@@ -367,20 +351,6 @@ static void ebpf_filesystem_free(ebpf_module_t *em)
static void ebpf_filesystem_exit(void *ptr)
{
ebpf_module_t *em = (ebpf_module_t *)ptr;
- netdata_thread_cancel(*filesystem_threads.thread);
- ebpf_filesystem_free(em);
-}
-
-/**
- * File system cleanup
- *
- * Clean up allocated thread.
- *
- * @param ptr thread data.
- */
-static void ebpf_filesystem_cleanup(void *ptr)
-{
- ebpf_module_t *em = (ebpf_module_t *)ptr;
ebpf_filesystem_free(em);
}
@@ -483,30 +453,16 @@ static void read_filesystem_tables()
*
* @return It always returns NULL.
*/
-void *ebpf_filesystem_read_hash(void *ptr)
+void ebpf_filesystem_read_hash(ebpf_module_t *em)
{
- netdata_thread_cleanup_push(ebpf_filesystem_cleanup, ptr);
- ebpf_module_t *em = (ebpf_module_t *)ptr;
-
- heartbeat_t hb;
- heartbeat_init(&hb);
- usec_t step = NETDATA_FILESYSTEM_READ_SLEEP_MS * em->update_every;
- int update_every = em->update_every;
- while (!ebpf_exit_plugin) {
- (void)heartbeat_next(&hb, step);
+ ebpf_obsolete_fs_charts(em->update_every);
- (void) ebpf_update_partitions(em);
- ebpf_obsolete_fs_charts(update_every);
+ (void) ebpf_update_partitions(em);
- // No more partitions, it is not necessary to read tables
- if (em->optional)
- continue;
-
- read_filesystem_tables();
- }
+ if (em->optional)
+ return;
- netdata_thread_cleanup_pop(1);
- return NULL;
+ read_filesystem_tables();
}
/**
@@ -543,21 +499,18 @@ static void ebpf_histogram_send_data()
*/
static void filesystem_collector(ebpf_module_t *em)
{
- filesystem_threads.thread = mallocz(sizeof(netdata_thread_t));
- filesystem_threads.start_routine = ebpf_filesystem_read_hash;
-
- netdata_thread_create(filesystem_threads.thread, filesystem_threads.name,
- NETDATA_THREAD_OPTION_DEFAULT, ebpf_filesystem_read_hash, em);
-
int update_every = em->update_every;
heartbeat_t hb;
heartbeat_init(&hb);
- usec_t step = update_every * USEC_PER_SEC;
+ int counter = update_every - 1;
while (!ebpf_exit_plugin) {
- (void)heartbeat_next(&hb, step);
- if (ebpf_exit_plugin)
- break;
+ (void)heartbeat_next(&hb, USEC_PER_SEC);
+
+ if (ebpf_exit_plugin || ++counter != update_every)
+ continue;
+ counter = 0;
+ ebpf_filesystem_read_hash(em);
pthread_mutex_lock(&lock);
ebpf_create_fs_charts(update_every);
diff --git a/collectors/ebpf.plugin/ebpf_filesystem.h b/collectors/ebpf.plugin/ebpf_filesystem.h
index 0d558df7..cf19b253 100644
--- a/collectors/ebpf.plugin/ebpf_filesystem.h
+++ b/collectors/ebpf.plugin/ebpf_filesystem.h
@@ -11,7 +11,6 @@
#define NETDATA_FS_MAX_DIST_NAME 64UL
#define NETDATA_FILESYSTEM_CONFIG_NAME "filesystem"
-#define NETDATA_FILESYSTEM_READ_SLEEP_MS 600000ULL
// Process configuration name
#define NETDATA_FILESYSTEM_CONFIG_FILE "filesystem.conf"
diff --git a/collectors/ebpf.plugin/ebpf_hardirq.c b/collectors/ebpf.plugin/ebpf_hardirq.c
index b07dd24c..20c4b9d0 100644
--- a/collectors/ebpf.plugin/ebpf_hardirq.c
+++ b/collectors/ebpf.plugin/ebpf_hardirq.c
@@ -135,17 +135,6 @@ static hardirq_ebpf_val_t *hardirq_ebpf_vals = NULL;
// tmp store for static hard IRQ values we get from a per-CPU eBPF map.
static hardirq_ebpf_static_val_t *hardirq_ebpf_static_vals = NULL;
-static struct netdata_static_thread hardirq_threads = {
- .name = "HARDIRQ KERNEL",
- .config_section = NULL,
- .config_name = NULL,
- .env_name = NULL,
- .enabled = 1,
- .thread = NULL,
- .init_routine = NULL,
- .start_routine = NULL
-};
-
/**
* Hardirq Free
*
@@ -156,21 +145,18 @@ static struct netdata_static_thread hardirq_threads = {
static void ebpf_hardirq_free(ebpf_module_t *em)
{
pthread_mutex_lock(&ebpf_exit_cleanup);
- if (em->thread->enabled == NETDATA_THREAD_EBPF_RUNNING) {
- em->thread->enabled = NETDATA_THREAD_EBPF_STOPPING;
- pthread_mutex_unlock(&ebpf_exit_cleanup);
- return;
- }
+ em->thread->enabled = NETDATA_THREAD_EBPF_STOPPING;
pthread_mutex_unlock(&ebpf_exit_cleanup);
- freez(hardirq_threads.thread);
for (int i = 0; hardirq_tracepoints[i].class != NULL; i++) {
ebpf_disable_tracepoint(&hardirq_tracepoints[i]);
}
freez(hardirq_ebpf_vals);
freez(hardirq_ebpf_static_vals);
+ pthread_mutex_lock(&ebpf_exit_cleanup);
em->thread->enabled = NETDATA_THREAD_EBPF_STOPPED;
+ pthread_mutex_unlock(&ebpf_exit_cleanup);
}
/**
@@ -183,20 +169,6 @@ static void ebpf_hardirq_free(ebpf_module_t *em)
static void hardirq_exit(void *ptr)
{
ebpf_module_t *em = (ebpf_module_t *)ptr;
- netdata_thread_cancel(*hardirq_threads.thread);
- ebpf_hardirq_free(em);
-}
-
-/**
- * Hardirq clean up
- *
- * Clean up allocated memory.
- *
- * @param ptr thread data.
- */
-static void hardirq_cleanup(void *ptr)
-{
- ebpf_module_t *em = (ebpf_module_t *)ptr;
ebpf_hardirq_free(em);
}
@@ -331,24 +303,10 @@ static void hardirq_read_latency_static_map(int mapfd)
/**
* Read eBPF maps for hard IRQ.
*/
-static void *hardirq_reader(void *ptr)
+static void hardirq_reader()
{
- netdata_thread_cleanup_push(hardirq_cleanup, ptr);
- heartbeat_t hb;
- heartbeat_init(&hb);
-
- ebpf_module_t *em = (ebpf_module_t *)ptr;
-
- usec_t step = NETDATA_HARDIRQ_SLEEP_MS * em->update_every;
- while (!ebpf_exit_plugin) {
- (void)heartbeat_next(&hb, step);
-
- hardirq_read_latency_map(hardirq_maps[HARDIRQ_MAP_LATENCY].map_fd);
- hardirq_read_latency_static_map(hardirq_maps[HARDIRQ_MAP_LATENCY_STATIC].map_fd);
- }
-
- netdata_thread_cleanup_pop(1);
- return NULL;
+ hardirq_read_latency_map(hardirq_maps[HARDIRQ_MAP_LATENCY].map_fd);
+ hardirq_read_latency_static_map(hardirq_maps[HARDIRQ_MAP_LATENCY_STATIC].map_fd);
}
static void hardirq_create_charts(int update_every)
@@ -428,17 +386,6 @@ static void hardirq_collector(ebpf_module_t *em)
avl_init_lock(&hardirq_pub, hardirq_val_cmp);
- // create reader thread.
- hardirq_threads.thread = mallocz(sizeof(netdata_thread_t));
- hardirq_threads.start_routine = hardirq_reader;
- netdata_thread_create(
- hardirq_threads.thread,
- hardirq_threads.name,
- NETDATA_THREAD_OPTION_DEFAULT,
- hardirq_reader,
- em
- );
-
// create chart and static dims.
pthread_mutex_lock(&lock);
hardirq_create_charts(em->update_every);
@@ -449,13 +396,17 @@ static void hardirq_collector(ebpf_module_t *em)
// loop and read from published data until ebpf plugin is closed.
heartbeat_t hb;
heartbeat_init(&hb);
- usec_t step = em->update_every * USEC_PER_SEC;
+ int update_every = em->update_every;
+ int counter = update_every - 1;
//This will be cancelled by its parent
while (!ebpf_exit_plugin) {
- (void)heartbeat_next(&hb, step);
- if (ebpf_exit_plugin)
- break;
+ (void)heartbeat_next(&hb, USEC_PER_SEC);
+
+ if (ebpf_exit_plugin || ++counter != update_every)
+ continue;
+ counter = 0;
+ hardirq_reader();
pthread_mutex_lock(&lock);
// write dims now for all hitherto discovered IRQs.
diff --git a/collectors/ebpf.plugin/ebpf_hardirq.h b/collectors/ebpf.plugin/ebpf_hardirq.h
index 381da57d..fe38b1bb 100644
--- a/collectors/ebpf.plugin/ebpf_hardirq.h
+++ b/collectors/ebpf.plugin/ebpf_hardirq.h
@@ -47,7 +47,6 @@ typedef struct hardirq_ebpf_static_val {
*****************************************************************/
#define NETDATA_EBPF_MODULE_NAME_HARDIRQ "hardirq"
-#define NETDATA_HARDIRQ_SLEEP_MS 650000ULL
#define NETDATA_HARDIRQ_CONFIG_FILE "hardirq.conf"
typedef struct hardirq_val {
diff --git a/collectors/ebpf.plugin/ebpf_mdflush.c b/collectors/ebpf.plugin/ebpf_mdflush.c
index dc805da2..1a5a7731 100644
--- a/collectors/ebpf.plugin/ebpf_mdflush.c
+++ b/collectors/ebpf.plugin/ebpf_mdflush.c
@@ -35,17 +35,6 @@ static avl_tree_lock mdflush_pub;
// tmp store for mdflush values we get from a per-CPU eBPF map.
static mdflush_ebpf_val_t *mdflush_ebpf_vals = NULL;
-static struct netdata_static_thread mdflush_threads = {
- .name = "MDFLUSH KERNEL",
- .config_section = NULL,
- .config_name = NULL,
- .env_name = NULL,
- .enabled = 1,
- .thread = NULL,
- .init_routine = NULL,
- .start_routine = NULL
-};
-
/**
* MDflush Free
*
@@ -55,18 +44,10 @@ static struct netdata_static_thread mdflush_threads = {
*/
static void ebpf_mdflush_free(ebpf_module_t *em)
{
- pthread_mutex_lock(&ebpf_exit_cleanup);
- if (em->thread->enabled == NETDATA_THREAD_EBPF_RUNNING) {
- em->thread->enabled = NETDATA_THREAD_EBPF_STOPPING;
- pthread_mutex_unlock(&ebpf_exit_cleanup);
- return;
- }
- pthread_mutex_unlock(&ebpf_exit_cleanup);
-
freez(mdflush_ebpf_vals);
- freez(mdflush_threads.thread);
-
+ pthread_mutex_lock(&ebpf_exit_cleanup);
em->thread->enabled = NETDATA_THREAD_EBPF_STOPPED;
+ pthread_mutex_unlock(&ebpf_exit_cleanup);
}
/**
@@ -83,20 +64,6 @@ static void mdflush_exit(void *ptr)
}
/**
- * CLeanup
- *
- * Clean allocated memory.
- *
- * @param ptr thread data.
- */
-static void mdflush_cleanup(void *ptr)
-{
- ebpf_module_t *em = (ebpf_module_t *)ptr;
- netdata_thread_cancel(*mdflush_threads.thread);
- ebpf_mdflush_free(em);
-}
-
-/**
* Compare mdflush values.
*
* @param a `netdata_mdflush_t *`.
@@ -188,28 +155,6 @@ static void mdflush_read_count_map()
}
}
-/**
- * Read eBPF maps for mdflush.
- */
-static void *mdflush_reader(void *ptr)
-{
- netdata_thread_cleanup_push(mdflush_cleanup, ptr);
- heartbeat_t hb;
- heartbeat_init(&hb);
-
- ebpf_module_t *em = (ebpf_module_t *)ptr;
-
- usec_t step = NETDATA_MDFLUSH_SLEEP_MS * em->update_every;
- while (!ebpf_exit_plugin) {
- (void)heartbeat_next(&hb, step);
-
- mdflush_read_count_map();
- }
-
- netdata_thread_cleanup_pop(1);
- return NULL;
-}
-
static void mdflush_create_charts(int update_every)
{
ebpf_create_chart(
@@ -256,34 +201,27 @@ static void mdflush_collector(ebpf_module_t *em)
{
mdflush_ebpf_vals = callocz(ebpf_nprocs, sizeof(mdflush_ebpf_val_t));
+ int update_every = em->update_every;
avl_init_lock(&mdflush_pub, mdflush_val_cmp);
- // create reader thread.
- mdflush_threads.thread = mallocz(sizeof(netdata_thread_t));
- mdflush_threads.start_routine = mdflush_reader;
- netdata_thread_create(
- mdflush_threads.thread,
- mdflush_threads.name,
- NETDATA_THREAD_OPTION_DEFAULT,
- mdflush_reader,
- em
- );
-
// create chart and static dims.
pthread_mutex_lock(&lock);
- mdflush_create_charts(em->update_every);
+ mdflush_create_charts(update_every);
ebpf_update_stats(&plugin_statistics, em);
pthread_mutex_unlock(&lock);
// loop and read from published data until ebpf plugin is closed.
heartbeat_t hb;
heartbeat_init(&hb);
- usec_t step = em->update_every * USEC_PER_SEC;
+ int counter = update_every - 1;
while (!ebpf_exit_plugin) {
- (void)heartbeat_next(&hb, step);
- if (ebpf_exit_plugin)
- break;
+ (void)heartbeat_next(&hb, USEC_PER_SEC);
+ if (ebpf_exit_plugin || ++counter != update_every)
+ continue;
+
+ counter = 0;
+ mdflush_read_count_map();
// write dims now for all hitherto discovered devices.
write_begin_chart("mdstat", "mdstat_flush");
avl_traverse_lock(&mdflush_pub, mdflush_write_dims, NULL);
diff --git a/collectors/ebpf.plugin/ebpf_mdflush.h b/collectors/ebpf.plugin/ebpf_mdflush.h
index b04eefd2..4913ad01 100644
--- a/collectors/ebpf.plugin/ebpf_mdflush.h
+++ b/collectors/ebpf.plugin/ebpf_mdflush.h
@@ -6,8 +6,6 @@
// Module name
#define NETDATA_EBPF_MODULE_NAME_MDFLUSH "mdflush"
-#define NETDATA_MDFLUSH_SLEEP_MS 850000ULL
-
// charts
#define NETDATA_MDFLUSH_GLOBAL_CHART "mdflush"
diff --git a/collectors/ebpf.plugin/ebpf_mount.c b/collectors/ebpf.plugin/ebpf_mount.c
index ec1f07a6..e06010b5 100644
--- a/collectors/ebpf.plugin/ebpf_mount.c
+++ b/collectors/ebpf.plugin/ebpf_mount.c
@@ -22,17 +22,6 @@ static netdata_idx_t *mount_values = NULL;
static netdata_idx_t mount_hash_values[NETDATA_MOUNT_END];
-struct netdata_static_thread mount_thread = {
- .name = "MOUNT KERNEL",
- .config_section = NULL,
- .config_name = NULL,
- .env_name = NULL,
- .enabled = 1,
- .thread = NULL,
- .init_routine = NULL,
- .start_routine = NULL
-};
-
netdata_ebpf_targets_t mount_targets[] = { {.name = "mount", .mode = EBPF_LOAD_TRAMPOLINE},
{.name = "umount", .mode = EBPF_LOAD_TRAMPOLINE},
{.name = NULL, .mode = EBPF_LOAD_TRAMPOLINE}};
@@ -187,7 +176,7 @@ static void ebpf_mount_set_hash_tables(struct mount_bpf *obj)
* @param obj is the main structure for bpf objects.
* @param em structure with configuration
*
- * @return it returns 0 on succes and -1 otherwise
+ * @return it returns 0 on success and -1 otherwise
*/
static inline int ebpf_mount_load_and_attach(struct mount_bpf *obj, ebpf_module_t *em)
{
@@ -239,14 +228,9 @@ static inline int ebpf_mount_load_and_attach(struct mount_bpf *obj, ebpf_module_
static void ebpf_mount_free(ebpf_module_t *em)
{
pthread_mutex_lock(&ebpf_exit_cleanup);
- if (em->thread->enabled == NETDATA_THREAD_EBPF_RUNNING) {
- em->thread->enabled = NETDATA_THREAD_EBPF_STOPPING;
- pthread_mutex_unlock(&ebpf_exit_cleanup);
- return;
- }
+ em->thread->enabled = NETDATA_THREAD_EBPF_STOPPING;
pthread_mutex_unlock(&ebpf_exit_cleanup);
- freez(mount_thread.thread);
freez(mount_values);
#ifdef LIBBPF_MAJOR_VERSION
@@ -269,20 +253,6 @@ static void ebpf_mount_free(ebpf_module_t *em)
static void ebpf_mount_exit(void *ptr)
{
ebpf_module_t *em = (ebpf_module_t *)ptr;
- netdata_thread_cancel(*mount_thread.thread);
- ebpf_mount_free(em);
-}
-
-/**
- * Mount cleanup
- *
- * Clean up allocated memory.
- *
- * @param ptr thread data.
- */
-static void ebpf_mount_cleanup(void *ptr)
-{
- ebpf_module_t *em = (ebpf_module_t *)ptr;
ebpf_mount_free(em);
}
@@ -297,7 +267,7 @@ static void ebpf_mount_cleanup(void *ptr)
*
* Read the table with number of calls for all functions
*/
-static void read_global_table()
+static void ebpf_mount_read_global_table()
{
uint32_t idx;
netdata_idx_t *val = mount_hash_values;
@@ -318,36 +288,6 @@ static void read_global_table()
}
/**
- * Mount read hash
- *
- * This is the thread callback.
- * This thread is necessary, because we cannot freeze the whole plugin to read the data.
- *
- * @param ptr It is a NULL value for this thread.
- *
- * @return It always returns NULL.
- */
-void *ebpf_mount_read_hash(void *ptr)
-{
- netdata_thread_cleanup_push(ebpf_mount_cleanup, ptr);
- heartbeat_t hb;
- heartbeat_init(&hb);
-
- ebpf_module_t *em = (ebpf_module_t *)ptr;
-
- usec_t step = NETDATA_LATENCY_MOUNT_SLEEP_MS * em->update_every;
- //This will be cancelled by its parent
- while (!ebpf_exit_plugin) {
- (void)heartbeat_next(&hb, step);
-
- read_global_table();
- }
-
- netdata_thread_cleanup_pop(1);
- return NULL;
-}
-
-/**
* Send data to Netdata calling auxiliary functions.
*/
static void ebpf_mount_send_data()
@@ -371,23 +311,20 @@ static void ebpf_mount_send_data()
*/
static void mount_collector(ebpf_module_t *em)
{
- mount_thread.thread = mallocz(sizeof(netdata_thread_t));
- mount_thread.start_routine = ebpf_mount_read_hash;
- memset(mount_hash_values, 0, sizeof(mount_hash_values));
-
mount_values = callocz((size_t)ebpf_nprocs, sizeof(netdata_idx_t));
-
- netdata_thread_create(mount_thread.thread, mount_thread.name, NETDATA_THREAD_OPTION_DEFAULT,
- ebpf_mount_read_hash, em);
+ memset(mount_hash_values, 0, sizeof(mount_hash_values));
heartbeat_t hb;
heartbeat_init(&hb);
- usec_t step = em->update_every * USEC_PER_SEC;
+ int update_every = em->update_every;
+ int counter = update_every - 1;
while (!ebpf_exit_plugin) {
- (void)heartbeat_next(&hb, step);
- if (ebpf_exit_plugin)
- break;
+ (void)heartbeat_next(&hb, USEC_PER_SEC);
+ if (ebpf_exit_plugin || ++counter != update_every)
+ continue;
+ counter = 0;
+ ebpf_mount_read_global_table();
pthread_mutex_lock(&lock);
ebpf_mount_send_data();
diff --git a/collectors/ebpf.plugin/ebpf_mount.h b/collectors/ebpf.plugin/ebpf_mount.h
index 5a8d11a5..11b21f83 100644
--- a/collectors/ebpf.plugin/ebpf_mount.h
+++ b/collectors/ebpf.plugin/ebpf_mount.h
@@ -8,8 +8,6 @@
#define NETDATA_EBPF_MOUNT_SYSCALL 2
-#define NETDATA_LATENCY_MOUNT_SLEEP_MS 700000ULL
-
#define NETDATA_EBPF_MOUNT_CALLS "call"
#define NETDATA_EBPF_MOUNT_ERRORS "error"
#define NETDATA_EBPF_MOUNT_FAMILY "mount (eBPF)"
diff --git a/collectors/ebpf.plugin/ebpf_oomkill.c b/collectors/ebpf.plugin/ebpf_oomkill.c
index d93e4159..82420d54 100644
--- a/collectors/ebpf.plugin/ebpf_oomkill.c
+++ b/collectors/ebpf.plugin/ebpf_oomkill.c
@@ -46,7 +46,9 @@ static netdata_publish_syscall_t oomkill_publish_aggregated = {.name = "oomkill"
static void oomkill_cleanup(void *ptr)
{
ebpf_module_t *em = (ebpf_module_t *)ptr;
+ pthread_mutex_lock(&ebpf_exit_cleanup);
em->thread->enabled = NETDATA_THREAD_EBPF_STOPPED;
+ pthread_mutex_unlock(&ebpf_exit_cleanup);
}
static void oomkill_write_data(int32_t *keys, uint32_t total)
@@ -294,12 +296,13 @@ static void oomkill_collector(ebpf_module_t *em)
// loop and read until ebpf plugin is closed.
heartbeat_t hb;
heartbeat_init(&hb);
- usec_t step = update_every * USEC_PER_SEC;
+ int counter = update_every - 1;
while (!ebpf_exit_plugin) {
- (void)heartbeat_next(&hb, step);
- if (ebpf_exit_plugin)
- break;
+ (void)heartbeat_next(&hb, USEC_PER_SEC);
+ if (!ebpf_exit_plugin || ++counter != update_every)
+ continue;
+ counter = 0;
pthread_mutex_lock(&collect_data_mutex);
pthread_mutex_lock(&lock);
diff --git a/collectors/ebpf.plugin/ebpf_oomkill.h b/collectors/ebpf.plugin/ebpf_oomkill.h
index 78608638..f921f9d8 100644
--- a/collectors/ebpf.plugin/ebpf_oomkill.h
+++ b/collectors/ebpf.plugin/ebpf_oomkill.h
@@ -17,7 +17,6 @@ typedef uint8_t oomkill_ebpf_val_t;
*****************************************************************/
#define NETDATA_EBPF_MODULE_NAME_OOMKILL "oomkill"
-#define NETDATA_OOMKILL_SLEEP_MS 650000ULL
#define NETDATA_OOMKILL_CONFIG_FILE "oomkill.conf"
#define NETDATA_OOMKILL_CHART "oomkills"
diff --git a/collectors/ebpf.plugin/ebpf_process.c b/collectors/ebpf.plugin/ebpf_process.c
index 682577da..9a191d39 100644
--- a/collectors/ebpf.plugin/ebpf_process.c
+++ b/collectors/ebpf.plugin/ebpf_process.c
@@ -57,17 +57,6 @@ struct config process_config = { .first_section = NULL,
static char *threads_stat[NETDATA_EBPF_THREAD_STAT_END] = {"total", "running"};
static char *load_event_stat[NETDATA_EBPF_LOAD_STAT_END] = {"legacy", "co-re"};
-static struct netdata_static_thread cgroup_thread = {
- .name = "EBPF CGROUP",
- .config_section = NULL,
- .config_name = NULL,
- .env_name = NULL,
- .enabled = 1,
- .thread = NULL,
- .init_routine = NULL,
- .start_routine = NULL
-};
-
/*****************************************************************
*
* PROCESS DATA AND SEND TO NETDATA
@@ -327,55 +316,6 @@ static void ebpf_process_update_apps_data()
}
/**
- * Cgroup Exit
- *
- * Function used with netdata_thread_clean_push
- *
- * @param ptr unused argument
- */
-static void ebpf_cgroup_exit(void *ptr)
-{
- UNUSED(ptr);
-}
-
-/**
- * Cgroup update shm
- *
- * This is the thread callback.
- * This thread is necessary, because we cannot freeze the whole plugin to read the data from shared memory.
- *
- * @param ptr It is a NULL value for this thread.
- *
- * @return It always returns NULL.
- */
-void *ebpf_cgroup_update_shm(void *ptr)
-{
- netdata_thread_cleanup_push(ebpf_cgroup_exit, ptr);
- heartbeat_t hb;
- heartbeat_init(&hb);
-
- usec_t step = 3 * USEC_PER_SEC;
- int counter = NETDATA_EBPF_CGROUP_UPDATE - 1;
- //This will be cancelled by its parent
- while (!ebpf_exit_plugin) {
- (void)heartbeat_next(&hb, step);
-
- // We are using a small heartbeat time to wake up thread,
- // but we should not update so frequently the shared memory data
- if (++counter >= NETDATA_EBPF_CGROUP_UPDATE) {
- counter = 0;
- if (!shm_ebpf_cgroup.header)
- ebpf_map_cgroup_shared_memory();
-
- ebpf_parse_cgroup_shm_data();
- }
- }
-
- netdata_thread_cleanup_pop(1);
- return NULL;
-}
-
-/**
* Update cgroup
*
* Update cgroup data based in
@@ -745,7 +685,6 @@ static void ebpf_process_exit(void *ptr)
pthread_mutex_lock(&ebpf_exit_cleanup);
em->thread->enabled = NETDATA_THREAD_EBPF_STOPPED;
pthread_mutex_unlock(&ebpf_exit_cleanup);
- pthread_cancel(*cgroup_thread.thread);
}
/*****************************************************************
@@ -1104,13 +1043,6 @@ void ebpf_send_statistic_data()
*/
static void process_collector(ebpf_module_t *em)
{
- // Start cgroup integration before other threads
- cgroup_thread.thread = mallocz(sizeof(netdata_thread_t));
- cgroup_thread.start_routine = ebpf_cgroup_update_shm;
-
- netdata_thread_create(cgroup_thread.thread, cgroup_thread.name, NETDATA_THREAD_OPTION_DEFAULT,
- ebpf_cgroup_update_shm, NULL);
-
heartbeat_t hb;
heartbeat_init(&hb);
int publish_global = em->global_charts;
@@ -1152,7 +1084,7 @@ static void process_collector(ebpf_module_t *em)
ebpf_process_update_apps_data();
}
- if (cgroups) {
+ if (cgroups && shm_ebpf_cgroup.header) {
ebpf_update_process_cgroup();
}
}
@@ -1169,7 +1101,7 @@ static void process_collector(ebpf_module_t *em)
ebpf_process_send_apps_data(apps_groups_root_target, em);
}
- if (cgroups) {
+ if (cgroups && shm_ebpf_cgroup.header) {
ebpf_process_send_cgroup_data(em);
}
}
diff --git a/collectors/ebpf.plugin/ebpf_process.h b/collectors/ebpf.plugin/ebpf_process.h
index 43df34d4..6fded16f 100644
--- a/collectors/ebpf.plugin/ebpf_process.h
+++ b/collectors/ebpf.plugin/ebpf_process.h
@@ -39,7 +39,7 @@
#define NETDATA_SYSTEMD_PROCESS_EXIT_CONTEXT "services.task_exit"
#define NETDATA_SYSTEMD_PROCESS_ERROR_CONTEXT "services.task_error"
-#define NETDATA_EBPF_CGROUP_UPDATE 10
+#define NETDATA_EBPF_CGROUP_UPDATE 30
// Statistical information
enum netdata_ebpf_thread_stats{
diff --git a/collectors/ebpf.plugin/ebpf_shm.c b/collectors/ebpf.plugin/ebpf_shm.c
index f81287d8..4057eff7 100644
--- a/collectors/ebpf.plugin/ebpf_shm.c
+++ b/collectors/ebpf.plugin/ebpf_shm.c
@@ -34,17 +34,6 @@ static ebpf_local_maps_t shm_maps[] = {{.name = "tbl_pid_shm", .internal_input =
.map_fd = ND_EBPF_MAP_FD_NOT_INITIALIZED},
{.name = NULL, .internal_input = 0, .user_input = 0}};
-struct netdata_static_thread shm_threads = {
- .name = "SHM KERNEL",
- .config_section = NULL,
- .config_name = NULL,
- .env_name = NULL,
- .enabled = 1,
- .thread = NULL,
- .init_routine = NULL,
- .start_routine = NULL
-};
-
netdata_ebpf_targets_t shm_targets[] = { {.name = "shmget", .mode = EBPF_LOAD_TRAMPOLINE},
{.name = "shmat", .mode = EBPF_LOAD_TRAMPOLINE},
{.name = "shmdt", .mode = EBPF_LOAD_TRAMPOLINE},
@@ -246,7 +235,7 @@ static void ebpf_shm_adjust_map_size(struct shm_bpf *obj, ebpf_module_t *em)
* @param obj is the main structure for bpf objects.
* @param em structure with configuration
*
- * @return it returns 0 on succes and -1 otherwise
+ * @return it returns 0 on success and -1 otherwise
*/
static inline int ebpf_shm_load_and_attach(struct shm_bpf *obj, ebpf_module_t *em)
{
@@ -299,11 +288,7 @@ static inline int ebpf_shm_load_and_attach(struct shm_bpf *obj, ebpf_module_t *e
static void ebpf_shm_free(ebpf_module_t *em)
{
pthread_mutex_lock(&ebpf_exit_cleanup);
- if (em->thread->enabled == NETDATA_THREAD_EBPF_RUNNING) {
- em->thread->enabled = NETDATA_THREAD_EBPF_STOPPING;
- pthread_mutex_unlock(&ebpf_exit_cleanup);
- return;
- }
+ em->thread->enabled = NETDATA_THREAD_EBPF_STOPPING;
pthread_mutex_unlock(&ebpf_exit_cleanup);
ebpf_cleanup_publish_syscall(shm_publish_aggregated);
@@ -316,7 +301,9 @@ static void ebpf_shm_free(ebpf_module_t *em)
shm_bpf__destroy(bpf_obj);
#endif
+ pthread_mutex_lock(&ebpf_exit_cleanup);
em->thread->enabled = NETDATA_THREAD_EBPF_STOPPED;
+ pthread_mutex_unlock(&ebpf_exit_cleanup);
}
/**
@@ -329,20 +316,6 @@ static void ebpf_shm_free(ebpf_module_t *em)
static void ebpf_shm_exit(void *ptr)
{
ebpf_module_t *em = (ebpf_module_t *)ptr;
- netdata_thread_cancel(*shm_threads.thread);
- ebpf_shm_free(em);
-}
-
-/**
- * SHM Cleanup
- *
- * Clean up allocated memory.
- *
- * @param ptr thread data.
- */
-static void ebpf_shm_cleanup(void *ptr)
-{
- ebpf_module_t *em = (ebpf_module_t *)ptr;
ebpf_shm_free(em);
}
@@ -491,7 +464,7 @@ static void shm_send_global()
*
* Read the table with number of calls for all functions
*/
-static void read_global_table()
+static void ebpf_shm_read_global_table()
{
netdata_idx_t *stored = shm_values;
netdata_idx_t *val = shm_hash_values;
@@ -512,30 +485,6 @@ static void read_global_table()
}
/**
- * Shared memory reader thread.
- *
- * @param ptr It is a NULL value for this thread.
- * @return It always returns NULL.
- */
-void *ebpf_shm_read_hash(void *ptr)
-{
- netdata_thread_cleanup_push(ebpf_shm_cleanup, ptr);
- heartbeat_t hb;
- heartbeat_init(&hb);
-
- ebpf_module_t *em = (ebpf_module_t *)ptr;
- usec_t step = NETDATA_SHM_SLEEP_MS * em->update_every;
- while (!ebpf_exit_plugin) {
- (void)heartbeat_next(&hb, step);
-
- read_global_table();
- }
-
- netdata_thread_cleanup_pop(1);
- return NULL;
-}
-
-/**
* Sum values for all targets.
*/
static void ebpf_shm_sum_pids(netdata_publish_shm_t *shm, struct pid_on_target *root)
@@ -894,28 +843,19 @@ void ebpf_shm_send_cgroup_data(int update_every)
*/
static void shm_collector(ebpf_module_t *em)
{
- shm_threads.thread = mallocz(sizeof(netdata_thread_t));
- shm_threads.start_routine = ebpf_shm_read_hash;
-
- netdata_thread_create(
- shm_threads.thread,
- shm_threads.name,
- NETDATA_THREAD_OPTION_DEFAULT,
- ebpf_shm_read_hash,
- em
- );
-
int cgroups = em->cgroup_charts;
int update_every = em->update_every;
heartbeat_t hb;
heartbeat_init(&hb);
- usec_t step = update_every * USEC_PER_SEC;
+ int counter = update_every - 1;
while (!ebpf_exit_plugin) {
- (void)heartbeat_next(&hb, step);
- if (ebpf_exit_plugin)
- break;
+ (void)heartbeat_next(&hb, USEC_PER_SEC);
+ if (ebpf_exit_plugin || ++counter != update_every)
+ continue;
+ counter = 0;
netdata_apps_integration_flags_t apps = em->apps_charts;
+ ebpf_shm_read_global_table();
pthread_mutex_lock(&collect_data_mutex);
if (apps) {
read_apps_table();
diff --git a/collectors/ebpf.plugin/ebpf_shm.h b/collectors/ebpf.plugin/ebpf_shm.h
index 4e068819..b06a4a5d 100644
--- a/collectors/ebpf.plugin/ebpf_shm.h
+++ b/collectors/ebpf.plugin/ebpf_shm.h
@@ -6,8 +6,6 @@
// Module name
#define NETDATA_EBPF_MODULE_NAME_SHM "shm"
-#define NETDATA_SHM_SLEEP_MS 850000ULL
-
// charts
#define NETDATA_SHM_GLOBAL_CHART "shared_memory_calls"
#define NETDATA_SHMGET_CHART "shmget_call"
diff --git a/collectors/ebpf.plugin/ebpf_socket.c b/collectors/ebpf.plugin/ebpf_socket.c
index 3a023e4a..1954be71 100644
--- a/collectors/ebpf.plugin/ebpf_socket.c
+++ b/collectors/ebpf.plugin/ebpf_socket.c
@@ -62,8 +62,6 @@ ebpf_socket_publish_apps_t **socket_bandwidth_curr = NULL;
static ebpf_bandwidth_t *bandwidth_vector = NULL;
pthread_mutex_t nv_mutex;
-int wait_to_plot = 0;
-
netdata_vector_plot_t inbound_vectors = { .plot = NULL, .next = 0, .last = 0 };
netdata_vector_plot_t outbound_vectors = { .plot = NULL, .next = 0, .last = 0 };
netdata_socket_t *socket_values;
@@ -389,7 +387,7 @@ static void ebpf_socket_adjust_map_size(struct socket_bpf *obj, ebpf_module_t *e
* @param obj is the main structure for bpf objects.
* @param em structure with configuration
*
- * @return it returns 0 on succes and -1 otherwise
+ * @return it returns 0 on success and -1 otherwise
*/
static inline int ebpf_socket_load_and_attach(struct socket_bpf *obj, ebpf_module_t *em)
{
@@ -459,6 +457,9 @@ static inline void clean_internal_socket_plot(netdata_socket_plot_t *ptr)
*/
static void clean_allocated_socket_plot()
{
+ if (!network_viewer_opt.enabled)
+ return;
+
uint32_t i;
uint32_t end = inbound_vectors.last;
netdata_socket_plot_t *plot = inbound_vectors.plot;
@@ -647,7 +648,8 @@ static void ebpf_socket_free(ebpf_module_t *em )
static void ebpf_socket_exit(void *ptr)
{
ebpf_module_t *em = (ebpf_module_t *)ptr;
- netdata_thread_cancel(*socket_threads.thread);
+ if (socket_threads.thread)
+ netdata_thread_cancel(*socket_threads.thread);
ebpf_socket_free(em);
}
@@ -724,7 +726,7 @@ static void ebpf_update_global_publish(
*/
static inline void update_nv_plot_data(netdata_plot_values_t *plot, netdata_socket_t *sock)
{
- if (sock->ct > plot->last_time) {
+ if (sock->ct != plot->last_time) {
plot->last_time = sock->ct;
plot->plot_recv_packets = sock->recv_packets;
plot->plot_sent_packets = sock->sent_packets;
@@ -747,6 +749,7 @@ static inline void update_nv_plot_data(netdata_plot_values_t *plot, netdata_sock
*/
static inline void calculate_nv_plot()
{
+ pthread_mutex_lock(&nv_mutex);
uint32_t i;
uint32_t end = inbound_vectors.next;
for (i = 0; i < end; i++) {
@@ -764,9 +767,12 @@ static inline void calculate_nv_plot()
}
outbound_vectors.max_plot = end;
+ /*
// The 'Other' dimension is always calculated for the chart to have at least one dimension
update_nv_plot_data(&outbound_vectors.plot[outbound_vectors.last].plot,
&outbound_vectors.plot[outbound_vectors.last].sock);
+ */
+ pthread_mutex_unlock(&nv_mutex);
}
/**
@@ -1441,17 +1447,17 @@ static void ebpf_socket_create_nv_charts(netdata_vector_plot_t *ptr, int update_
*
* @return It returns 1 if the IP is inside the range and 0 otherwise
*/
-static int is_specific_ip_inside_range(union netdata_ip_t *cmp, int family)
+static int ebpf_is_specific_ip_inside_range(union netdata_ip_t *cmp, int family)
{
if (!network_viewer_opt.excluded_ips && !network_viewer_opt.included_ips)
return 1;
- uint32_t ipv4_test = ntohl(cmp->addr32[0]);
+ uint32_t ipv4_test = htonl(cmp->addr32[0]);
ebpf_network_viewer_ip_list_t *move = network_viewer_opt.excluded_ips;
while (move) {
if (family == AF_INET) {
- if (ntohl(move->first.addr32[0]) <= ipv4_test &&
- ipv4_test <= ntohl(move->last.addr32[0]) )
+ if (move->first.addr32[0] <= ipv4_test &&
+ ipv4_test <= move->last.addr32[0])
return 0;
} else {
if (memcmp(move->first.addr8, cmp->addr8, sizeof(union netdata_ip_t)) <= 0 &&
@@ -1464,12 +1470,13 @@ static int is_specific_ip_inside_range(union netdata_ip_t *cmp, int family)
move = network_viewer_opt.included_ips;
while (move) {
- if (family == AF_INET) {
- if (ntohl(move->first.addr32[0]) <= ipv4_test &&
- ntohl(move->last.addr32[0]) >= ipv4_test)
+ if (family == AF_INET && move->ver == AF_INET) {
+ if (move->first.addr32[0] <= ipv4_test &&
+ move->last.addr32[0] >= ipv4_test)
return 1;
} else {
- if (memcmp(move->first.addr8, cmp->addr8, sizeof(union netdata_ip_t)) <= 0 &&
+ if (move->ver == AF_INET6 &&
+ memcmp(move->first.addr8, cmp->addr8, sizeof(union netdata_ip_t)) <= 0 &&
memcmp(move->last.addr8, cmp->addr8, sizeof(union netdata_ip_t)) >= 0) {
return 1;
}
@@ -1565,7 +1572,7 @@ int is_socket_allowed(netdata_socket_idx_t *key, int family)
if (!is_port_inside_range(key->dport))
return 0;
- return is_specific_ip_inside_range(&key->daddr, family);
+ return ebpf_is_specific_ip_inside_range(&key->daddr, family);
}
/**
@@ -1580,38 +1587,26 @@ int is_socket_allowed(netdata_socket_idx_t *key, int family)
*
* @return It returns 0 case the values are equal, 1 case a is bigger than b and -1 case a is smaller than b.
*/
-static int compare_sockets(void *a, void *b)
+static int ebpf_compare_sockets(void *a, void *b)
{
struct netdata_socket_plot *val1 = a;
struct netdata_socket_plot *val2 = b;
- int cmp;
+ int cmp = 0;
// We do not need to compare val2 family, because data inside hash table is always from the same family
if (val1->family == AF_INET) { //IPV4
- if (val1->flags & NETDATA_INBOUND_DIRECTION) {
- if (val1->index.sport == val2->index.sport)
- cmp = 0;
- else {
- cmp = (val1->index.sport > val2->index.sport)?1:-1;
- }
- } else {
+ if (network_viewer_opt.included_port || network_viewer_opt.excluded_port)
cmp = memcmp(&val1->index.dport, &val2->index.dport, sizeof(uint16_t));
- if (!cmp) {
- cmp = memcmp(&val1->index.daddr.addr32[0], &val2->index.daddr.addr32[0], sizeof(uint32_t));
- }
+
+ if (!cmp) {
+ cmp = memcmp(&val1->index.daddr.addr32[0], &val2->index.daddr.addr32[0], sizeof(uint32_t));
}
} else {
- if (val1->flags & NETDATA_INBOUND_DIRECTION) {
- if (val1->index.sport == val2->index.sport)
- cmp = 0;
- else {
- cmp = (val1->index.sport > val2->index.sport)?1:-1;
- }
- } else {
+ if (network_viewer_opt.included_port || network_viewer_opt.excluded_port)
cmp = memcmp(&val1->index.dport, &val2->index.dport, sizeof(uint16_t));
- if (!cmp) {
- cmp = memcmp(&val1->index.daddr.addr32, &val2->index.daddr.addr32, 4*sizeof(uint32_t));
- }
+
+ if (!cmp) {
+ cmp = memcmp(&val1->index.daddr.addr32, &val2->index.daddr.addr32, 4*sizeof(uint32_t));
}
}
@@ -1631,12 +1626,15 @@ static int compare_sockets(void *a, void *b)
*
* @return it returns the size of the data copied on success and -1 otherwise.
*/
-static inline int build_outbound_dimension_name(char *dimname, char *hostname, char *service_name,
- char *proto, int family)
+static inline int ebpf_build_outbound_dimension_name(char *dimname, char *hostname, char *service_name,
+ char *proto, int family)
{
- return snprintf(dimname, CONFIG_MAX_NAME - 7, (family == AF_INET)?"%s:%s:%s_":"%s:%s:[%s]_",
- service_name, proto,
- hostname);
+ if (network_viewer_opt.included_port || network_viewer_opt.excluded_port)
+ return snprintf(dimname, CONFIG_MAX_NAME - 7, (family == AF_INET)?"%s:%s:%s_":"%s:%s:[%s]_",
+ service_name, proto, hostname);
+
+ return snprintf(dimname, CONFIG_MAX_NAME - 7, (family == AF_INET)?"%s:%s_":"%s:[%s]_",
+ proto, hostname);
}
/**
@@ -1692,7 +1690,7 @@ static inline void fill_resolved_name(netdata_socket_plot_t *ptr, char *hostname
}
if (is_outbound)
- size = build_outbound_dimension_name(dimname, hostname, service_name, protocol, ptr->family);
+ size = ebpf_build_outbound_dimension_name(dimname, hostname, service_name, protocol, ptr->family);
else
size = build_inbound_dimension_name(dimname,service_name, protocol);
@@ -1850,14 +1848,12 @@ static void fill_last_nv_dimension(netdata_socket_plot_t *ptr, int is_outbound)
*/
static inline void update_socket_data(netdata_socket_t *sock, netdata_socket_t *lvalues)
{
- sock->recv_packets += lvalues->recv_packets;
- sock->sent_packets += lvalues->sent_packets;
- sock->recv_bytes += lvalues->recv_bytes;
- sock->sent_bytes += lvalues->sent_bytes;
- sock->retransmit += lvalues->retransmit;
-
- if (lvalues->ct > sock->ct)
- sock->ct = lvalues->ct;
+ sock->recv_packets = lvalues->recv_packets;
+ sock->sent_packets = lvalues->sent_packets;
+ sock->recv_bytes = lvalues->recv_bytes;
+ sock->sent_bytes = lvalues->sent_bytes;
+ sock->retransmit = lvalues->retransmit;
+ sock->ct = lvalues->ct;
}
/**
@@ -1881,7 +1877,7 @@ static void store_socket_inside_avl(netdata_vector_plot_t *out, netdata_socket_t
ret = (netdata_socket_plot_t *) avl_search_lock(&out->tree, (avl_t *)&test);
if (ret) {
- if (lvalues->ct > ret->plot.last_time) {
+ if (lvalues->ct != ret->plot.last_time) {
update_socket_data(&ret->sock, lvalues);
}
} else {
@@ -1892,7 +1888,7 @@ static void store_socket_inside_avl(netdata_vector_plot_t *out, netdata_socket_t
int resolved;
if (curr == last) {
- if (lvalues->ct > w->plot.last_time) {
+ if (lvalues->ct != w->plot.last_time) {
update_socket_data(&w->sock, lvalues);
}
return;
@@ -1977,6 +1973,9 @@ netdata_vector_plot_t * select_vector_to_store(uint32_t *direction, netdata_sock
*/
static void hash_accumulator(netdata_socket_t *values, netdata_socket_idx_t *key, int family, int end)
{
+ if (!network_viewer_opt.enabled || !is_socket_allowed(key, family))
+ return;
+
uint64_t bsent = 0, brecv = 0, psent = 0, precv = 0;
uint16_t retransmit = 0;
int i;
@@ -1994,7 +1993,7 @@ static void hash_accumulator(netdata_socket_t *values, netdata_socket_idx_t *key
if (!protocol)
protocol = w->protocol;
- if (w->ct > ct)
+ if (w->ct != ct)
ct = w->ct;
}
@@ -2006,11 +2005,9 @@ static void hash_accumulator(netdata_socket_t *values, netdata_socket_idx_t *key
values[0].protocol = (!protocol)?IPPROTO_TCP:protocol;
values[0].ct = ct;
- if (is_socket_allowed(key, family)) {
- uint32_t dir;
- netdata_vector_plot_t *table = select_vector_to_store(&dir, key, protocol);
- store_socket_inside_avl(table, &values[0], key, family, dir);
- }
+ uint32_t dir;
+ netdata_vector_plot_t *table = select_vector_to_store(&dir, key, protocol);
+ store_socket_inside_avl(table, &values[0], key, family, dir);
}
/**
@@ -2018,16 +2015,13 @@ static void hash_accumulator(netdata_socket_t *values, netdata_socket_idx_t *key
*
* Read data from hash tables created on kernel ring.
*
- * @param fd the hash table with data.
- * @param family the family associated to the hash table
+ * @param fd the hash table with data.
+ * @param family the family associated to the hash table
*
* @return it returns 0 on success and -1 otherwise.
*/
-static void read_socket_hash_table(int fd, int family, int network_connection)
+static void ebpf_read_socket_hash_table(int fd, int family)
{
- if (wait_to_plot)
- return;
-
netdata_socket_idx_t key = {};
netdata_socket_idx_t next_key = {};
@@ -2046,9 +2040,7 @@ static void read_socket_hash_table(int fd, int family, int network_connection)
continue;
}
- if (network_connection) {
- hash_accumulator(values, &key, family, end);
- }
+ hash_accumulator(values, &key, family, end);
key = next_key;
}
@@ -2057,12 +2049,12 @@ static void read_socket_hash_table(int fd, int family, int network_connection)
/**
* Fill Network Viewer Port list
*
- * Fill the strcture with values read from /proc or hash table.
+ * Fill the structure with values read from /proc or hash table.
*
* @param out the structure where we will store data.
* @param value the ports we are listen to.
* @param proto the protocol used for this connection.
- * @param in the strcuture with values read form different sources.
+ * @param in the structure with values read form different sources.
*/
static inline void fill_nv_port_list(ebpf_network_viewer_port_list_t *out, uint16_t value, uint16_t proto,
netdata_passive_connection_t *in)
@@ -2081,7 +2073,7 @@ static inline void fill_nv_port_list(ebpf_network_viewer_port_list_t *out, uint1
*
* @param value the ports we are listen to.
* @param proto the protocol used with port connection.
- * @param in the strcuture with values read form different sources.
+ * @param in the structure with values read form different sources.
*/
void update_listen_table(uint16_t value, uint16_t proto, netdata_passive_connection_t *in)
{
@@ -2159,22 +2151,19 @@ static void read_listen_table()
void *ebpf_socket_read_hash(void *ptr)
{
netdata_thread_cleanup_push(ebpf_socket_cleanup, ptr);
- ebpf_module_t *em = (ebpf_module_t *)ptr;
heartbeat_t hb;
heartbeat_init(&hb);
- usec_t step = NETDATA_SOCKET_READ_SLEEP_MS * em->update_every;
int fd_ipv4 = socket_maps[NETDATA_SOCKET_TABLE_IPV4].map_fd;
int fd_ipv6 = socket_maps[NETDATA_SOCKET_TABLE_IPV6].map_fd;
- int network_connection = em->optional;
while (!ebpf_exit_plugin) {
- (void)heartbeat_next(&hb, step);
+ (void)heartbeat_next(&hb, USEC_PER_SEC);
+ if (ebpf_exit_plugin)
+ continue;
pthread_mutex_lock(&nv_mutex);
- read_listen_table();
- read_socket_hash_table(fd_ipv4, AF_INET, network_connection);
- read_socket_hash_table(fd_ipv6, AF_INET6, network_connection);
- wait_to_plot = 1;
+ ebpf_read_socket_hash_table(fd_ipv4, AF_INET);
+ ebpf_read_socket_hash_table(fd_ipv6, AF_INET6);
pthread_mutex_unlock(&nv_mutex);
}
@@ -2863,44 +2852,50 @@ static void ebpf_socket_send_cgroup_data(int update_every)
/**
* Main loop for this collector.
*
- * @param step the number of microseconds used with heart beat
* @param em the structure with thread information
*/
-static void socket_collector(usec_t step, ebpf_module_t *em)
+static void socket_collector(ebpf_module_t *em)
{
heartbeat_t hb;
heartbeat_init(&hb);
+ uint32_t network_connection = network_viewer_opt.enabled;
- socket_threads.thread = mallocz(sizeof(netdata_thread_t));
- socket_threads.start_routine = ebpf_socket_read_hash;
+ if (network_connection) {
+ socket_threads.thread = mallocz(sizeof(netdata_thread_t));
+ socket_threads.start_routine = ebpf_socket_read_hash;
- netdata_thread_create(socket_threads.thread, socket_threads.name,
- NETDATA_THREAD_OPTION_DEFAULT, ebpf_socket_read_hash, em);
+ netdata_thread_create(socket_threads.thread, socket_threads.name,
+ NETDATA_THREAD_OPTION_DEFAULT, ebpf_socket_read_hash, em);
+ }
int cgroups = em->cgroup_charts;
if (cgroups)
ebpf_socket_update_cgroup_algorithm();
int socket_global_enabled = em->global_charts;
- int network_connection = em->optional;
int update_every = em->update_every;
+ int counter = update_every - 1;
while (!ebpf_exit_plugin) {
- (void)heartbeat_next(&hb, step);
- if (ebpf_exit_plugin)
- break;
+ (void)heartbeat_next(&hb, USEC_PER_SEC);
+ if (ebpf_exit_plugin || ++counter != update_every)
+ continue;
+ counter = 0;
netdata_apps_integration_flags_t socket_apps_enabled = em->apps_charts;
- pthread_mutex_lock(&collect_data_mutex);
- if (socket_global_enabled)
+ if (socket_global_enabled) {
+ read_listen_table();
read_hash_global_tables();
+ }
+ pthread_mutex_lock(&collect_data_mutex);
if (socket_apps_enabled)
ebpf_socket_update_apps_data();
if (cgroups)
ebpf_update_socket_cgroup();
- calculate_nv_plot();
+ if (network_connection)
+ calculate_nv_plot();
pthread_mutex_lock(&lock);
if (socket_global_enabled)
@@ -2925,7 +2920,6 @@ static void socket_collector(usec_t step, ebpf_module_t *em)
ebpf_socket_create_nv_charts(&outbound_vectors, update_every);
fflush(stdout);
ebpf_socket_send_nv_data(&outbound_vectors);
- wait_to_plot = 0;
pthread_mutex_unlock(&nv_mutex);
}
@@ -2959,8 +2953,10 @@ static void ebpf_socket_allocate_global_vectors(int apps)
bandwidth_vector = callocz((size_t)ebpf_nprocs, sizeof(ebpf_bandwidth_t));
socket_values = callocz((size_t)ebpf_nprocs, sizeof(netdata_socket_t));
- inbound_vectors.plot = callocz(network_viewer_opt.max_dim, sizeof(netdata_socket_plot_t));
- outbound_vectors.plot = callocz(network_viewer_opt.max_dim, sizeof(netdata_socket_plot_t));
+ if (network_viewer_opt.enabled) {
+ inbound_vectors.plot = callocz(network_viewer_opt.max_dim, sizeof(netdata_socket_plot_t));
+ outbound_vectors.plot = callocz(network_viewer_opt.max_dim, sizeof(netdata_socket_plot_t));
+ }
}
/**
@@ -3219,12 +3215,11 @@ static void get_ipv6_first_addr(union netdata_ip_t *out, union netdata_ip_t *in,
*
* @return It returns 1 if the IP is inside the range and 0 otherwise
*/
-static int is_ip_inside_range(union netdata_ip_t *rfirst, union netdata_ip_t *rlast,
- union netdata_ip_t *cmpfirst, union netdata_ip_t *cmplast, int family)
+static int ebpf_is_ip_inside_range(union netdata_ip_t *rfirst, union netdata_ip_t *rlast,
+ union netdata_ip_t *cmpfirst, union netdata_ip_t *cmplast, int family)
{
if (family == AF_INET) {
- if (ntohl(rfirst->addr32[0]) <= ntohl(cmpfirst->addr32[0]) &&
- ntohl(rlast->addr32[0]) >= ntohl(cmplast->addr32[0]))
+ if ((rfirst->addr32[0] <= cmpfirst->addr32[0]) && (rlast->addr32[0] >= cmplast->addr32[0]))
return 1;
} else {
if (memcmp(rfirst->addr8, cmpfirst->addr8, sizeof(union netdata_ip_t)) <= 0 &&
@@ -3241,16 +3236,22 @@ static int is_ip_inside_range(union netdata_ip_t *rfirst, union netdata_ip_t *rl
*
* @param out a pointer to the link list.
* @param in the structure that will be linked.
+ * @param table the modified table.
*/
-void fill_ip_list(ebpf_network_viewer_ip_list_t **out, ebpf_network_viewer_ip_list_t *in, char *table)
+void ebpf_fill_ip_list(ebpf_network_viewer_ip_list_t **out, ebpf_network_viewer_ip_list_t *in, char *table)
{
#ifndef NETDATA_INTERNAL_CHECKS
UNUSED(table);
#endif
+ if (in->ver == AF_INET) { // It is simpler to compare using host order
+ in->first.addr32[0] = ntohl(in->first.addr32[0]);
+ in->last.addr32[0] = ntohl(in->last.addr32[0]);
+ }
if (likely(*out)) {
ebpf_network_viewer_ip_list_t *move = *out, *store = *out;
while (move) {
- if (in->ver == move->ver && is_ip_inside_range(&move->first, &move->last, &in->first, &in->last, in->ver)) {
+ if (in->ver == move->ver &&
+ ebpf_is_ip_inside_range(&move->first, &move->last, &in->first, &in->last, in->ver)) {
info("The range/value (%s) is inside the range/value (%s) already inserted, it will be ignored.",
in->value, move->value);
freez(in->value);
@@ -3267,14 +3268,12 @@ void fill_ip_list(ebpf_network_viewer_ip_list_t **out, ebpf_network_viewer_ip_li
}
#ifdef NETDATA_INTERNAL_CHECKS
- char first[512], last[512];
+ char first[256], last[512];
if (in->ver == AF_INET) {
- if (inet_ntop(AF_INET, in->first.addr8, first, INET_ADDRSTRLEN) &&
- inet_ntop(AF_INET, in->last.addr8, last, INET_ADDRSTRLEN))
- info("Adding values %s - %s to %s IP list \"%s\" used on network viewer",
- first, last,
- (*out == network_viewer_opt.included_ips)?"included":"excluded",
- table);
+ info("Adding values %s: (%u - %u) to %s IP list \"%s\" used on network viewer",
+ in->value, in->first.addr32[0], in->last.addr32[0],
+ (*out == network_viewer_opt.included_ips)?"included":"excluded",
+ table);
} else {
if (inet_ntop(AF_INET6, in->first.addr8, first, INET6_ADDRSTRLEN) &&
inet_ntop(AF_INET6, in->last.addr8, last, INET6_ADDRSTRLEN))
@@ -3294,7 +3293,7 @@ void fill_ip_list(ebpf_network_viewer_ip_list_t **out, ebpf_network_viewer_ip_li
* @param out a pointer to store the link list
* @param ip the value given as parameter
*/
-static void parse_ip_list(void **out, char *ip)
+static void ebpf_parse_ip_list(void **out, char *ip)
{
ebpf_network_viewer_ip_list_t **list = (ebpf_network_viewer_ip_list_t **)out;
@@ -3442,7 +3441,7 @@ static void parse_ip_list(void **out, char *ip)
ebpf_network_viewer_ip_list_t *store;
- storethisip:
+storethisip:
store = callocz(1, sizeof(ebpf_network_viewer_ip_list_t));
store->value = ipdup;
store->hash = simple_hash(ipdup);
@@ -3450,7 +3449,7 @@ static void parse_ip_list(void **out, char *ip)
memcpy(store->first.addr8, first.addr8, sizeof(first.addr8));
memcpy(store->last.addr8, last.addr8, sizeof(last.addr8));
- fill_ip_list(list, store, "socket");
+ ebpf_fill_ip_list(list, store, "socket");
return;
cleanipdup:
@@ -3464,7 +3463,7 @@ cleanipdup:
*
* @param ptr is a pointer with the text to parse.
*/
-static void parse_ips(char *ptr)
+static void ebpf_parse_ips(char *ptr)
{
// No value
if (unlikely(!ptr))
@@ -3491,8 +3490,9 @@ static void parse_ips(char *ptr)
}
if (isascii(*ptr)) { // Parse port
- parse_ip_list((!neg)?(void **)&network_viewer_opt.included_ips:(void **)&network_viewer_opt.excluded_ips,
- ptr);
+ ebpf_parse_ip_list((!neg)?(void **)&network_viewer_opt.included_ips:
+ (void **)&network_viewer_opt.excluded_ips,
+ ptr);
}
ptr = end;
@@ -3762,7 +3762,7 @@ void parse_network_viewer_section(struct config *cfg)
value = appconfig_get(cfg, EBPF_NETWORK_VIEWER_SECTION,
"ips", "!127.0.0.1/8 10.0.0.0/8 172.16.0.0/12 192.168.0.0/16 fc00::/7 !::1/128");
- parse_ips(value);
+ ebpf_parse_ips(value);
}
/**
@@ -3916,16 +3916,9 @@ void *ebpf_socket_thread(void *ptr)
{
netdata_thread_cleanup_push(ebpf_socket_exit, ptr);
- memset(&inbound_vectors.tree, 0, sizeof(avl_tree_lock));
- memset(&outbound_vectors.tree, 0, sizeof(avl_tree_lock));
- avl_init_lock(&inbound_vectors.tree, compare_sockets);
- avl_init_lock(&outbound_vectors.tree, compare_sockets);
-
ebpf_module_t *em = (ebpf_module_t *)ptr;
em->maps = socket_maps;
- parse_network_viewer_section(&socket_config);
- parse_service_name_section(&socket_config);
parse_table_size_options(&socket_config);
if (pthread_mutex_init(&nv_mutex, NULL)) {
@@ -3935,7 +3928,15 @@ void *ebpf_socket_thread(void *ptr)
}
ebpf_socket_allocate_global_vectors(em->apps_charts);
- initialize_inbound_outbound();
+
+ if (network_viewer_opt.enabled) {
+ memset(&inbound_vectors.tree, 0, sizeof(avl_tree_lock));
+ memset(&outbound_vectors.tree, 0, sizeof(avl_tree_lock));
+ avl_init_lock(&inbound_vectors.tree, ebpf_compare_sockets);
+ avl_init_lock(&outbound_vectors.tree, ebpf_compare_sockets);
+
+ initialize_inbound_outbound();
+ }
if (running_on_kernel < NETDATA_EBPF_KERNEL_5_0)
em->mode = MODE_ENTRY;
@@ -3966,7 +3967,7 @@ void *ebpf_socket_thread(void *ptr)
pthread_mutex_unlock(&lock);
- socket_collector((usec_t)(em->update_every * USEC_PER_SEC), em);
+ socket_collector(em);
endsocket:
ebpf_update_disabled_plugin_stats(em);
diff --git a/collectors/ebpf.plugin/ebpf_socket.h b/collectors/ebpf.plugin/ebpf_socket.h
index ca6b193f..63b1e107 100644
--- a/collectors/ebpf.plugin/ebpf_socket.h
+++ b/collectors/ebpf.plugin/ebpf_socket.h
@@ -244,6 +244,7 @@ typedef struct ebpf_network_viewer_hostname_list {
#define NETDATA_NV_CAP_VALUE 50L
typedef struct ebpf_network_viewer_options {
+ uint32_t enabled;
uint32_t max_dim; // Store value read from 'maximum dimensions'
uint32_t hostname_resolution_enabled;
@@ -360,7 +361,7 @@ void clean_port_structure(ebpf_network_viewer_port_list_t **clean);
extern ebpf_network_viewer_port_list_t *listen_ports;
void update_listen_table(uint16_t value, uint16_t proto, netdata_passive_connection_t *values);
void parse_network_viewer_section(struct config *cfg);
-void fill_ip_list(ebpf_network_viewer_ip_list_t **out, ebpf_network_viewer_ip_list_t *in, char *table);
+void ebpf_fill_ip_list(ebpf_network_viewer_ip_list_t **out, ebpf_network_viewer_ip_list_t *in, char *table);
void parse_service_name_section(struct config *cfg);
extern ebpf_socket_publish_apps_t **socket_bandwidth_curr;
diff --git a/collectors/ebpf.plugin/ebpf_softirq.c b/collectors/ebpf.plugin/ebpf_softirq.c
index 3b5d1592..49e9c305 100644
--- a/collectors/ebpf.plugin/ebpf_softirq.c
+++ b/collectors/ebpf.plugin/ebpf_softirq.c
@@ -54,17 +54,6 @@ static softirq_val_t softirq_vals[] = {
// tmp store for soft IRQ values we get from a per-CPU eBPF map.
static softirq_ebpf_val_t *softirq_ebpf_vals = NULL;
-static struct netdata_static_thread softirq_threads = {
- .name = "SOFTIRQ KERNEL",
- .config_section = NULL,
- .config_name = NULL,
- .env_name = NULL,
- .enabled = 1,
- .thread = NULL,
- .init_routine = NULL,
- .start_routine = NULL
-};
-
/**
* Cachestat Free
*
@@ -75,40 +64,20 @@ static struct netdata_static_thread softirq_threads = {
static void ebpf_softirq_free(ebpf_module_t *em)
{
pthread_mutex_lock(&ebpf_exit_cleanup);
- if (em->thread->enabled == NETDATA_THREAD_EBPF_RUNNING) {
- em->thread->enabled = NETDATA_THREAD_EBPF_STOPPING;
- pthread_mutex_unlock(&ebpf_exit_cleanup);
- return;
- }
+ em->thread->enabled = NETDATA_THREAD_EBPF_STOPPING;
pthread_mutex_unlock(&ebpf_exit_cleanup);
- freez(softirq_threads.thread);
-
for (int i = 0; softirq_tracepoints[i].class != NULL; i++) {
ebpf_disable_tracepoint(&softirq_tracepoints[i]);
}
freez(softirq_ebpf_vals);
pthread_mutex_lock(&ebpf_exit_cleanup);
- em->thread->enabled = NETDATA_MAIN_THREAD_EXITED;
+ em->thread->enabled = NETDATA_THREAD_EBPF_STOPPED;
pthread_mutex_unlock(&ebpf_exit_cleanup);
}
/**
- * Exit
- *
- * Cancel thread.
- *
- * @param ptr thread data.
- */
-static void softirq_exit(void *ptr)
-{
- ebpf_module_t *em = (ebpf_module_t *)ptr;
- netdata_thread_cancel(*softirq_threads.thread);
- ebpf_softirq_free(em);
-}
-
-/**
* Cleanup
*
* Clean up allocated memory.
@@ -146,28 +115,6 @@ static void softirq_read_latency_map()
}
}
-/**
- * Read eBPF maps for soft IRQ.
- */
-static void *softirq_reader(void *ptr)
-{
- netdata_thread_cleanup_push(softirq_exit, ptr);
- heartbeat_t hb;
- heartbeat_init(&hb);
-
- ebpf_module_t *em = (ebpf_module_t *)ptr;
-
- usec_t step = NETDATA_SOFTIRQ_SLEEP_MS * em->update_every;
- while (!ebpf_exit_plugin) {
- (void)heartbeat_next(&hb, step);
-
- softirq_read_latency_map();
- }
-
- netdata_thread_cleanup_pop(1);
- return NULL;
-}
-
static void softirq_create_charts(int update_every)
{
ebpf_create_chart(
@@ -212,17 +159,6 @@ static void softirq_collector(ebpf_module_t *em)
{
softirq_ebpf_vals = callocz(ebpf_nprocs, sizeof(softirq_ebpf_val_t));
- // create reader thread.
- softirq_threads.thread = mallocz(sizeof(netdata_thread_t));
- softirq_threads.start_routine = softirq_reader;
- netdata_thread_create(
- softirq_threads.thread,
- softirq_threads.name,
- NETDATA_THREAD_OPTION_DEFAULT,
- softirq_reader,
- em
- );
-
// create chart and static dims.
pthread_mutex_lock(&lock);
softirq_create_charts(em->update_every);
@@ -233,13 +169,16 @@ static void softirq_collector(ebpf_module_t *em)
// loop and read from published data until ebpf plugin is closed.
heartbeat_t hb;
heartbeat_init(&hb);
- usec_t step = em->update_every * USEC_PER_SEC;
+ int update_every = em->update_every;
+ int counter = update_every - 1;
//This will be cancelled by its parent
while (!ebpf_exit_plugin) {
- (void)heartbeat_next(&hb, step);
- if (ebpf_exit_plugin)
- break;
+ (void)heartbeat_next(&hb, USEC_PER_SEC);
+ if (ebpf_exit_plugin || ++counter != update_every)
+ continue;
+ counter = 0;
+ softirq_read_latency_map();
pthread_mutex_lock(&lock);
// write dims now for all hitherto discovered IRQs.
diff --git a/collectors/ebpf.plugin/ebpf_softirq.h b/collectors/ebpf.plugin/ebpf_softirq.h
index 7dcddbb4..eea2a184 100644
--- a/collectors/ebpf.plugin/ebpf_softirq.h
+++ b/collectors/ebpf.plugin/ebpf_softirq.h
@@ -20,7 +20,6 @@ typedef struct softirq_ebpf_val {
*****************************************************************/
#define NETDATA_EBPF_MODULE_NAME_SOFTIRQ "softirq"
-#define NETDATA_SOFTIRQ_SLEEP_MS 650000ULL
#define NETDATA_SOFTIRQ_CONFIG_FILE "softirq.conf"
typedef struct sofirq_val {
diff --git a/collectors/ebpf.plugin/ebpf_swap.c b/collectors/ebpf.plugin/ebpf_swap.c
index 8199573a..059efb63 100644
--- a/collectors/ebpf.plugin/ebpf_swap.c
+++ b/collectors/ebpf.plugin/ebpf_swap.c
@@ -34,17 +34,6 @@ static ebpf_local_maps_t swap_maps[] = {{.name = "tbl_pid_swap", .internal_input
.map_fd = ND_EBPF_MAP_FD_NOT_INITIALIZED},
{.name = NULL, .internal_input = 0, .user_input = 0}};
-struct netdata_static_thread swap_threads = {
- .name = "SWAP KERNEL",
- .config_section = NULL,
- .config_name = NULL,
- .env_name = NULL,
- .enabled = 1,
- .thread = NULL,
- .init_routine = NULL,
- .start_routine = NULL
-};
-
netdata_ebpf_targets_t swap_targets[] = { {.name = "swap_readpage", .mode = EBPF_LOAD_TRAMPOLINE},
{.name = "swap_writepage", .mode = EBPF_LOAD_TRAMPOLINE},
{.name = NULL, .mode = EBPF_LOAD_TRAMPOLINE}};
@@ -184,7 +173,7 @@ static void ebpf_swap_disable_release_task(struct swap_bpf *obj)
* @param obj is the main structure for bpf objects.
* @param em structure with configuration
*
- * @return it returns 0 on succes and -1 otherwise
+ * @return it returns 0 on success and -1 otherwise
*/
static inline int ebpf_swap_load_and_attach(struct swap_bpf *obj, ebpf_module_t *em)
{
@@ -236,18 +225,13 @@ static inline int ebpf_swap_load_and_attach(struct swap_bpf *obj, ebpf_module_t
static void ebpf_swap_free(ebpf_module_t *em)
{
pthread_mutex_lock(&ebpf_exit_cleanup);
- if (em->thread->enabled == NETDATA_THREAD_EBPF_RUNNING) {
- em->thread->enabled = NETDATA_THREAD_EBPF_STOPPING;
- pthread_mutex_unlock(&ebpf_exit_cleanup);
- return;
- }
+ em->thread->enabled = NETDATA_THREAD_EBPF_STOPPING;
pthread_mutex_unlock(&ebpf_exit_cleanup);
ebpf_cleanup_publish_syscall(swap_publish_aggregated);
freez(swap_vector);
freez(swap_values);
- freez(swap_threads.thread);
#ifdef LIBBPF_MAJOR_VERSION
if (bpf_obj)
@@ -268,20 +252,6 @@ static void ebpf_swap_free(ebpf_module_t *em)
static void ebpf_swap_exit(void *ptr)
{
ebpf_module_t *em = (ebpf_module_t *)ptr;
- netdata_thread_cancel(*swap_threads.thread);
- ebpf_swap_free(em);
-}
-
-/**
- * Swap cleanup
- *
- * Clean up allocated memory.
- *
- * @param ptr thread data.
- */
-static void ebpf_swap_cleanup(void *ptr)
-{
- ebpf_module_t *em = (ebpf_module_t *)ptr;
ebpf_swap_free(em);
}
@@ -412,7 +382,7 @@ static void swap_send_global()
*
* Read the table with number of calls to all functions
*/
-static void read_global_table()
+static void ebpf_swap_read_global_table()
{
netdata_idx_t *stored = swap_values;
netdata_idx_t *val = swap_hash_values;
@@ -433,33 +403,6 @@ static void read_global_table()
}
/**
- * Swap read hash
- *
- * This is the thread callback.
- *
- * @param ptr It is a NULL value for this thread.
- *
- * @return It always returns NULL.
- */
-void *ebpf_swap_read_hash(void *ptr)
-{
- netdata_thread_cleanup_push(ebpf_swap_cleanup, ptr);
- heartbeat_t hb;
- heartbeat_init(&hb);
-
- ebpf_module_t *em = (ebpf_module_t *)ptr;
- usec_t step = NETDATA_SWAP_SLEEP_MS * em->update_every;
- while (!ebpf_exit_plugin) {
- (void)heartbeat_next(&hb, step);
-
- read_global_table();
- }
-
- netdata_thread_cleanup_pop(1);
- return NULL;
-}
-
-/**
* Sum PIDs
*
* Sum values for all targets.
@@ -714,23 +657,19 @@ void ebpf_swap_send_cgroup_data(int update_every)
*/
static void swap_collector(ebpf_module_t *em)
{
- swap_threads.thread = mallocz(sizeof(netdata_thread_t));
- swap_threads.start_routine = ebpf_swap_read_hash;
-
- netdata_thread_create(swap_threads.thread, swap_threads.name, NETDATA_THREAD_OPTION_DEFAULT,
- ebpf_swap_read_hash, em);
-
int cgroup = em->cgroup_charts;
int update_every = em->update_every;
heartbeat_t hb;
heartbeat_init(&hb);
- usec_t step = update_every * USEC_PER_SEC;
+ int counter = update_every - 1;
while (!ebpf_exit_plugin) {
- (void)heartbeat_next(&hb, step);
- if (ebpf_exit_plugin)
- break;
+ (void)heartbeat_next(&hb, USEC_PER_SEC);
+ if (ebpf_exit_plugin || ++counter != update_every)
+ continue;
+ counter = 0;
netdata_apps_integration_flags_t apps = em->apps_charts;
+ ebpf_swap_read_global_table();
pthread_mutex_lock(&collect_data_mutex);
if (apps)
read_apps_table();
diff --git a/collectors/ebpf.plugin/ebpf_sync.c b/collectors/ebpf.plugin/ebpf_sync.c
index 84049753..7c81c1df 100644
--- a/collectors/ebpf.plugin/ebpf_sync.c
+++ b/collectors/ebpf.plugin/ebpf_sync.c
@@ -10,17 +10,6 @@ static netdata_publish_syscall_t sync_counter_publish_aggregated[NETDATA_SYNC_ID
static netdata_idx_t sync_hash_values[NETDATA_SYNC_IDX_END];
-struct netdata_static_thread sync_threads = {
- .name = "SYNC KERNEL",
- .config_section = NULL,
- .config_name = NULL,
- .env_name = NULL,
- .enabled = 1,
- .thread = NULL,
- .init_routine = NULL,
- .start_routine = NULL
-};
-
static ebpf_local_maps_t sync_maps[] = {{.name = "tbl_sync", .internal_input = NETDATA_SYNC_END,
.user_input = 0, .type = NETDATA_EBPF_MAP_STATIC,
.map_fd = ND_EBPF_MAP_FD_NOT_INITIALIZED},
@@ -77,7 +66,7 @@ static inline void ebpf_sync_disable_probe(struct sync_bpf *obj)
}
/**
- * Disable tramppoline
+ * Disable trampoline
*
* Disable trampoline to use another method.
*
@@ -140,7 +129,7 @@ static void ebpf_sync_set_hash_tables(struct sync_bpf *obj, sync_syscalls_index_
* @param target the syscall that we are attaching a tracer.
* @param idx the index for the main structure
*
- * @return it returns 0 on succes and -1 otherwise
+ * @return it returns 0 on success and -1 otherwise
*/
static inline int ebpf_sync_load_and_attach(struct sync_bpf *obj, ebpf_module_t *em, char *target,
sync_syscalls_index_t idx)
@@ -216,17 +205,12 @@ void ebpf_sync_cleanup_objects()
static void ebpf_sync_free(ebpf_module_t *em)
{
pthread_mutex_lock(&ebpf_exit_cleanup);
- if (em->thread->enabled == NETDATA_THREAD_EBPF_RUNNING) {
- em->thread->enabled = NETDATA_THREAD_EBPF_STOPPING;
- pthread_mutex_unlock(&ebpf_exit_cleanup);
- return;
- }
+ em->thread->enabled = NETDATA_THREAD_EBPF_STOPPING;
pthread_mutex_unlock(&ebpf_exit_cleanup);
#ifdef LIBBPF_MAJOR_VERSION
ebpf_sync_cleanup_objects();
#endif
- freez(sync_threads.thread);
pthread_mutex_lock(&ebpf_exit_cleanup);
em->thread->enabled = NETDATA_THREAD_EBPF_STOPPED;
@@ -243,18 +227,6 @@ static void ebpf_sync_free(ebpf_module_t *em)
static void ebpf_sync_exit(void *ptr)
{
ebpf_module_t *em = (ebpf_module_t *)ptr;
- netdata_thread_cancel(*sync_threads.thread);
- ebpf_sync_free(em);
-}
-
-/**
- * Clean up the main thread.
- *
- * @param ptr thread data.
- */
-static void ebpf_sync_cleanup(void *ptr)
-{
- ebpf_module_t *em = (ebpf_module_t *)ptr;
ebpf_sync_free(em);
}
@@ -350,7 +322,7 @@ static int ebpf_sync_initialize_syscall(ebpf_module_t *em)
*
* Read the table with number of calls for all functions
*/
-static void read_global_table()
+static void ebpf_sync_read_global_table()
{
netdata_idx_t stored;
uint32_t idx = NETDATA_SYNC_CALL;
@@ -366,34 +338,6 @@ static void read_global_table()
}
/**
- * Sync read hash
- *
- * This is the thread callback.
- *
- * @param ptr It is a NULL value for this thread.
- *
- * @return It always returns NULL.
- */
-void *ebpf_sync_read_hash(void *ptr)
-{
- netdata_thread_cleanup_push(ebpf_sync_cleanup, ptr);
- ebpf_module_t *em = (ebpf_module_t *)ptr;
-
- heartbeat_t hb;
- heartbeat_init(&hb);
- usec_t step = NETDATA_EBPF_SYNC_SLEEP_MS * em->update_every;
-
- while (!ebpf_exit_plugin) {
- (void)heartbeat_next(&hb, step);
-
- read_global_table();
- }
-
- netdata_thread_cleanup_pop(1);
- return NULL;
-}
-
-/**
* Create Sync charts
*
* Create charts and dimensions according user input.
@@ -452,20 +396,17 @@ static void sync_send_data()
*/
static void sync_collector(ebpf_module_t *em)
{
- sync_threads.thread = mallocz(sizeof(netdata_thread_t));
- sync_threads.start_routine = ebpf_sync_read_hash;
-
- netdata_thread_create(sync_threads.thread, sync_threads.name, NETDATA_THREAD_OPTION_DEFAULT,
- ebpf_sync_read_hash, em);
-
heartbeat_t hb;
heartbeat_init(&hb);
- usec_t step = em->update_every * USEC_PER_SEC;
+ int update_every = em->update_every;
+ int counter = update_every - 1;
while (!ebpf_exit_plugin) {
- (void)heartbeat_next(&hb, step);
- if (ebpf_exit_plugin)
- break;
+ (void)heartbeat_next(&hb, USEC_PER_SEC);
+ if (ebpf_exit_plugin || ++counter != update_every)
+ continue;
+ counter = 0;
+ ebpf_sync_read_global_table();
pthread_mutex_lock(&lock);
sync_send_data();
diff --git a/collectors/ebpf.plugin/ebpf_vfs.c b/collectors/ebpf.plugin/ebpf_vfs.c
index ad6de4a0..b3c0ba45 100644
--- a/collectors/ebpf.plugin/ebpf_vfs.c
+++ b/collectors/ebpf.plugin/ebpf_vfs.c
@@ -34,17 +34,6 @@ struct config vfs_config = { .first_section = NULL,
.index = { .avl_tree = { .root = NULL, .compar = appconfig_section_compare },
.rwlock = AVL_LOCK_INITIALIZER } };
-struct netdata_static_thread vfs_threads = {
- .name = "VFS KERNEL",
- .config_section = NULL,
- .config_name = NULL,
- .env_name = NULL,
- .enabled = 1,
- .thread = NULL,
- .init_routine = NULL,
- .start_routine = NULL
-};
-
netdata_ebpf_targets_t vfs_targets[] = { {.name = "vfs_write", .mode = EBPF_LOAD_TRAMPOLINE},
{.name = "vfs_writev", .mode = EBPF_LOAD_TRAMPOLINE},
{.name = "vfs_read", .mode = EBPF_LOAD_TRAMPOLINE},
@@ -357,7 +346,7 @@ static void ebpf_vfs_disable_release_task(struct vfs_bpf *obj)
* @param obj is the main structure for bpf objects.
* @param em structure with configuration
*
- * @return it returns 0 on succes and -1 otherwise
+ * @return it returns 0 on success and -1 otherwise
*/
static inline int ebpf_vfs_load_and_attach(struct vfs_bpf *obj, ebpf_module_t *em)
{
@@ -409,16 +398,11 @@ static inline int ebpf_vfs_load_and_attach(struct vfs_bpf *obj, ebpf_module_t *e
static void ebpf_vfs_free(ebpf_module_t *em)
{
pthread_mutex_lock(&ebpf_exit_cleanup);
- if (em->thread->enabled == NETDATA_THREAD_EBPF_RUNNING) {
- em->thread->enabled = NETDATA_THREAD_EBPF_STOPPING;
- pthread_mutex_unlock(&ebpf_exit_cleanup);
- return;
- }
+ em->thread->enabled = NETDATA_THREAD_EBPF_STOPPING;
pthread_mutex_unlock(&ebpf_exit_cleanup);
freez(vfs_hash_values);
freez(vfs_vector);
- freez(vfs_threads.thread);
#ifdef LIBBPF_MAJOR_VERSION
if (bpf_obj)
@@ -440,18 +424,6 @@ static void ebpf_vfs_free(ebpf_module_t *em)
static void ebpf_vfs_exit(void *ptr)
{
ebpf_module_t *em = (ebpf_module_t *)ptr;
- netdata_thread_cancel(*vfs_threads.thread);
- ebpf_vfs_free(em);
-}
-
-/**
-* Clean up the main thread.
-*
-* @param ptr thread data.
-**/
-static void ebpf_vfs_cleanup(void *ptr)
-{
- ebpf_module_t *em = (ebpf_module_t *)ptr;
ebpf_vfs_free(em);
}
@@ -518,7 +490,7 @@ static void ebpf_vfs_send_data(ebpf_module_t *em)
/**
* Read the hash table and store data to allocated vectors.
*/
-static void read_global_table()
+static void ebpf_vfs_read_global_table()
{
uint64_t idx;
netdata_idx_t res[NETDATA_VFS_COUNTER];
@@ -874,36 +846,6 @@ static void read_update_vfs_cgroup()
}
/**
- * VFS read hash
- *
- * This is the thread callback.
- * This thread is necessary, because we cannot freeze the whole plugin to read the data.
- *
- * @param ptr It is a NULL value for this thread.
- *
- * @return It always returns NULL.
- */
-void *ebpf_vfs_read_hash(void *ptr)
-{
- netdata_thread_cleanup_push(ebpf_vfs_cleanup, ptr);
- heartbeat_t hb;
- heartbeat_init(&hb);
-
- ebpf_module_t *em = (ebpf_module_t *)ptr;
-
- usec_t step = NETDATA_LATENCY_VFS_SLEEP_MS * em->update_every;
- //This will be cancelled by its parent
- while (!ebpf_exit_plugin) {
- (void)heartbeat_next(&hb, step);
-
- read_global_table();
- }
-
- netdata_thread_cleanup_pop(1);
- return NULL;
-}
-
-/**
* Sum PIDs
*
* Sum values for all targets.
@@ -1525,22 +1467,19 @@ static void ebpf_vfs_send_cgroup_data(ebpf_module_t *em)
*/
static void vfs_collector(ebpf_module_t *em)
{
- vfs_threads.thread = mallocz(sizeof(netdata_thread_t));
- vfs_threads.start_routine = ebpf_vfs_read_hash;
-
- netdata_thread_create(vfs_threads.thread, vfs_threads.name, NETDATA_THREAD_OPTION_DEFAULT,
- ebpf_vfs_read_hash, em);
-
int cgroups = em->cgroup_charts;
heartbeat_t hb;
heartbeat_init(&hb);
- usec_t step = em->update_every * USEC_PER_SEC;
+ int update_every = em->update_every;
+ int counter = update_every - 1;
while (!ebpf_exit_plugin) {
- (void)heartbeat_next(&hb, step);
- if (ebpf_exit_plugin)
- break;
+ (void)heartbeat_next(&hb, USEC_PER_SEC);
+ if (ebpf_exit_plugin || ++counter != update_every)
+ continue;
+ counter = 0;
netdata_apps_integration_flags_t apps = em->apps_charts;
+ ebpf_vfs_read_global_table();
pthread_mutex_lock(&collect_data_mutex);
if (apps)
ebpf_vfs_read_apps();
diff --git a/collectors/ebpf.plugin/ebpf_vfs.h b/collectors/ebpf.plugin/ebpf_vfs.h
index 2e3c7cc2..d7fc2672 100644
--- a/collectors/ebpf.plugin/ebpf_vfs.h
+++ b/collectors/ebpf.plugin/ebpf_vfs.h
@@ -8,8 +8,6 @@
#define NETDATA_DIRECTORY_VFS_CONFIG_FILE "vfs.conf"
-#define NETDATA_LATENCY_VFS_SLEEP_MS 750000ULL
-
// Global chart name
#define NETDATA_VFS_FILE_CLEAN_COUNT "vfs_deleted_objects"
#define NETDATA_VFS_FILE_IO_COUNT "vfs_io"
diff --git a/collectors/fping.plugin/Makefile.am b/collectors/fping.plugin/Makefile.am
deleted file mode 100644
index 90654832..00000000
--- a/collectors/fping.plugin/Makefile.am
+++ /dev/null
@@ -1,24 +0,0 @@
-# SPDX-License-Identifier: GPL-3.0-or-later
-
-AUTOMAKE_OPTIONS = subdir-objects
-MAINTAINERCLEANFILES = $(srcdir)/Makefile.in
-
-CLEANFILES = \
- fping.plugin \
- $(NULL)
-
-include $(top_srcdir)/build/subst.inc
-SUFFIXES = .in
-
-dist_plugins_SCRIPTS = \
- fping.plugin \
- $(NULL)
-
-dist_noinst_DATA = \
- fping.plugin.in \
- README.md \
- $(NULL)
-
-dist_libconfig_DATA = \
- fping.conf \
- $(NULL)
diff --git a/collectors/fping.plugin/README.md b/collectors/fping.plugin/README.md
deleted file mode 100644
index e32d3911..00000000
--- a/collectors/fping.plugin/README.md
+++ /dev/null
@@ -1,110 +0,0 @@
-<!--
-title: "fping.plugin"
-custom_edit_url: https://github.com/netdata/netdata/edit/master/collectors/fping.plugin/README.md
--->
-
-# fping.plugin
-
-The fping plugin supports monitoring latency, packet loss and uptime of any number of network end points,
-by pinging them with `fping`.
-
-This plugin requires version 5.1 or newer of `fping` (earlier versions may or may not work). Our static builds and
-Docker images come bundled with a known working version of `fping`. Native packages and local builds will need to
-have a working version installed before the plugin is usable.
-
-## Installing fping locally
-
-If your distribution’s repositories do not include a working version of `fping`, the supplied plugin can install
-it, by running:
-
-```sh
-/usr/libexec/netdata/plugins.d/fping.plugin install
-```
-
-The above will download, build and install the right version as `/usr/local/bin/fping`. This requires a working C
-compiler, GNU autotools (at least autoconf and automake), and GNU make. On Debian or Ubuntu, you can pull in most
-of the required tools by installing the `build-essential` package (this should include everything except automake
-and autoconf).
-
-## Configuration
-
-Then you need to edit `/etc/netdata/fping.conf` (to edit it on your system run
-`/etc/netdata/edit-config fping.conf`) like this:
-
-```sh
-# set here all the hosts you need to ping
-# I suggest to use hostnames and put their IPs in /etc/hosts
-hosts="host1 host2 host3"
-
-# override the chart update frequency - the default is inherited from Netdata
-update_every=1
-
-# time in milliseconds (1 sec = 1000 ms) to ping the hosts
-# 200 = 5 pings per second
-ping_every=200
-
-# other fping options - these are the defaults
-fping_opts="-R -b 56 -i 1 -r 0 -t 5000"
-```
-
-## alarms
-
-Netdata will automatically attach a few alarms for each host.
-Check the [latest versions of the fping alarms](https://raw.githubusercontent.com/netdata/netdata/master/health/health.d/fping.conf)
-
-## Additional Tips
-
-### Customizing Amount of Pings Per Second
-
-For example, to update the chart every 10 seconds and use 2 pings every 10 seconds, use this:
-
-```sh
-# Chart Update Frequency (Time in Seconds)
-update_every=10
-
-# Time in Milliseconds (1 sec = 1000 ms) to Ping the Hosts
-# The Following Example Sends 1 Ping Every 5000 ms
-# Calculation Formula: ping_every = (update_every * 1000 ) / 2
-ping_every=5000
-```
-
-### Multiple fping Plugins With Different Settings
-
-You may need to run multiple fping plugins with different settings for different end points.
-For example, you may need to ping a few hosts 10 times per second, and others once per second.
-
-Netdata allows you to add as many `fping` plugins as you like.
-
-Follow this procedure:
-
-**1. Create New fping Configuration File**
-
-```sh
-# Step Into Configuration Directory
-cd /etc/netdata
-
-# Copy Original fping Configuration File To New Configuration File
-cp fping.conf fping2.conf
-```
-
-Edit `fping2.conf` and set the settings and the hosts you need for the seconds instance.
-
-**2. Soft Link Original fping Plugin to New Plugin File**
-
-```sh
-# Become root (If The Step Step Is Performed As Non-Root User)
-sudo su
-
-# Step Into The Plugins Directory
-cd /usr/libexec/netdata/plugins.d
-
-# Link fping.plugin to fping2.plugin
-ln -s fping.plugin fping2.plugin
-```
-
-That's it. Netdata will detect the new plugin and start it.
-
-You can name the new plugin any name you like.
-Just make sure the plugin and the configuration file have the same name.
-
-
diff --git a/collectors/fping.plugin/fping.conf b/collectors/fping.plugin/fping.conf
deleted file mode 100644
index 63a7f7ac..00000000
--- a/collectors/fping.plugin/fping.conf
+++ /dev/null
@@ -1,44 +0,0 @@
-# no need for shebang - this file is sourced from fping.plugin
-
-# fping.plugin requires a recent version of fping.
-#
-# You can get it on your system, by running:
-#
-# /usr/libexec/netdata/plugins.d/fping.plugin install
-
-# -----------------------------------------------------------------------------
-# configuration options
-
-# The fping binary to use. We need one that can output netdata friendly info
-# (supporting: -N). If you have multiple versions, put here the full filename
-# of the right one
-
-#fping="/usr/local/bin/fping"
-
-
-# a space separated list of hosts to fping
-# we suggest to put names here and the IPs of these names in /etc/hosts
-
-hosts=""
-
-
-# The update frequency of the chart - the default is inherited from netdata
-
-#update_every=2
-
-
-# The time in milliseconds (1 sec = 1000 ms) to ping the hosts
-# by default 5 pings per host per iteration
-# fping will not allow this to be below 20ms
-
-#ping_every="200"
-
-
-# other fping options - defaults:
-# -R = send packets with random data
-# -b 56 = the number of bytes per packet
-# -i 1 = 1 ms when sending packets to others hosts (switching hosts)
-# -r 0 = never retry packets
-# -t 5000 = per packet timeout at 5000 ms
-
-#fping_opts="-R -b 56 -i 1 -r 0 -t 5000"
diff --git a/collectors/fping.plugin/fping.plugin.in b/collectors/fping.plugin/fping.plugin.in
deleted file mode 100755
index 4b3d1d12..00000000
--- a/collectors/fping.plugin/fping.plugin.in
+++ /dev/null
@@ -1,202 +0,0 @@
-#!/usr/bin/env bash
-# SPDX-License-Identifier: GPL-3.0-or-later
-
-# netdata
-# real-time performance and health monitoring, done right!
-# (C) 2017 Costa Tsaousis <costa@tsaousis.gr>
-# GPL v3+
-#
-# This plugin requires a latest version of fping.
-# You can compile it from source, by running me with option: install
-
-export PATH="${PATH}:/sbin:/usr/sbin:/usr/local/sbin"
-export LC_ALL=C
-
-if [ "${1}" = "install" ]
- then
- [ "${UID}" != 0 ] && echo >&2 "Please run me as root. This will install a single binary file: /usr/local/bin/fping." && exit 1
-
- [ -z "${2}" ] && fping_version="5.1" || fping_version="${2}"
-
- run() {
- printf >&2 " > "
- printf >&2 "%q " "${@}"
- printf >&2 "\n"
- "${@}" || exit 1
- }
-
- download() {
- local curl="$(which curl 2>/dev/null || command -v curl 2>/dev/null)"
- [ ! -z "${curl}" ] && run curl -s -L "${1}" && return 0
-
- local wget="$(which wget 2>/dev/null || command -v wget 2>/dev/null)"
- [ ! -z "${wget}" ] && run wget -q -O - "${1}" && return 0
-
- echo >&2 "Cannot find 'curl' or 'wget' in this system." && exit 1
- }
-
- [ ! -d /usr/src ] && run mkdir -p /usr/src
- [ ! -d /usr/local/bin ] && run mkdir -p /usr/local/bin
-
- run cd /usr/src
-
- if [ -d "fping-${fping_version}" ]
- then
- run rm -rf "fping-${fping_version}" || exit 1
- fi
-
- download "https://github.com/schweikert/fping/releases/download/v${fping_version}/fping-${fping_version}.tar.gz" | run tar -zxvpf -
- [ $? -ne 0 ] && exit 1
- run cd "fping-${fping_version}" || exit 1
-
- run ./configure --prefix=/usr/local
- run make clean
- run make
- if [ -f /usr/local/bin/fping ]
- then
- run mv -f /usr/local/bin/fping /usr/local/bin/fping.old
- fi
- run mv src/fping /usr/local/bin/fping
- run chown root:root /usr/local/bin/fping
- run chmod 4755 /usr/local/bin/fping
- echo >&2
- echo >&2 "All done, you have a compatible fping now at /usr/local/bin/fping."
- echo >&2
-
- fping="$(which fping 2>/dev/null || command -v fping 2>/dev/null)"
- if [ "${fping}" != "/usr/local/bin/fping" ]
- then
- echo >&2 "You have another fping installed at: ${fping}."
- echo >&2 "Please set:"
- echo >&2
- echo >&2 " fping=\"/usr/local/bin/fping\""
- echo >&2
- echo >&2 "at /etc/netdata/fping.conf"
- echo >&2
- fi
- exit 0
-fi
-
-# -----------------------------------------------------------------------------
-
-PROGRAM_NAME="$(basename "${0}")"
-
-logdate() {
- date "+%Y-%m-%d %H:%M:%S"
-}
-
-log() {
- local status="${1}"
- shift
-
- echo >&2 "$(logdate): ${PROGRAM_NAME}: ${status}: ${*}"
-
-}
-
-warning() {
- log WARNING "${@}"
-}
-
-error() {
- log ERROR "${@}"
-}
-
-info() {
- log INFO "${@}"
-}
-
-fatal() {
- log FATAL "${@}"
- echo "DISABLE"
- exit 1
-}
-
-debug=0
-debug() {
- [ $debug -eq 1 ] && log DEBUG "${@}"
-}
-
-# -----------------------------------------------------------------------------
-
-# store in ${plugin} the name we run under
-# this allows us to copy/link fping.plugin under a different name
-# to have multiple fping plugins running with different settings
-plugin="${PROGRAM_NAME/.plugin/}"
-
-
-# -----------------------------------------------------------------------------
-
-# the frequency to send info to netdata
-# passed by netdata as the first parameter
-update_every="${1-1}"
-
-# the netdata configuration directory
-# passed by netdata as an environment variable
-[ -z "${NETDATA_USER_CONFIG_DIR}" ] && NETDATA_USER_CONFIG_DIR="@configdir_POST@"
-[ -z "${NETDATA_STOCK_CONFIG_DIR}" ] && NETDATA_STOCK_CONFIG_DIR="@libconfigdir_POST@"
-
-# -----------------------------------------------------------------------------
-# configuration options
-# can be overwritten at /etc/netdata/fping.conf
-
-# the fping binary to use
-# we need one that can output netdata friendly info (supporting: -N)
-# if you have multiple versions, put here the full filename of the right one
-fping="$( which fping 2>/dev/null || command -v fping 2>/dev/null )"
-
-# a space separated list of hosts to fping
-# we suggest to put names here and the IPs of these names in /etc/hosts
-hosts=""
-
-# the time in milliseconds (1 sec = 1000 ms)
-# to ping the hosts - by default 5 pings per host per iteration
-ping_every="$((update_every * 1000 / 5))"
-
-# fping options
-fping_opts="-R -b 56 -i 1 -r 0 -t 5000"
-
-# -----------------------------------------------------------------------------
-# load the configuration files
-
-for CONFIG in "${NETDATA_STOCK_CONFIG_DIR}/${plugin}.conf" "${NETDATA_USER_CONFIG_DIR}/${plugin}.conf"
-do
- if [ -f "${CONFIG}" ]
- then
- info "Loading config file '${CONFIG}'..."
- source "${CONFIG}"
- [ $? -ne 0 ] && error "Failed to load config file '${CONFIG}'."
- else
- warning "Cannot find file '${CONFIG}'."
- fi
-done
-
-if [ -z "${hosts}" ]
-then
- fatal "no hosts configured - nothing to do."
-fi
-
-if [ -z "${fping}" ]
-then
- fatal "fping command is not found. Please set its full path in '${NETDATA_USER_CONFIG_DIR}/${plugin}.conf'"
-fi
-
-if [ ! -x "${fping}" ]
-then
- fatal "fping command '${fping}' is not executable - cannot proceed."
-fi
-
-if [ ${ping_every} -lt 20 ]
- then
- warning "ping every was set to ${ping_every} but 20 is the minimum for non-root users. Setting it to 20 ms."
- ping_every=20
-fi
-
-# the fping options we will use
-options=( -N -l -Q ${update_every} -p ${ping_every} ${fping_opts} ${hosts} )
-
-# execute fping
-info "starting fping: ${fping} ${options[*]}"
-exec "${fping}" "${options[@]}"
-
-# if we cannot execute fping, stop
-fatal "command '${fping} ${options[*]}' failed to be executed (returned code $?)."
diff --git a/collectors/freebsd.plugin/README.md b/collectors/freebsd.plugin/README.md
index 9a97a7ec..3d37a41f 100644
--- a/collectors/freebsd.plugin/README.md
+++ b/collectors/freebsd.plugin/README.md
@@ -1,6 +1,10 @@
<!--
-title: "freebsd.plugin"
-custom_edit_url: https://github.com/netdata/netdata/edit/master/collectors/freebsd.plugin/README.md
+title: "FreeBSD system metrics (freebsd.plugin)"
+custom_edit_url: "https://github.com/netdata/netdata/edit/master/collectors/freebsd.plugin/README.md"
+sidebar_label: "FreeBSD system metrics (freebsd.plugin)"
+learn_status: "Published"
+learn_topic_type: "References"
+learn_rel_path: "References/Collectors references/System metrics"
-->
# freebsd.plugin
diff --git a/collectors/freebsd.plugin/freebsd_devstat.c b/collectors/freebsd.plugin/freebsd_devstat.c
index 0f037741..d4180d33 100644
--- a/collectors/freebsd.plugin/freebsd_devstat.c
+++ b/collectors/freebsd.plugin/freebsd_devstat.c
@@ -116,7 +116,7 @@ static void disks_cleanup() {
struct disk *dm = disks_root, *last = NULL;
while(dm) {
if (unlikely(!dm->updated)) {
- // info("Removing disk '%s', linked after '%s'", dm->name, last?last->name:"ROOT");
+ // collector_info("Removing disk '%s', linked after '%s'", dm->name, last?last->name:"ROOT");
if (disks_last_used == dm)
disks_last_used = last;
@@ -728,28 +728,28 @@ int do_kern_devstat(int update_every, usec_t dt) {
if (unlikely(common_error)) {
do_system_io = 0;
- error("DISABLED: system.io chart");
+ collector_error("DISABLED: system.io chart");
do_io = 0;
- error("DISABLED: disk.* charts");
+ collector_error("DISABLED: disk.* charts");
do_ops = 0;
- error("DISABLED: disk_ops.* charts");
+ collector_error("DISABLED: disk_ops.* charts");
do_qops = 0;
- error("DISABLED: disk_qops.* charts");
+ collector_error("DISABLED: disk_qops.* charts");
do_util = 0;
- error("DISABLED: disk_util.* charts");
+ collector_error("DISABLED: disk_util.* charts");
do_iotime = 0;
- error("DISABLED: disk_iotime.* charts");
+ collector_error("DISABLED: disk_iotime.* charts");
do_await = 0;
- error("DISABLED: disk_await.* charts");
+ collector_error("DISABLED: disk_await.* charts");
do_avagsz = 0;
- error("DISABLED: disk_avgsz.* charts");
+ collector_error("DISABLED: disk_avgsz.* charts");
do_svctm = 0;
- error("DISABLED: disk_svctm.* charts");
- error("DISABLED: kern.devstat module");
+ collector_error("DISABLED: disk_svctm.* charts");
+ collector_error("DISABLED: kern.devstat module");
return 1;
}
} else {
- error("DISABLED: kern.devstat module");
+ collector_error("DISABLED: kern.devstat module");
return 1;
}
diff --git a/collectors/freebsd.plugin/freebsd_getifaddrs.c b/collectors/freebsd.plugin/freebsd_getifaddrs.c
index 1e870c0d..f1e67088 100644
--- a/collectors/freebsd.plugin/freebsd_getifaddrs.c
+++ b/collectors/freebsd.plugin/freebsd_getifaddrs.c
@@ -73,7 +73,7 @@ static void network_interfaces_cleanup() {
struct cgroup_network_interface *ifm = network_interfaces_root, *last = NULL;
while(ifm) {
if (unlikely(!ifm->updated)) {
- // info("Removing network interface '%s', linked after '%s'", ifm->name, last?last->name:"ROOT");
+ // collector_info("Removing network interface '%s', linked after '%s'", ifm->name, last?last->name:"ROOT");
if (network_interfaces_last_used == ifm)
network_interfaces_last_used = last;
@@ -193,26 +193,26 @@ int do_getifaddrs(int update_every, usec_t dt) {
struct ifaddrs *ifap;
if (unlikely(getifaddrs(&ifap))) {
- error("FREEBSD: getifaddrs() failed");
+ collector_error("FREEBSD: getifaddrs() failed");
do_bandwidth_net = 0;
- error("DISABLED: system.net chart");
+ collector_error("DISABLED: system.net chart");
do_packets_net = 0;
- error("DISABLED: system.packets chart");
+ collector_error("DISABLED: system.packets chart");
do_bandwidth_ipv4 = 0;
- error("DISABLED: system.ipv4 chart");
+ collector_error("DISABLED: system.ipv4 chart");
do_bandwidth_ipv6 = 0;
- error("DISABLED: system.ipv6 chart");
+ collector_error("DISABLED: system.ipv6 chart");
do_bandwidth = 0;
- error("DISABLED: net.* charts");
+ collector_error("DISABLED: net.* charts");
do_packets = 0;
- error("DISABLED: net_packets.* charts");
+ collector_error("DISABLED: net_packets.* charts");
do_errors = 0;
- error("DISABLED: net_errors.* charts");
+ collector_error("DISABLED: net_errors.* charts");
do_drops = 0;
- error("DISABLED: net_drops.* charts");
+ collector_error("DISABLED: net_drops.* charts");
do_events = 0;
- error("DISABLED: net_events.* charts");
- error("DISABLED: getifaddrs module");
+ collector_error("DISABLED: net_events.* charts");
+ collector_error("DISABLED: getifaddrs module");
return 1;
} else {
#define IFA_DATA(s) (((struct if_data *)ifa->ifa_data)->ifi_ ## s)
@@ -589,7 +589,7 @@ int do_getifaddrs(int update_every, usec_t dt) {
freeifaddrs(ifap);
}
} else {
- error("DISABLED: getifaddrs module");
+ collector_error("DISABLED: getifaddrs module");
return 1;
}
diff --git a/collectors/freebsd.plugin/freebsd_getmntinfo.c b/collectors/freebsd.plugin/freebsd_getmntinfo.c
index e8feefc2..d17cddfc 100644
--- a/collectors/freebsd.plugin/freebsd_getmntinfo.c
+++ b/collectors/freebsd.plugin/freebsd_getmntinfo.c
@@ -54,7 +54,7 @@ static void mount_points_cleanup() {
struct mount_point *m = mount_points_root, *last = NULL;
while(m) {
if (unlikely(!m->updated)) {
- // info("Removing mount point '%s', linked after '%s'", m->name, last?last->name:"ROOT");
+ // collector_info("Removing mount point '%s', linked after '%s'", m->name, last?last->name:"ROOT");
if (mount_points_last_used == m)
mount_points_last_used = last;
@@ -163,12 +163,12 @@ int do_getmntinfo(int update_every, usec_t dt) {
// there is no mount info in sysctl MIBs
if (unlikely(!(mntsize = getmntinfo(&mntbuf, MNT_NOWAIT)))) {
- error("FREEBSD: getmntinfo() failed");
+ collector_error("FREEBSD: getmntinfo() failed");
do_space = 0;
- error("DISABLED: disk_space.* charts");
+ collector_error("DISABLED: disk_space.* charts");
do_inodes = 0;
- error("DISABLED: disk_inodes.* charts");
- error("DISABLED: getmntinfo module");
+ collector_error("DISABLED: disk_inodes.* charts");
+ collector_error("DISABLED: getmntinfo module");
return 1;
} else {
int i;
@@ -289,7 +289,7 @@ int do_getmntinfo(int update_every, usec_t dt) {
}
}
} else {
- error("DISABLED: getmntinfo module");
+ collector_error("DISABLED: getmntinfo module");
return 1;
}
diff --git a/collectors/freebsd.plugin/freebsd_ipfw.c b/collectors/freebsd.plugin/freebsd_ipfw.c
index 178eaa36..dcb771ce 100644
--- a/collectors/freebsd.plugin/freebsd_ipfw.c
+++ b/collectors/freebsd.plugin/freebsd_ipfw.c
@@ -6,11 +6,11 @@
#define FREE_MEM_THRESHOLD 10000 // number of unused chunks that trigger memory freeing
-#define COMMON_IPFW_ERROR() error("DISABLED: ipfw.packets chart"); \
- error("DISABLED: ipfw.bytes chart"); \
- error("DISABLED: ipfw.dyn_active chart"); \
- error("DISABLED: ipfw.dyn_expired chart"); \
- error("DISABLED: ipfw.mem chart");
+#define COMMON_IPFW_ERROR() collector_error("DISABLED: ipfw.packets chart"); \
+ collector_error("DISABLED: ipfw.bytes chart"); \
+ collector_error("DISABLED: ipfw.dyn_active chart"); \
+ collector_error("DISABLED: ipfw.dyn_expired chart"); \
+ collector_error("DISABLED: ipfw.mem chart");
// --------------------------------------------------------------------------------------------------------------------
// ipfw
@@ -83,8 +83,8 @@ int do_ipfw(int update_every, usec_t dt) {
if (unlikely(ipfw_socket == -1))
ipfw_socket = socket(AF_INET, SOCK_RAW, IPPROTO_RAW);
if (unlikely(ipfw_socket == -1)) {
- error("FREEBSD: can't get socket for ipfw configuration");
- error("FREEBSD: run netdata as root to get access to ipfw data");
+ collector_error("FREEBSD: can't get socket for ipfw configuration");
+ collector_error("FREEBSD: run netdata as root to get access to ipfw data");
COMMON_IPFW_ERROR();
return 1;
}
@@ -100,7 +100,7 @@ int do_ipfw(int update_every, usec_t dt) {
error = getsockopt(ipfw_socket, IPPROTO_IP, IP_FW3, op3, optlen);
if (error)
if (errno != ENOMEM) {
- error("FREEBSD: ipfw socket reading error");
+ collector_error("FREEBSD: ipfw socket reading error");
COMMON_IPFW_ERROR();
return 1;
}
@@ -113,7 +113,7 @@ int do_ipfw(int update_every, usec_t dt) {
op3->opcode = IP_FW_XGET;
error = getsockopt(ipfw_socket, IPPROTO_IP, IP_FW3, op3, optlen);
if (error) {
- error("FREEBSD: ipfw socket reading error");
+ collector_error("FREEBSD: ipfw socket reading error");
COMMON_IPFW_ERROR();
return 1;
}
@@ -352,7 +352,7 @@ int do_ipfw(int update_every, usec_t dt) {
return 0;
#else
- error("FREEBSD: ipfw charts supported for FreeBSD 11.0 and newer releases only");
+ collector_error("FREEBSD: ipfw charts supported for FreeBSD 11.0 and newer releases only");
COMMON_IPFW_ERROR();
return 1;
#endif
diff --git a/collectors/freebsd.plugin/freebsd_kstat_zfs.c b/collectors/freebsd.plugin/freebsd_kstat_zfs.c
index 046a1e69..165efa17 100644
--- a/collectors/freebsd.plugin/freebsd_kstat_zfs.c
+++ b/collectors/freebsd.plugin/freebsd_kstat_zfs.c
@@ -238,9 +238,9 @@ int do_kstat_zfs_misc_zio_trim(int update_every, usec_t dt) {
GETSYSCTL_SIMPLE("kstat.zfs.misc.zio_trim.success", mib_success, success) ||
GETSYSCTL_SIMPLE("kstat.zfs.misc.zio_trim.failed", mib_failed, failed) ||
GETSYSCTL_SIMPLE("kstat.zfs.misc.zio_trim.unsupported", mib_unsupported, unsupported))) {
- error("DISABLED: zfs.trim_bytes chart");
- error("DISABLED: zfs.trim_success chart");
- error("DISABLED: kstat.zfs.misc.zio_trim module");
+ collector_error("DISABLED: zfs.trim_bytes chart");
+ collector_error("DISABLED: zfs.trim_success chart");
+ collector_error("DISABLED: kstat.zfs.misc.zio_trim module");
return 1;
} else {
diff --git a/collectors/freebsd.plugin/freebsd_sysctl.c b/collectors/freebsd.plugin/freebsd_sysctl.c
index dd94a161..7d68bda9 100644
--- a/collectors/freebsd.plugin/freebsd_sysctl.c
+++ b/collectors/freebsd.plugin/freebsd_sysctl.c
@@ -96,17 +96,17 @@ int freebsd_plugin_init()
{
system_pagesize = getpagesize();
if (system_pagesize <= 0) {
- error("FREEBSD: can't get system page size");
+ collector_error("FREEBSD: can't get system page size");
return 1;
}
if (unlikely(GETSYSCTL_BY_NAME("kern.smp.cpus", number_of_cpus))) {
- error("FREEBSD: can't get number of cpus");
+ collector_error("FREEBSD: can't get number of cpus");
return 1;
}
if (unlikely(!number_of_cpus)) {
- error("FREEBSD: wrong number of cpus");
+ collector_error("FREEBSD: wrong number of cpus");
return 1;
}
@@ -126,8 +126,8 @@ int do_vm_loadavg(int update_every, usec_t dt){
struct loadavg sysload;
if (unlikely(GETSYSCTL_SIMPLE("vm.loadavg", mib, sysload))) {
- error("DISABLED: system.load chart");
- error("DISABLED: vm.loadavg module");
+ collector_error("DISABLED: system.load chart");
+ collector_error("DISABLED: vm.loadavg module");
return 1;
} else {
static RRDSET *st = NULL;
@@ -185,12 +185,12 @@ int do_vm_vmtotal(int update_every, usec_t dt) {
if (unlikely(GETSYSCTL_SIMPLE("vm.vmtotal", mib, vmtotal_data))) {
do_all_processes = 0;
- error("DISABLED: system.active_processes chart");
+ collector_error("DISABLED: system.active_processes chart");
do_processes = 0;
- error("DISABLED: system.processes chart");
+ collector_error("DISABLED: system.processes chart");
do_mem_real = 0;
- error("DISABLED: mem.real chart");
- error("DISABLED: vm.vmtotal module");
+ collector_error("DISABLED: mem.real chart");
+ collector_error("DISABLED: vm.vmtotal module");
return 1;
} else {
if (likely(do_all_processes)) {
@@ -277,7 +277,7 @@ int do_vm_vmtotal(int update_every, usec_t dt) {
}
}
} else {
- error("DISABLED: vm.vmtotal module");
+ collector_error("DISABLED: vm.vmtotal module");
return 1;
}
@@ -290,17 +290,17 @@ int do_kern_cp_time(int update_every, usec_t dt) {
(void)dt;
if (unlikely(CPUSTATES != 5)) {
- error("FREEBSD: There are %d CPU states (5 was expected)", CPUSTATES);
- error("DISABLED: system.cpu chart");
- error("DISABLED: kern.cp_time module");
+ collector_error("FREEBSD: There are %d CPU states (5 was expected)", CPUSTATES);
+ collector_error("DISABLED: system.cpu chart");
+ collector_error("DISABLED: kern.cp_time module");
return 1;
} else {
static int mib[2] = {0, 0};
long cp_time[CPUSTATES];
if (unlikely(GETSYSCTL_SIMPLE("kern.cp_time", mib, cp_time))) {
- error("DISABLED: system.cpu chart");
- error("DISABLED: kern.cp_time module");
+ collector_error("DISABLED: system.cpu chart");
+ collector_error("DISABLED: kern.cp_time module");
return 1;
} else {
static RRDSET *st = NULL;
@@ -348,9 +348,9 @@ int do_kern_cp_times(int update_every, usec_t dt) {
(void)dt;
if (unlikely(CPUSTATES != 5)) {
- error("FREEBSD: There are %d CPU states (5 was expected)", CPUSTATES);
- error("DISABLED: cpu.cpuXX charts");
- error("DISABLED: kern.cp_times module");
+ collector_error("FREEBSD: There are %d CPU states (5 was expected)", CPUSTATES);
+ collector_error("DISABLED: cpu.cpuXX charts");
+ collector_error("DISABLED: kern.cp_times module");
return 1;
} else {
static int mib[2] = {0, 0};
@@ -361,8 +361,8 @@ int do_kern_cp_times(int update_every, usec_t dt) {
if(unlikely(number_of_cpus != old_number_of_cpus))
pcpu_cp_time = reallocz(pcpu_cp_time, sizeof(cp_time) * number_of_cpus);
if (unlikely(GETSYSCTL_WSIZE("kern.cp_times", mib, pcpu_cp_time, sizeof(cp_time) * number_of_cpus))) {
- error("DISABLED: cpu.cpuXX charts");
- error("DISABLED: kern.cp_times module");
+ collector_error("DISABLED: cpu.cpuXX charts");
+ collector_error("DISABLED: kern.cp_times module");
return 1;
} else {
int i;
@@ -449,8 +449,8 @@ int do_dev_cpu_temperature(int update_every, usec_t dt) {
if (unlikely(!(mib[i * 4])))
sprintf(char_mib, "dev.cpu.%d.temperature", i);
if (unlikely(getsysctl_simple(char_mib, &mib[i * 4], 4, &pcpu_temperature[i], sizeof(int)))) {
- error("DISABLED: cpu.temperature chart");
- error("DISABLED: dev.cpu.temperature module");
+ collector_error("DISABLED: cpu.temperature chart");
+ collector_error("DISABLED: dev.cpu.temperature module");
return 1;
}
}
@@ -505,8 +505,8 @@ int do_dev_cpu_0_freq(int update_every, usec_t dt) {
int cpufreq;
if (unlikely(GETSYSCTL_SIMPLE("dev.cpu.0.freq", mib, cpufreq))) {
- error("DISABLED: cpu.scaling_cur_freq chart");
- error("DISABLED: dev.cpu.0.freq module");
+ collector_error("DISABLED: cpu.scaling_cur_freq chart");
+ collector_error("DISABLED: dev.cpu.0.freq module");
return 1;
} else {
static RRDSET *st = NULL;
@@ -547,9 +547,9 @@ int do_hw_intcnt(int update_every, usec_t dt) {
size_t intrcnt_size = 0;
if (unlikely(GETSYSCTL_SIZE("hw.intrcnt", mib_hw_intrcnt, intrcnt_size))) {
- error("DISABLED: system.intr chart");
- error("DISABLED: system.interrupts chart");
- error("DISABLED: hw.intrcnt module");
+ collector_error("DISABLED: system.intr chart");
+ collector_error("DISABLED: system.interrupts chart");
+ collector_error("DISABLED: hw.intrcnt module");
return 1;
} else {
unsigned long nintr = 0;
@@ -560,9 +560,9 @@ int do_hw_intcnt(int update_every, usec_t dt) {
if (unlikely(nintr != old_nintr))
intrcnt = reallocz(intrcnt, nintr * sizeof(u_long));
if (unlikely(GETSYSCTL_WSIZE("hw.intrcnt", mib_hw_intrcnt, intrcnt, nintr * sizeof(u_long)))) {
- error("DISABLED: system.intr chart");
- error("DISABLED: system.interrupts chart");
- error("DISABLED: hw.intrcnt module");
+ collector_error("DISABLED: system.intr chart");
+ collector_error("DISABLED: system.interrupts chart");
+ collector_error("DISABLED: hw.intrcnt module");
return 1;
} else {
unsigned long long totalintr = 0;
@@ -602,17 +602,17 @@ int do_hw_intcnt(int update_every, usec_t dt) {
static char *intrnames = NULL;
if (unlikely(GETSYSCTL_SIZE("hw.intrnames", mib_hw_intrnames, size))) {
- error("DISABLED: system.intr chart");
- error("DISABLED: system.interrupts chart");
- error("DISABLED: hw.intrcnt module");
+ collector_error("DISABLED: system.intr chart");
+ collector_error("DISABLED: system.interrupts chart");
+ collector_error("DISABLED: hw.intrcnt module");
return 1;
} else {
if (unlikely(nintr != old_nintr))
intrnames = reallocz(intrnames, size);
if (unlikely(GETSYSCTL_WSIZE("hw.intrnames", mib_hw_intrnames, intrnames, size))) {
- error("DISABLED: system.intr chart");
- error("DISABLED: system.interrupts chart");
- error("DISABLED: hw.intrcnt module");
+ collector_error("DISABLED: system.intr chart");
+ collector_error("DISABLED: system.interrupts chart");
+ collector_error("DISABLED: hw.intrcnt module");
return 1;
} else {
static RRDSET *st_interrupts = NULL;
@@ -666,8 +666,8 @@ int do_vm_stats_sys_v_intr(int update_every, usec_t dt) {
u_int int_number;
if (unlikely(GETSYSCTL_SIMPLE("vm.stats.sys.v_intr", mib, int_number))) {
- error("DISABLED: system.dev_intr chart");
- error("DISABLED: vm.stats.sys.v_intr module");
+ collector_error("DISABLED: system.dev_intr chart");
+ collector_error("DISABLED: vm.stats.sys.v_intr module");
return 1;
} else {
static RRDSET *st = NULL;
@@ -707,8 +707,8 @@ int do_vm_stats_sys_v_soft(int update_every, usec_t dt) {
u_int soft_intr_number;
if (unlikely(GETSYSCTL_SIMPLE("vm.stats.sys.v_soft", mib, soft_intr_number))) {
- error("DISABLED: system.dev_intr chart");
- error("DISABLED: vm.stats.sys.v_soft module");
+ collector_error("DISABLED: system.dev_intr chart");
+ collector_error("DISABLED: vm.stats.sys.v_soft module");
return 1;
} else {
static RRDSET *st = NULL;
@@ -748,8 +748,8 @@ int do_vm_stats_sys_v_swtch(int update_every, usec_t dt) {
u_int ctxt_number;
if (unlikely(GETSYSCTL_SIMPLE("vm.stats.sys.v_swtch", mib, ctxt_number))) {
- error("DISABLED: system.ctxt chart");
- error("DISABLED: vm.stats.sys.v_swtch module");
+ collector_error("DISABLED: system.ctxt chart");
+ collector_error("DISABLED: vm.stats.sys.v_swtch module");
return 1;
} else {
static RRDSET *st = NULL;
@@ -789,8 +789,8 @@ int do_vm_stats_sys_v_forks(int update_every, usec_t dt) {
u_int forks_number;
if (unlikely(GETSYSCTL_SIMPLE("vm.stats.vm.v_forks", mib, forks_number))) {
- error("DISABLED: system.forks chart");
- error("DISABLED: vm.stats.sys.v_swtch module");
+ collector_error("DISABLED: system.forks chart");
+ collector_error("DISABLED: vm.stats.sys.v_swtch module");
return 1;
} else {
@@ -834,8 +834,8 @@ int do_vm_swap_info(int update_every, usec_t dt) {
static int mib[3] = {0, 0, 0};
if (unlikely(getsysctl_mib("vm.swap_info", mib, 2))) {
- error("DISABLED: system.swap chart");
- error("DISABLED: vm.swap_info module");
+ collector_error("DISABLED: system.swap chart");
+ collector_error("DISABLED: vm.swap_info module");
return 1;
} else {
int i;
@@ -852,15 +852,15 @@ int do_vm_swap_info(int update_every, usec_t dt) {
size = sizeof(xsw);
if (unlikely(sysctl(mib, 3, &xsw, &size, NULL, 0) == -1 )) {
if (unlikely(errno != ENOENT)) {
- error("FREEBSD: sysctl(%s...) failed: %s", "vm.swap_info", strerror(errno));
- error("DISABLED: system.swap chart");
- error("DISABLED: vm.swap_info module");
+ collector_error("FREEBSD: sysctl(%s...) failed: %s", "vm.swap_info", strerror(errno));
+ collector_error("DISABLED: system.swap chart");
+ collector_error("DISABLED: vm.swap_info module");
return 1;
} else {
if (unlikely(size != sizeof(xsw))) {
- error("FREEBSD: sysctl(%s...) expected %lu, got %lu", "vm.swap_info", (unsigned long)sizeof(xsw), (unsigned long)size);
- error("DISABLED: system.swap chart");
- error("DISABLED: vm.swap_info module");
+ collector_error("FREEBSD: sysctl(%s...) expected %lu, got %lu", "vm.swap_info", (unsigned long)sizeof(xsw), (unsigned long)size);
+ collector_error("DISABLED: system.swap chart");
+ collector_error("DISABLED: vm.swap_info module");
return 1;
} else break;
}
@@ -932,8 +932,8 @@ int do_system_ram(int update_every, usec_t dt) {
#endif
GETSYSCTL_SIMPLE("vfs.bufspace", mib_vfs_bufspace, vfs_bufspace_count) ||
GETSYSCTL_SIMPLE("vm.stats.vm.v_free_count", mib_free_count, vmmeter_data.v_free_count))) {
- error("DISABLED: system.ram chart");
- error("DISABLED: system.ram module");
+ collector_error("DISABLED: system.ram chart");
+ collector_error("DISABLED: system.ram module");
return 1;
} else {
static RRDSET *st = NULL, *st_mem_available = NULL;
@@ -1026,8 +1026,8 @@ int do_vm_stats_sys_v_swappgs(int update_every, usec_t dt) {
if (unlikely(GETSYSCTL_SIMPLE("vm.stats.vm.v_swappgsin", mib_swappgsin, vmmeter_data.v_swappgsin) ||
GETSYSCTL_SIMPLE("vm.stats.vm.v_swappgsout", mib_swappgsout, vmmeter_data.v_swappgsout))) {
- error("DISABLED: system.swapio chart");
- error("DISABLED: vm.stats.vm.v_swappgs module");
+ collector_error("DISABLED: system.swapio chart");
+ collector_error("DISABLED: vm.stats.vm.v_swappgs module");
return 1;
} else {
static RRDSET *st = NULL;
@@ -1074,8 +1074,8 @@ int do_vm_stats_sys_v_pgfaults(int update_every, usec_t dt) {
GETSYSCTL_SIMPLE("vm.stats.vm.v_cow_faults", mib_cow_faults, vmmeter_data.v_cow_faults) ||
GETSYSCTL_SIMPLE("vm.stats.vm.v_cow_optim", mib_cow_optim, vmmeter_data.v_cow_optim) ||
GETSYSCTL_SIMPLE("vm.stats.vm.v_intrans", mib_intrans, vmmeter_data.v_intrans))) {
- error("DISABLED: mem.pgfaults chart");
- error("DISABLED: vm.stats.vm.v_pgfaults module");
+ collector_error("DISABLED: mem.pgfaults chart");
+ collector_error("DISABLED: vm.stats.vm.v_pgfaults module");
return 1;
} else {
static RRDSET *st = NULL;
@@ -1131,9 +1131,9 @@ int do_kern_ipc_sem(int update_every, usec_t dt) {
} ipc_sem = {0, 0, 0};
if (unlikely(GETSYSCTL_SIMPLE("kern.ipc.semmni", mib_semmni, ipc_sem.semmni))) {
- error("DISABLED: system.ipc_semaphores chart");
- error("DISABLED: system.ipc_semaphore_arrays chart");
- error("DISABLED: kern.ipc.sem module");
+ collector_error("DISABLED: system.ipc_semaphores chart");
+ collector_error("DISABLED: system.ipc_semaphore_arrays chart");
+ collector_error("DISABLED: kern.ipc.sem module");
return 1;
} else {
static struct semid_kernel *ipc_sem_data = NULL;
@@ -1145,9 +1145,9 @@ int do_kern_ipc_sem(int update_every, usec_t dt) {
old_semmni = ipc_sem.semmni;
}
if (unlikely(GETSYSCTL_WSIZE("kern.ipc.sema", mib_sema, ipc_sem_data, sizeof(struct semid_kernel) * ipc_sem.semmni))) {
- error("DISABLED: system.ipc_semaphores chart");
- error("DISABLED: system.ipc_semaphore_arrays chart");
- error("DISABLED: kern.ipc.sem module");
+ collector_error("DISABLED: system.ipc_semaphores chart");
+ collector_error("DISABLED: system.ipc_semaphore_arrays chart");
+ collector_error("DISABLED: kern.ipc.sem module");
return 1;
} else {
int i;
@@ -1223,9 +1223,9 @@ int do_kern_ipc_shm(int update_every, usec_t dt) {
} ipc_shm = {0, 0, 0};
if (unlikely(GETSYSCTL_SIMPLE("kern.ipc.shmmni", mib_shmmni, ipc_shm.shmmni))) {
- error("DISABLED: system.ipc_shared_mem_segs chart");
- error("DISABLED: system.ipc_shared_mem_size chart");
- error("DISABLED: kern.ipc.shmmodule");
+ collector_error("DISABLED: system.ipc_shared_mem_segs chart");
+ collector_error("DISABLED: system.ipc_shared_mem_size chart");
+ collector_error("DISABLED: kern.ipc.shmmodule");
return 1;
} else {
static struct shmid_kernel *ipc_shm_data = NULL;
@@ -1238,9 +1238,9 @@ int do_kern_ipc_shm(int update_every, usec_t dt) {
}
if (unlikely(
GETSYSCTL_WSIZE("kern.ipc.shmsegs", mib_shmsegs, ipc_shm_data, sizeof(struct shmid_kernel) * ipc_shm.shmmni))) {
- error("DISABLED: system.ipc_shared_mem_segs chart");
- error("DISABLED: system.ipc_shared_mem_size chart");
- error("DISABLED: kern.ipc.shmmodule");
+ collector_error("DISABLED: system.ipc_shared_mem_segs chart");
+ collector_error("DISABLED: system.ipc_shared_mem_size chart");
+ collector_error("DISABLED: kern.ipc.shmmodule");
return 1;
} else {
unsigned long i;
@@ -1318,10 +1318,10 @@ int do_kern_ipc_msq(int update_every, usec_t dt) {
} ipc_msq = {0, 0, 0, 0, 0};
if (unlikely(GETSYSCTL_SIMPLE("kern.ipc.msgmni", mib_msgmni, ipc_msq.msgmni))) {
- error("DISABLED: system.ipc_msq_queues chart");
- error("DISABLED: system.ipc_msq_messages chart");
- error("DISABLED: system.ipc_msq_size chart");
- error("DISABLED: kern.ipc.msg module");
+ collector_error("DISABLED: system.ipc_msq_queues chart");
+ collector_error("DISABLED: system.ipc_msq_messages chart");
+ collector_error("DISABLED: system.ipc_msq_size chart");
+ collector_error("DISABLED: kern.ipc.msg module");
return 1;
} else {
static struct msqid_kernel *ipc_msq_data = NULL;
@@ -1334,10 +1334,10 @@ int do_kern_ipc_msq(int update_every, usec_t dt) {
}
if (unlikely(
GETSYSCTL_WSIZE("kern.ipc.msqids", mib_msqids, ipc_msq_data, sizeof(struct msqid_kernel) * ipc_msq.msgmni))) {
- error("DISABLED: system.ipc_msq_queues chart");
- error("DISABLED: system.ipc_msq_messages chart");
- error("DISABLED: system.ipc_msq_size chart");
- error("DISABLED: kern.ipc.msg module");
+ collector_error("DISABLED: system.ipc_msq_queues chart");
+ collector_error("DISABLED: system.ipc_msq_messages chart");
+ collector_error("DISABLED: system.ipc_msq_size chart");
+ collector_error("DISABLED: kern.ipc.msg module");
return 1;
} else {
int i;
@@ -1520,11 +1520,11 @@ int do_net_isr(int update_every, usec_t dt) {
}
if (unlikely(common_error)) {
do_netisr = 0;
- error("DISABLED: system.softnet_stat chart");
+ collector_error("DISABLED: system.softnet_stat chart");
do_netisr_per_core = 0;
- error("DISABLED: system.cpuX_softnet_stat chart");
+ collector_error("DISABLED: system.cpuX_softnet_stat chart");
common_error = 0;
- error("DISABLED: net.isr module");
+ collector_error("DISABLED: net.isr module");
return 1;
} else {
unsigned long i, n;
@@ -1554,7 +1554,7 @@ int do_net_isr(int update_every, usec_t dt) {
}
}
} else {
- error("DISABLED: net.isr module");
+ collector_error("DISABLED: net.isr module");
return 1;
}
@@ -1662,8 +1662,8 @@ int do_net_inet_tcp_states(int update_every, usec_t dt) {
// see http://net-snmp.sourceforge.net/docs/mibs/tcp.html
if (unlikely(GETSYSCTL_SIMPLE("net.inet.tcp.states", mib, tcps_states))) {
- error("DISABLED: ipv4.tcpsock chart");
- error("DISABLED: net.inet.tcp.states module");
+ collector_error("DISABLED: ipv4.tcpsock chart");
+ collector_error("DISABLED: net.inet.tcp.states module");
return 1;
} else {
static RRDSET *st = NULL;
@@ -1726,22 +1726,22 @@ int do_net_inet_tcp_stats(int update_every, usec_t dt) {
if (unlikely(GETSYSCTL_SIMPLE("net.inet.tcp.stats", mib, tcpstat))) {
do_tcp_packets = 0;
- error("DISABLED: ipv4.tcppackets chart");
+ collector_error("DISABLED: ipv4.tcppackets chart");
do_tcp_errors = 0;
- error("DISABLED: ipv4.tcperrors chart");
+ collector_error("DISABLED: ipv4.tcperrors chart");
do_tcp_handshake = 0;
- error("DISABLED: ipv4.tcphandshake chart");
+ collector_error("DISABLED: ipv4.tcphandshake chart");
do_tcpext_connaborts = 0;
- error("DISABLED: ipv4.tcpconnaborts chart");
+ collector_error("DISABLED: ipv4.tcpconnaborts chart");
do_tcpext_ofo = 0;
- error("DISABLED: ipv4.tcpofo chart");
+ collector_error("DISABLED: ipv4.tcpofo chart");
do_tcpext_syncookies = 0;
- error("DISABLED: ipv4.tcpsyncookies chart");
+ collector_error("DISABLED: ipv4.tcpsyncookies chart");
do_tcpext_listen = 0;
- error("DISABLED: ipv4.tcplistenissues chart");
+ collector_error("DISABLED: ipv4.tcplistenissues chart");
do_ecn = 0;
- error("DISABLED: ipv4.ecnpkts chart");
- error("DISABLED: net.inet.tcp.stats module");
+ collector_error("DISABLED: ipv4.ecnpkts chart");
+ collector_error("DISABLED: net.inet.tcp.stats module");
return 1;
} else {
if (likely(do_tcp_packets)) {
@@ -2035,7 +2035,7 @@ int do_net_inet_tcp_stats(int update_every, usec_t dt) {
}
} else {
- error("DISABLED: net.inet.tcp.stats module");
+ collector_error("DISABLED: net.inet.tcp.stats module");
return 1;
}
@@ -2060,10 +2060,10 @@ int do_net_inet_udp_stats(int update_every, usec_t dt) {
if (unlikely(GETSYSCTL_SIMPLE("net.inet.udp.stats", mib, udpstat))) {
do_udp_packets = 0;
- error("DISABLED: ipv4.udppackets chart");
+ collector_error("DISABLED: ipv4.udppackets chart");
do_udp_errors = 0;
- error("DISABLED: ipv4.udperrors chart");
- error("DISABLED: net.inet.udp.stats module");
+ collector_error("DISABLED: ipv4.udperrors chart");
+ collector_error("DISABLED: net.inet.udp.stats module");
return 1;
} else {
if (likely(do_udp_packets)) {
@@ -2134,7 +2134,7 @@ int do_net_inet_udp_stats(int update_every, usec_t dt) {
}
}
} else {
- error("DISABLED: net.inet.udp.stats module");
+ collector_error("DISABLED: net.inet.udp.stats module");
return 1;
}
@@ -2163,12 +2163,12 @@ int do_net_inet_icmp_stats(int update_every, usec_t dt) {
if (unlikely(GETSYSCTL_SIMPLE("net.inet.icmp.stats", mib, icmpstat))) {
do_icmp_packets = 0;
- error("DISABLED: ipv4.icmp chart");
+ collector_error("DISABLED: ipv4.icmp chart");
do_icmp_errors = 0;
- error("DISABLED: ipv4.icmp_errors chart");
+ collector_error("DISABLED: ipv4.icmp_errors chart");
do_icmpmsg = 0;
- error("DISABLED: ipv4.icmpmsg chart");
- error("DISABLED: net.inet.icmp.stats module");
+ collector_error("DISABLED: ipv4.icmpmsg chart");
+ collector_error("DISABLED: net.inet.icmp.stats module");
return 1;
} else {
int i;
@@ -2275,7 +2275,7 @@ int do_net_inet_icmp_stats(int update_every, usec_t dt) {
}
}
} else {
- error("DISABLED: net.inet.icmp.stats module");
+ collector_error("DISABLED: net.inet.icmp.stats module");
return 1;
}
@@ -2302,14 +2302,14 @@ int do_net_inet_ip_stats(int update_every, usec_t dt) {
if (unlikely(GETSYSCTL_SIMPLE("net.inet.ip.stats", mib, ipstat))) {
do_ip_packets = 0;
- error("DISABLED: ipv4.packets chart");
+ collector_error("DISABLED: ipv4.packets chart");
do_ip_fragsout = 0;
- error("DISABLED: ipv4.fragsout chart");
+ collector_error("DISABLED: ipv4.fragsout chart");
do_ip_fragsin = 0;
- error("DISABLED: ipv4.fragsin chart");
+ collector_error("DISABLED: ipv4.fragsin chart");
do_ip_errors = 0;
- error("DISABLED: ipv4.errors chart");
- error("DISABLED: net.inet.ip.stats module");
+ collector_error("DISABLED: ipv4.errors chart");
+ collector_error("DISABLED: net.inet.ip.stats module");
return 1;
} else {
if (likely(do_ip_packets)) {
@@ -2456,7 +2456,7 @@ int do_net_inet_ip_stats(int update_every, usec_t dt) {
}
}
} else {
- error("DISABLED: net.inet.ip.stats module");
+ collector_error("DISABLED: net.inet.ip.stats module");
return 1;
}
@@ -2486,14 +2486,14 @@ int do_net_inet6_ip6_stats(int update_every, usec_t dt) {
if (unlikely(GETSYSCTL_SIMPLE("net.inet6.ip6.stats", mib, ip6stat))) {
do_ip6_packets = 0;
- error("DISABLED: ipv6.packets chart");
+ collector_error("DISABLED: ipv6.packets chart");
do_ip6_fragsout = 0;
- error("DISABLED: ipv6.fragsout chart");
+ collector_error("DISABLED: ipv6.fragsout chart");
do_ip6_fragsin = 0;
- error("DISABLED: ipv6.fragsin chart");
+ collector_error("DISABLED: ipv6.fragsin chart");
do_ip6_errors = 0;
- error("DISABLED: ipv6.errors chart");
- error("DISABLED: net.inet6.ip6.stats module");
+ collector_error("DISABLED: ipv6.errors chart");
+ collector_error("DISABLED: net.inet6.ip6.stats module");
return 1;
} else {
if (do_ip6_packets == CONFIG_BOOLEAN_YES || (do_ip6_packets == CONFIG_BOOLEAN_AUTO &&
@@ -2674,7 +2674,7 @@ int do_net_inet6_ip6_stats(int update_every, usec_t dt) {
}
}
} else {
- error("DISABLED: net.inet6.ip6.stats module");
+ collector_error("DISABLED: net.inet6.ip6.stats module");
return 1;
}
@@ -2711,20 +2711,20 @@ int do_net_inet6_icmp6_stats(int update_every, usec_t dt) {
if (unlikely(GETSYSCTL_SIMPLE("net.inet6.icmp6.stats", mib, icmp6stat))) {
do_icmp6 = 0;
- error("DISABLED: ipv6.icmp chart");
+ collector_error("DISABLED: ipv6.icmp chart");
do_icmp6_redir = 0;
- error("DISABLED: ipv6.icmpredir chart");
+ collector_error("DISABLED: ipv6.icmpredir chart");
do_icmp6_errors = 0;
- error("DISABLED: ipv6.icmperrors chart");
+ collector_error("DISABLED: ipv6.icmperrors chart");
do_icmp6_echos = 0;
- error("DISABLED: ipv6.icmpechos chart");
+ collector_error("DISABLED: ipv6.icmpechos chart");
do_icmp6_router = 0;
- error("DISABLED: ipv6.icmprouter chart");
+ collector_error("DISABLED: ipv6.icmprouter chart");
do_icmp6_neighbor = 0;
- error("DISABLED: ipv6.icmpneighbor chart");
+ collector_error("DISABLED: ipv6.icmpneighbor chart");
do_icmp6_types = 0;
- error("DISABLED: ipv6.icmptypes chart");
- error("DISABLED: net.inet6.icmp6.stats module");
+ collector_error("DISABLED: ipv6.icmptypes chart");
+ collector_error("DISABLED: net.inet6.icmp6.stats module");
return 1;
} else {
int i;
@@ -3054,7 +3054,7 @@ int do_net_inet6_icmp6_stats(int update_every, usec_t dt) {
}
}
} else {
- error("DISABLED: net.inet6.icmp6.stats module");
+ collector_error("DISABLED: net.inet6.icmp6.stats module");
return 1;
}
diff --git a/collectors/freebsd.plugin/plugin_freebsd.c b/collectors/freebsd.plugin/plugin_freebsd.c
index a52ece3f..e47b224c 100644
--- a/collectors/freebsd.plugin/plugin_freebsd.c
+++ b/collectors/freebsd.plugin/plugin_freebsd.c
@@ -78,7 +78,7 @@ static void freebsd_main_cleanup(void *ptr)
struct netdata_static_thread *static_thread = (struct netdata_static_thread *)ptr;
static_thread->enabled = NETDATA_MAIN_THREAD_EXITING;
- info("cleaning up...");
+ collector_info("cleaning up...");
static_thread->enabled = NETDATA_MAIN_THREAD_EXITED;
}
diff --git a/collectors/freeipmi.plugin/README.md b/collectors/freeipmi.plugin/README.md
index ff13717d..e33a9d3b 100644
--- a/collectors/freeipmi.plugin/README.md
+++ b/collectors/freeipmi.plugin/README.md
@@ -1,6 +1,10 @@
<!--
title: "freeipmi.plugin"
-custom_edit_url: https://github.com/netdata/netdata/edit/master/collectors/freeipmi.plugin/README.md
+custom_edit_url: "https://github.com/netdata/netdata/edit/master/collectors/freeipmi.plugin/README.md"
+sidebar_label: "freeipmi.plugin"
+learn_status: "Published"
+learn_topic_type: "References"
+learn_rel_path: "References/Collectors references/Devices"
-->
# freeipmi.plugin
diff --git a/collectors/freeipmi.plugin/freeipmi_plugin.c b/collectors/freeipmi.plugin/freeipmi_plugin.c
index 351b6e32..bcc5139f 100644
--- a/collectors/freeipmi.plugin/freeipmi_plugin.c
+++ b/collectors/freeipmi.plugin/freeipmi_plugin.c
@@ -775,7 +775,7 @@ _ipmimonitoring_sensors (struct ipmi_monitoring_ipmi_config *ipmi_config)
int rv = -1;
if (!(ctx = ipmi_monitoring_ctx_create ())) {
- error("ipmi_monitoring_ctx_create()");
+ collector_error("ipmi_monitoring_ctx_create()");
goto cleanup;
}
@@ -784,8 +784,8 @@ _ipmimonitoring_sensors (struct ipmi_monitoring_ipmi_config *ipmi_config)
if (ipmi_monitoring_ctx_sdr_cache_directory (ctx,
sdr_cache_directory) < 0)
{
- error("ipmi_monitoring_ctx_sdr_cache_directory(): %s\n",
- ipmi_monitoring_ctx_errormsg (ctx));
+ collector_error("ipmi_monitoring_ctx_sdr_cache_directory(): %s\n",
+ ipmi_monitoring_ctx_errormsg (ctx));
goto cleanup;
}
}
@@ -796,8 +796,8 @@ _ipmimonitoring_sensors (struct ipmi_monitoring_ipmi_config *ipmi_config)
if (ipmi_monitoring_ctx_sensor_config_file (ctx,
sensor_config_file) < 0)
{
- error( "ipmi_monitoring_ctx_sensor_config_file(): %s\n",
- ipmi_monitoring_ctx_errormsg (ctx));
+ collector_error( "ipmi_monitoring_ctx_sensor_config_file(): %s\n",
+ ipmi_monitoring_ctx_errormsg (ctx));
goto cleanup;
}
}
@@ -805,8 +805,8 @@ _ipmimonitoring_sensors (struct ipmi_monitoring_ipmi_config *ipmi_config)
{
if (ipmi_monitoring_ctx_sensor_config_file (ctx, NULL) < 0)
{
- error( "ipmi_monitoring_ctx_sensor_config_file(): %s\n",
- ipmi_monitoring_ctx_errormsg (ctx));
+ collector_error( "ipmi_monitoring_ctx_sensor_config_file(): %s\n",
+ ipmi_monitoring_ctx_errormsg (ctx));
goto cleanup;
}
}
@@ -851,8 +851,8 @@ _ipmimonitoring_sensors (struct ipmi_monitoring_ipmi_config *ipmi_config)
NULL,
NULL)) < 0)
{
- error( "ipmi_monitoring_sensor_readings_by_record_id(): %s",
- ipmi_monitoring_ctx_errormsg (ctx));
+ collector_error( "ipmi_monitoring_sensor_readings_by_record_id(): %s",
+ ipmi_monitoring_ctx_errormsg (ctx));
goto cleanup;
}
}
@@ -867,8 +867,8 @@ _ipmimonitoring_sensors (struct ipmi_monitoring_ipmi_config *ipmi_config)
NULL,
NULL)) < 0)
{
- error( "ipmi_monitoring_sensor_readings_by_record_id(): %s",
- ipmi_monitoring_ctx_errormsg (ctx));
+ collector_error( "ipmi_monitoring_sensor_readings_by_record_id(): %s",
+ ipmi_monitoring_ctx_errormsg (ctx));
goto cleanup;
}
}
@@ -883,8 +883,8 @@ _ipmimonitoring_sensors (struct ipmi_monitoring_ipmi_config *ipmi_config)
NULL,
NULL)) < 0)
{
- error( "ipmi_monitoring_sensor_readings_by_sensor_type(): %s",
- ipmi_monitoring_ctx_errormsg (ctx));
+ collector_error( "ipmi_monitoring_sensor_readings_by_sensor_type(): %s",
+ ipmi_monitoring_ctx_errormsg (ctx));
goto cleanup;
}
}
@@ -920,58 +920,57 @@ _ipmimonitoring_sensors (struct ipmi_monitoring_ipmi_config *ipmi_config)
if ((record_id = ipmi_monitoring_sensor_read_record_id (ctx)) < 0)
{
- error( "ipmi_monitoring_sensor_read_record_id(): %s",
- ipmi_monitoring_ctx_errormsg (ctx));
+ collector_error( "ipmi_monitoring_sensor_read_record_id(): %s",
+ ipmi_monitoring_ctx_errormsg (ctx));
goto cleanup;
}
if ((sensor_number = ipmi_monitoring_sensor_read_sensor_number (ctx)) < 0)
{
- error( "ipmi_monitoring_sensor_read_sensor_number(): %s",
- ipmi_monitoring_ctx_errormsg (ctx));
+ collector_error( "ipmi_monitoring_sensor_read_sensor_number(): %s",
+ ipmi_monitoring_ctx_errormsg (ctx));
goto cleanup;
}
if ((sensor_type = ipmi_monitoring_sensor_read_sensor_type (ctx)) < 0)
{
- error( "ipmi_monitoring_sensor_read_sensor_type(): %s",
- ipmi_monitoring_ctx_errormsg (ctx));
+ collector_error( "ipmi_monitoring_sensor_read_sensor_type(): %s",
+ ipmi_monitoring_ctx_errormsg (ctx));
goto cleanup;
}
if (!(sensor_name = ipmi_monitoring_sensor_read_sensor_name (ctx)))
{
- error( "ipmi_monitoring_sensor_read_sensor_name(): %s",
- ipmi_monitoring_ctx_errormsg (ctx));
+ collector_error( "ipmi_monitoring_sensor_read_sensor_name(): %s",
+ ipmi_monitoring_ctx_errormsg (ctx));
goto cleanup;
}
if ((sensor_state = ipmi_monitoring_sensor_read_sensor_state (ctx)) < 0)
{
- error( "ipmi_monitoring_sensor_read_sensor_state(): %s",
- ipmi_monitoring_ctx_errormsg (ctx));
+ collector_error( "ipmi_monitoring_sensor_read_sensor_state(): %s",
+ ipmi_monitoring_ctx_errormsg (ctx));
goto cleanup;
}
if ((sensor_units = ipmi_monitoring_sensor_read_sensor_units (ctx)) < 0)
{
- error( "ipmi_monitoring_sensor_read_sensor_units(): %s",
- ipmi_monitoring_ctx_errormsg (ctx));
+ collector_error( "ipmi_monitoring_sensor_read_sensor_units(): %s",
+ ipmi_monitoring_ctx_errormsg (ctx));
goto cleanup;
}
#ifdef NETDATA_COMMENTED
if ((sensor_bitmask_type = ipmi_monitoring_sensor_read_sensor_bitmask_type (ctx)) < 0)
{
- error( "ipmi_monitoring_sensor_read_sensor_bitmask_type(): %s",
- ipmi_monitoring_ctx_errormsg (ctx));
+ collector_error( "ipmi_monitoring_sensor_read_sensor_bitmask_type(): %s",
+ ipmi_monitoring_ctx_errormsg (ctx));
goto cleanup;
}
if ((sensor_bitmask = ipmi_monitoring_sensor_read_sensor_bitmask (ctx)) < 0)
{
- error(
- "ipmi_monitoring_sensor_read_sensor_bitmask(): %s",
- ipmi_monitoring_ctx_errormsg (ctx));
+ collector_error("ipmi_monitoring_sensor_read_sensor_bitmask(): %s",
+ ipmi_monitoring_ctx_errormsg (ctx));
goto cleanup;
}
@@ -986,8 +985,8 @@ _ipmimonitoring_sensors (struct ipmi_monitoring_ipmi_config *ipmi_config)
if ((sensor_reading_type = ipmi_monitoring_sensor_read_sensor_reading_type (ctx)) < 0)
{
- error( "ipmi_monitoring_sensor_read_sensor_reading_type(): %s",
- ipmi_monitoring_ctx_errormsg (ctx));
+ collector_error( "ipmi_monitoring_sensor_read_sensor_reading_type(): %s",
+ ipmi_monitoring_ctx_errormsg (ctx));
goto cleanup;
}
@@ -996,8 +995,8 @@ _ipmimonitoring_sensors (struct ipmi_monitoring_ipmi_config *ipmi_config)
#ifdef NETDATA_COMMENTED
if ((event_reading_type_code = ipmi_monitoring_sensor_read_event_reading_type_code (ctx)) < 0)
{
- error( "ipmi_monitoring_sensor_read_event_reading_type_code(): %s",
- ipmi_monitoring_ctx_errormsg (ctx));
+ collector_error( "ipmi_monitoring_sensor_read_event_reading_type_code(): %s",
+ ipmi_monitoring_ctx_errormsg (ctx));
goto cleanup;
}
#endif // NETDATA_COMMENTED
@@ -1131,7 +1130,7 @@ _ipmimonitoring_sel (struct ipmi_monitoring_ipmi_config *ipmi_config)
if (!(ctx = ipmi_monitoring_ctx_create ()))
{
- error("ipmi_monitoring_ctx_create()");
+ collector_error("ipmi_monitoring_ctx_create()");
goto cleanup;
}
@@ -1140,8 +1139,8 @@ _ipmimonitoring_sel (struct ipmi_monitoring_ipmi_config *ipmi_config)
if (ipmi_monitoring_ctx_sdr_cache_directory (ctx,
sdr_cache_directory) < 0)
{
- error( "ipmi_monitoring_ctx_sdr_cache_directory(): %s",
- ipmi_monitoring_ctx_errormsg (ctx));
+ collector_error( "ipmi_monitoring_ctx_sdr_cache_directory(): %s",
+ ipmi_monitoring_ctx_errormsg (ctx));
goto cleanup;
}
}
@@ -1152,8 +1151,8 @@ _ipmimonitoring_sel (struct ipmi_monitoring_ipmi_config *ipmi_config)
if (ipmi_monitoring_ctx_sel_config_file (ctx,
sel_config_file) < 0)
{
- error( "ipmi_monitoring_ctx_sel_config_file(): %s",
- ipmi_monitoring_ctx_errormsg (ctx));
+ collector_error( "ipmi_monitoring_ctx_sel_config_file(): %s",
+ ipmi_monitoring_ctx_errormsg (ctx));
goto cleanup;
}
}
@@ -1161,8 +1160,8 @@ _ipmimonitoring_sel (struct ipmi_monitoring_ipmi_config *ipmi_config)
{
if (ipmi_monitoring_ctx_sel_config_file (ctx, NULL) < 0)
{
- error( "ipmi_monitoring_ctx_sel_config_file(): %s",
- ipmi_monitoring_ctx_errormsg (ctx));
+ collector_error( "ipmi_monitoring_ctx_sel_config_file(): %s",
+ ipmi_monitoring_ctx_errormsg (ctx));
goto cleanup;
}
}
@@ -1192,8 +1191,8 @@ _ipmimonitoring_sel (struct ipmi_monitoring_ipmi_config *ipmi_config)
NULL,
NULL)) < 0)
{
- error( "ipmi_monitoring_sel_by_record_id(): %s",
- ipmi_monitoring_ctx_errormsg (ctx));
+ collector_error( "ipmi_monitoring_sel_by_record_id(): %s",
+ ipmi_monitoring_ctx_errormsg (ctx));
goto cleanup;
}
}
@@ -1208,8 +1207,8 @@ _ipmimonitoring_sel (struct ipmi_monitoring_ipmi_config *ipmi_config)
NULL,
NULL)) < 0)
{
- error( "ipmi_monitoring_sel_by_sensor_type(): %s",
- ipmi_monitoring_ctx_errormsg (ctx));
+ collector_error( "ipmi_monitoring_sel_by_sensor_type(): %s",
+ ipmi_monitoring_ctx_errormsg (ctx));
goto cleanup;
}
}
@@ -1225,8 +1224,8 @@ _ipmimonitoring_sel (struct ipmi_monitoring_ipmi_config *ipmi_config)
NULL,
NULL)) < 0)
{
- error( "ipmi_monitoring_sel_by_sensor_type(): %s",
- ipmi_monitoring_ctx_errormsg (ctx));
+ collector_error( "ipmi_monitoring_sel_by_sensor_type(): %s",
+ ipmi_monitoring_ctx_errormsg (ctx));
goto cleanup;
}
}
@@ -1241,8 +1240,8 @@ _ipmimonitoring_sel (struct ipmi_monitoring_ipmi_config *ipmi_config)
NULL,
NULL)) < 0)
{
- error( "ipmi_monitoring_sel_by_record_id(): %s",
- ipmi_monitoring_ctx_errormsg (ctx));
+ collector_error( "ipmi_monitoring_sel_by_record_id(): %s",
+ ipmi_monitoring_ctx_errormsg (ctx));
goto cleanup;
}
}
@@ -1281,29 +1280,29 @@ _ipmimonitoring_sel (struct ipmi_monitoring_ipmi_config *ipmi_config)
if ((record_id = ipmi_monitoring_sel_read_record_id (ctx)) < 0)
{
- error( "ipmi_monitoring_sel_read_record_id(): %s",
- ipmi_monitoring_ctx_errormsg (ctx));
+ collector_error( "ipmi_monitoring_sel_read_record_id(): %s",
+ ipmi_monitoring_ctx_errormsg (ctx));
goto cleanup;
}
if ((record_type = ipmi_monitoring_sel_read_record_type (ctx)) < 0)
{
- error( "ipmi_monitoring_sel_read_record_type(): %s",
- ipmi_monitoring_ctx_errormsg (ctx));
+ collector_error( "ipmi_monitoring_sel_read_record_type(): %s",
+ ipmi_monitoring_ctx_errormsg (ctx));
goto cleanup;
}
if ((record_type_class = ipmi_monitoring_sel_read_record_type_class (ctx)) < 0)
{
- error( "ipmi_monitoring_sel_read_record_type_class(): %s",
- ipmi_monitoring_ctx_errormsg (ctx));
+ collector_error( "ipmi_monitoring_sel_read_record_type_class(): %s",
+ ipmi_monitoring_ctx_errormsg (ctx));
goto cleanup;
}
if ((sel_state = ipmi_monitoring_sel_read_sel_state (ctx)) < 0)
{
- error( "ipmi_monitoring_sel_read_sel_state(): %s",
- ipmi_monitoring_ctx_errormsg (ctx));
+ collector_error( "ipmi_monitoring_sel_read_sel_state(): %s",
+ ipmi_monitoring_ctx_errormsg (ctx));
goto cleanup;
}
@@ -1334,8 +1333,8 @@ _ipmimonitoring_sel (struct ipmi_monitoring_ipmi_config *ipmi_config)
if (ipmi_monitoring_sel_read_timestamp (ctx, &timestamp) < 0)
{
- error( "ipmi_monitoring_sel_read_timestamp(): %s",
- ipmi_monitoring_ctx_errormsg (ctx));
+ collector_error( "ipmi_monitoring_sel_read_timestamp(): %s",
+ ipmi_monitoring_ctx_errormsg (ctx));
goto cleanup;
}
@@ -1363,36 +1362,36 @@ _ipmimonitoring_sel (struct ipmi_monitoring_ipmi_config *ipmi_config)
if (!(sensor_name = ipmi_monitoring_sel_read_sensor_name (ctx)))
{
- error( "ipmi_monitoring_sel_read_sensor_name(): %s",
- ipmi_monitoring_ctx_errormsg (ctx));
+ collector_error( "ipmi_monitoring_sel_read_sensor_name(): %s",
+ ipmi_monitoring_ctx_errormsg (ctx));
goto cleanup;
}
if ((sensor_type = ipmi_monitoring_sel_read_sensor_type (ctx)) < 0)
{
- error( "ipmi_monitoring_sel_read_sensor_type(): %s",
- ipmi_monitoring_ctx_errormsg (ctx));
+ collector_error( "ipmi_monitoring_sel_read_sensor_type(): %s",
+ ipmi_monitoring_ctx_errormsg (ctx));
goto cleanup;
}
if ((sensor_number = ipmi_monitoring_sel_read_sensor_number (ctx)) < 0)
{
- error( "ipmi_monitoring_sel_read_sensor_number(): %s",
- ipmi_monitoring_ctx_errormsg (ctx));
+ collector_error( "ipmi_monitoring_sel_read_sensor_number(): %s",
+ ipmi_monitoring_ctx_errormsg (ctx));
goto cleanup;
}
if ((event_direction = ipmi_monitoring_sel_read_event_direction (ctx)) < 0)
{
- error( "ipmi_monitoring_sel_read_event_direction(): %s",
- ipmi_monitoring_ctx_errormsg (ctx));
+ collector_error( "ipmi_monitoring_sel_read_event_direction(): %s",
+ ipmi_monitoring_ctx_errormsg (ctx));
goto cleanup;
}
if ((event_type_code = ipmi_monitoring_sel_read_event_type_code (ctx)) < 0)
{
- error( "ipmi_monitoring_sel_read_event_type_code(): %s",
- ipmi_monitoring_ctx_errormsg (ctx));
+ collector_error( "ipmi_monitoring_sel_read_event_type_code(): %s",
+ ipmi_monitoring_ctx_errormsg (ctx));
goto cleanup;
}
@@ -1401,29 +1400,29 @@ _ipmimonitoring_sel (struct ipmi_monitoring_ipmi_config *ipmi_config)
&event_data2,
&event_data3) < 0)
{
- error( "ipmi_monitoring_sel_read_event_data(): %s",
- ipmi_monitoring_ctx_errormsg (ctx));
+ collector_error( "ipmi_monitoring_sel_read_event_data(): %s",
+ ipmi_monitoring_ctx_errormsg (ctx));
goto cleanup;
}
if ((event_offset_type = ipmi_monitoring_sel_read_event_offset_type (ctx)) < 0)
{
- error( "ipmi_monitoring_sel_read_event_offset_type(): %s",
- ipmi_monitoring_ctx_errormsg (ctx));
+ collector_error( "ipmi_monitoring_sel_read_event_offset_type(): %s",
+ ipmi_monitoring_ctx_errormsg (ctx));
goto cleanup;
}
if ((event_offset = ipmi_monitoring_sel_read_event_offset (ctx)) < 0)
{
- error( "ipmi_monitoring_sel_read_event_offset(): %s",
- ipmi_monitoring_ctx_errormsg (ctx));
+ collector_error( "ipmi_monitoring_sel_read_event_offset(): %s",
+ ipmi_monitoring_ctx_errormsg (ctx));
goto cleanup;
}
if (!(event_offset_string = ipmi_monitoring_sel_read_event_offset_string (ctx)))
{
- error( "ipmi_monitoring_sel_read_event_offset_string(): %s",
- ipmi_monitoring_ctx_errormsg (ctx));
+ collector_error( "ipmi_monitoring_sel_read_event_offset_string(): %s",
+ ipmi_monitoring_ctx_errormsg (ctx));
goto cleanup;
}
@@ -1464,8 +1463,8 @@ _ipmimonitoring_sel (struct ipmi_monitoring_ipmi_config *ipmi_config)
{
if ((manufacturer_id = ipmi_monitoring_sel_read_manufacturer_id (ctx)) < 0)
{
- error( "ipmi_monitoring_sel_read_manufacturer_id(): %s",
- ipmi_monitoring_ctx_errormsg (ctx));
+ collector_error( "ipmi_monitoring_sel_read_manufacturer_id(): %s",
+ ipmi_monitoring_ctx_errormsg (ctx));
goto cleanup;
}
@@ -1474,8 +1473,8 @@ _ipmimonitoring_sel (struct ipmi_monitoring_ipmi_config *ipmi_config)
if ((oem_data_len = ipmi_monitoring_sel_read_oem_data (ctx, oem_data, 1024)) < 0)
{
- error( "ipmi_monitoring_sel_read_oem_data(): %s",
- ipmi_monitoring_ctx_errormsg (ctx));
+ collector_error( "ipmi_monitoring_sel_read_oem_data(): %s",
+ ipmi_monitoring_ctx_errormsg (ctx));
goto cleanup;
}
@@ -1596,6 +1595,7 @@ int host_is_local(const char *host)
}
int main (int argc, char **argv) {
+ stderror = stderr;
clocks_init();
// ------------------------------------------------------------------------
@@ -1779,7 +1779,7 @@ int main (int argc, char **argv) {
continue;
}
- error("freeipmi.plugin: ignoring parameter '%s'", argv[i]);
+ collector_error("freeipmi.plugin: ignoring parameter '%s'", argv[i]);
}
errno = 0;
@@ -1788,7 +1788,7 @@ int main (int argc, char **argv) {
netdata_update_every = freq;
else if(freq)
- error("update frequency %d seconds is too small for IPMI. Using %d.", freq, netdata_update_every);
+ collector_error("update frequency %d seconds is too small for IPMI. Using %d.", freq, netdata_update_every);
// ------------------------------------------------------------------------
@@ -1813,7 +1813,7 @@ int main (int argc, char **argv) {
if(debug) fprintf(stderr, "freeipmi.plugin: IPMI minimum update frequency was calculated to %d seconds.\n", freq);
if(freq > netdata_update_every) {
- info("enforcing minimum data collection frequency, calculated to %d seconds.", freq);
+ collector_info("enforcing minimum data collection frequency, calculated to %d seconds.", freq);
netdata_update_every = freq;
}
diff --git a/collectors/idlejitter.plugin/README.md b/collectors/idlejitter.plugin/README.md
index 5a92d531..1a3d8025 100644
--- a/collectors/idlejitter.plugin/README.md
+++ b/collectors/idlejitter.plugin/README.md
@@ -1,6 +1,10 @@
<!--
title: "idlejitter.plugin"
-custom_edit_url: https://github.com/netdata/netdata/edit/master/collectors/idlejitter.plugin/README.md
+custom_edit_url: "https://github.com/netdata/netdata/edit/master/collectors/idlejitter.plugin/README.md"
+sidebar_label: "idlejitter.plugin"
+learn_status: "Published"
+learn_topic_type: "References"
+learn_rel_path: "References/Collectors references/QoS"
-->
# idlejitter.plugin
diff --git a/collectors/idlejitter.plugin/plugin_idlejitter.c b/collectors/idlejitter.plugin/plugin_idlejitter.c
index b6339cc0..d9054886 100644
--- a/collectors/idlejitter.plugin/plugin_idlejitter.c
+++ b/collectors/idlejitter.plugin/plugin_idlejitter.c
@@ -10,7 +10,7 @@ static void cpuidlejitter_main_cleanup(void *ptr) {
struct netdata_static_thread *static_thread = (struct netdata_static_thread *)ptr;
static_thread->enabled = NETDATA_MAIN_THREAD_EXITING;
- info("cleaning up...");
+ collector_info("cleaning up...");
static_thread->enabled = NETDATA_MAIN_THREAD_EXITED;
}
@@ -48,7 +48,7 @@ void *cpuidlejitter_main(void *ptr) {
usec_t update_every_ut = localhost->rrd_update_every * USEC_PER_SEC;
struct timeval before, after;
- while (!netdata_exit) {
+ while (service_running(SERVICE_COLLECTORS)) {
int iterations = 0;
usec_t error_total = 0,
error_min = 0,
diff --git a/collectors/ioping.plugin/README.md b/collectors/ioping.plugin/README.md
index c4c3695c..1ab9238f 100644
--- a/collectors/ioping.plugin/README.md
+++ b/collectors/ioping.plugin/README.md
@@ -1,6 +1,10 @@
<!--
-title: "ioping.plugin"
-custom_edit_url: https://github.com/netdata/netdata/edit/master/collectors/ioping.plugin/README.md
+title: "Monitor latency for directories/files/devices (ioping.plugin)"
+custom_edit_url: "https://github.com/netdata/netdata/edit/master/collectors/ioping.plugin/README.md"
+sidebar_label: "Latency monitoring (ioping.plugin)"
+learn_status: "Published"
+learn_topic_type: "References"
+learn_rel_path: "References/Collectors references/QoS"
-->
# ioping.plugin
diff --git a/collectors/macos.plugin/README.md b/collectors/macos.plugin/README.md
index 92bbf1eb..3a3e8a1a 100644
--- a/collectors/macos.plugin/README.md
+++ b/collectors/macos.plugin/README.md
@@ -1,6 +1,10 @@
<!--
title: "macos.plugin"
-custom_edit_url: https://github.com/netdata/netdata/edit/master/collectors/macos.plugin/README.md
+custom_edit_url: "https://github.com/netdata/netdata/edit/master/collectors/macos.plugin/README.md"
+sidebar_label: "macos.plugin"
+learn_status: "Published"
+learn_topic_type: "References"
+learn_rel_path: "References/Collectors references/System metrics"
-->
# macos.plugin
diff --git a/collectors/macos.plugin/macos_fw.c b/collectors/macos.plugin/macos_fw.c
index 07f7d773..ca06f428 100644
--- a/collectors/macos.plugin/macos_fw.c
+++ b/collectors/macos.plugin/macos_fw.c
@@ -84,14 +84,14 @@ int do_macos_iokit(int update_every, usec_t dt) {
/* Get ports and services for drive statistics. */
if (unlikely(IOMainPort(bootstrap_port, &main_port))) {
- error("MACOS: IOMasterPort() failed");
+ collector_error("MACOS: IOMasterPort() failed");
do_io = 0;
- error("DISABLED: system.io");
+ collector_error("DISABLED: system.io");
/* Get the list of all drive objects. */
} else if (unlikely(IOServiceGetMatchingServices(main_port, IOServiceMatching("IOBlockStorageDriver"), &drive_list))) {
- error("MACOS: IOServiceGetMatchingServices() failed");
+ collector_error("MACOS: IOServiceGetMatchingServices() failed");
do_io = 0;
- error("DISABLED: system.io");
+ collector_error("DISABLED: system.io");
} else {
while ((drive = IOIteratorNext(drive_list)) != 0) {
properties = 0;
@@ -126,9 +126,9 @@ int do_macos_iokit(int update_every, usec_t dt) {
/* Obtain the properties for this drive object. */
if (unlikely(IORegistryEntryCreateCFProperties(drive, (CFMutableDictionaryRef *)&properties, kCFAllocatorDefault, 0))) {
IOObjectRelease(drive);
- error("MACOS: IORegistryEntryCreateCFProperties() failed");
+ collector_error("MACOS: IORegistryEntryCreateCFProperties() failed");
do_io = 0;
- error("DISABLED: system.io");
+ collector_error("DISABLED: system.io");
break;
} else if (likely(properties)) {
/* Obtain the statistics from the drive properties. */
@@ -413,11 +413,11 @@ int do_macos_iokit(int update_every, usec_t dt) {
if (likely(do_space || do_inodes)) {
// there is no mount info in sysctl MIBs
if (unlikely(!(mntsize = getmntinfo(&mntbuf, MNT_NOWAIT)))) {
- error("MACOS: getmntinfo() failed");
+ collector_error("MACOS: getmntinfo() failed");
do_space = 0;
- error("DISABLED: disk_space.X");
+ collector_error("DISABLED: disk_space.X");
do_inodes = 0;
- error("DISABLED: disk_inodes.X");
+ collector_error("DISABLED: disk_inodes.X");
} else {
for (i = 0; i < mntsize; i++) {
if (mntbuf[i].f_flags == MNT_RDONLY ||
@@ -500,9 +500,9 @@ int do_macos_iokit(int update_every, usec_t dt) {
if (likely(do_bandwidth)) {
if (unlikely(getifaddrs(&ifap))) {
- error("MACOS: getifaddrs()");
+ collector_error("MACOS: getifaddrs()");
do_bandwidth = 0;
- error("DISABLED: system.ipv4");
+ collector_error("DISABLED: system.ipv4");
} else {
for (ifa = ifap; ifa; ifa = ifa->ifa_next) {
if (ifa->ifa_addr->sa_family != AF_LINK)
diff --git a/collectors/macos.plugin/macos_mach_smi.c b/collectors/macos.plugin/macos_mach_smi.c
index 53b2607b..f21a56af 100644
--- a/collectors/macos.plugin/macos_mach_smi.c
+++ b/collectors/macos.plugin/macos_mach_smi.c
@@ -41,16 +41,16 @@ int do_macos_mach_smi(int update_every, usec_t dt) {
if (likely(do_cpu)) {
if (unlikely(HOST_CPU_LOAD_INFO_COUNT != 4)) {
- error("MACOS: There are %d CPU states (4 was expected)", HOST_CPU_LOAD_INFO_COUNT);
+ collector_error("MACOS: There are %d CPU states (4 was expected)", HOST_CPU_LOAD_INFO_COUNT);
do_cpu = 0;
- error("DISABLED: system.cpu");
+ collector_error("DISABLED: system.cpu");
} else {
count = HOST_CPU_LOAD_INFO_COUNT;
kr = host_statistics(host, HOST_CPU_LOAD_INFO, (host_info_t)cp_time, &count);
if (unlikely(kr != KERN_SUCCESS)) {
- error("MACOS: host_statistics() failed: %s", mach_error_string(kr));
+ collector_error("MACOS: host_statistics() failed: %s", mach_error_string(kr));
do_cpu = 0;
- error("DISABLED: system.cpu");
+ collector_error("DISABLED: system.cpu");
} else {
st = rrdset_find_active_bytype_localhost("system", "cpu");
@@ -95,13 +95,13 @@ int do_macos_mach_smi(int update_every, usec_t dt) {
kr = host_statistics(host, HOST_VM_INFO, (host_info_t)&vm_statistics, &count);
#endif
if (unlikely(kr != KERN_SUCCESS)) {
- error("MACOS: host_statistics64() failed: %s", mach_error_string(kr));
+ collector_error("MACOS: host_statistics64() failed: %s", mach_error_string(kr));
do_ram = 0;
- error("DISABLED: system.ram");
+ collector_error("DISABLED: system.ram");
do_swapio = 0;
- error("DISABLED: system.swapio");
+ collector_error("DISABLED: system.swapio");
do_pgfaults = 0;
- error("DISABLED: mem.pgfaults");
+ collector_error("DISABLED: mem.pgfaults");
} else {
if (likely(do_ram)) {
st = rrdset_find_active_localhost("system.ram");
diff --git a/collectors/macos.plugin/macos_sysctl.c b/collectors/macos.plugin/macos_sysctl.c
index 1f04f6e4..42f01d85 100644
--- a/collectors/macos.plugin/macos_sysctl.c
+++ b/collectors/macos.plugin/macos_sysctl.c
@@ -222,7 +222,7 @@ int do_macos_sysctl(int update_every, usec_t dt) {
if (likely(do_loadavg)) {
if (unlikely(GETSYSCTL_BY_NAME("vm.loadavg", sysload))) {
do_loadavg = 0;
- error("DISABLED: system.load");
+ collector_error("DISABLED: system.load");
} else {
st = rrdset_find_active_bytype_localhost("system", "load");
@@ -260,7 +260,7 @@ int do_macos_sysctl(int update_every, usec_t dt) {
if (likely(do_swap)) {
if (unlikely(GETSYSCTL_BY_NAME("vm.swapusage", swap_usage))) {
do_swap = 0;
- error("DISABLED: system.swap");
+ collector_error("DISABLED: system.swap");
} else {
st = rrdset_find_active_localhost("system.swap");
if (unlikely(!st)) {
@@ -298,15 +298,15 @@ int do_macos_sysctl(int update_every, usec_t dt) {
mib[4] = NET_RT_IFLIST2;
mib[5] = 0;
if (unlikely(sysctl(mib, 6, NULL, &size, NULL, 0))) {
- error("MACOS: sysctl(%s...) failed: %s", "net interfaces", strerror(errno));
+ collector_error("MACOS: sysctl(%s...) failed: %s", "net interfaces", strerror(errno));
do_bandwidth = 0;
- error("DISABLED: system.ipv4");
+ collector_error("DISABLED: system.ipv4");
} else {
ifstatdata = reallocz(ifstatdata, size);
if (unlikely(sysctl(mib, 6, ifstatdata, &size, NULL, 0) < 0)) {
- error("MACOS: sysctl(%s...) failed: %s", "net interfaces", strerror(errno));
+ collector_error("MACOS: sysctl(%s...) failed: %s", "net interfaces", strerror(errno));
do_bandwidth = 0;
- error("DISABLED: system.ipv4");
+ collector_error("DISABLED: system.ipv4");
} else {
lim = ifstatdata + size;
iftot.ift_ibytes = iftot.ift_obytes = 0;
@@ -353,19 +353,19 @@ int do_macos_sysctl(int update_every, usec_t dt) {
if (likely(do_tcp_packets || do_tcp_errors || do_tcp_handshake || do_tcpext_connaborts || do_tcpext_ofo || do_tcpext_syscookies || do_ecn)) {
if (unlikely(GETSYSCTL_BY_NAME("net.inet.tcp.stats", tcpstat))){
do_tcp_packets = 0;
- error("DISABLED: ipv4.tcppackets");
+ collector_error("DISABLED: ipv4.tcppackets");
do_tcp_errors = 0;
- error("DISABLED: ipv4.tcperrors");
+ collector_error("DISABLED: ipv4.tcperrors");
do_tcp_handshake = 0;
- error("DISABLED: ipv4.tcphandshake");
+ collector_error("DISABLED: ipv4.tcphandshake");
do_tcpext_connaborts = 0;
- error("DISABLED: ipv4.tcpconnaborts");
+ collector_error("DISABLED: ipv4.tcpconnaborts");
do_tcpext_ofo = 0;
- error("DISABLED: ipv4.tcpofo");
+ collector_error("DISABLED: ipv4.tcpofo");
do_tcpext_syscookies = 0;
- error("DISABLED: ipv4.tcpsyncookies");
+ collector_error("DISABLED: ipv4.tcpsyncookies");
do_ecn = 0;
- error("DISABLED: ipv4.ecnpkts");
+ collector_error("DISABLED: ipv4.ecnpkts");
} else {
if (likely(do_tcp_packets)) {
st = rrdset_find_active_localhost("ipv4.tcppackets");
@@ -597,9 +597,9 @@ int do_macos_sysctl(int update_every, usec_t dt) {
if (likely(do_udp_packets || do_udp_errors)) {
if (unlikely(GETSYSCTL_BY_NAME("net.inet.udp.stats", udpstat))) {
do_udp_packets = 0;
- error("DISABLED: ipv4.udppackets");
+ collector_error("DISABLED: ipv4.udppackets");
do_udp_errors = 0;
- error("DISABLED: ipv4.udperrors");
+ collector_error("DISABLED: ipv4.udperrors");
} else {
if (likely(do_udp_packets)) {
st = rrdset_find_active_localhost("ipv4.udppackets");
@@ -673,10 +673,10 @@ int do_macos_sysctl(int update_every, usec_t dt) {
if (likely(do_icmp_packets || do_icmpmsg)) {
if (unlikely(GETSYSCTL_BY_NAME("net.inet.icmp.stats", icmpstat))) {
do_icmp_packets = 0;
- error("DISABLED: ipv4.icmp");
- error("DISABLED: ipv4.icmp_errors");
+ collector_error("DISABLED: ipv4.icmp");
+ collector_error("DISABLED: ipv4.icmp_errors");
do_icmpmsg = 0;
- error("DISABLED: ipv4.icmpmsg");
+ collector_error("DISABLED: ipv4.icmpmsg");
} else {
for (i = 0; i <= ICMP_MAXTYPE; i++) {
icmp_total.msgs_in += icmpstat.icps_inhist[i];
@@ -777,13 +777,13 @@ int do_macos_sysctl(int update_every, usec_t dt) {
if (likely(do_ip_packets || do_ip_fragsout || do_ip_fragsin || do_ip_errors)) {
if (unlikely(GETSYSCTL_BY_NAME("net.inet.ip.stats", ipstat))) {
do_ip_packets = 0;
- error("DISABLED: ipv4.packets");
+ collector_error("DISABLED: ipv4.packets");
do_ip_fragsout = 0;
- error("DISABLED: ipv4.fragsout");
+ collector_error("DISABLED: ipv4.fragsout");
do_ip_fragsin = 0;
- error("DISABLED: ipv4.fragsin");
+ collector_error("DISABLED: ipv4.fragsin");
do_ip_errors = 0;
- error("DISABLED: ipv4.errors");
+ collector_error("DISABLED: ipv4.errors");
} else {
if (likely(do_ip_packets)) {
st = rrdset_find_active_localhost("ipv4.packets");
@@ -919,13 +919,13 @@ int do_macos_sysctl(int update_every, usec_t dt) {
if (likely(do_ip6_packets || do_ip6_fragsout || do_ip6_fragsin || do_ip6_errors)) {
if (unlikely(GETSYSCTL_BY_NAME("net.inet6.ip6.stats", ip6stat))) {
do_ip6_packets = 0;
- error("DISABLED: ipv6.packets");
+ collector_error("DISABLED: ipv6.packets");
do_ip6_fragsout = 0;
- error("DISABLED: ipv6.fragsout");
+ collector_error("DISABLED: ipv6.fragsout");
do_ip6_fragsin = 0;
- error("DISABLED: ipv6.fragsin");
+ collector_error("DISABLED: ipv6.fragsin");
do_ip6_errors = 0;
- error("DISABLED: ipv6.errors");
+ collector_error("DISABLED: ipv6.errors");
} else {
if (do_ip6_packets == CONFIG_BOOLEAN_YES || (do_ip6_packets == CONFIG_BOOLEAN_AUTO &&
(ip6stat.ip6s_localout ||
@@ -1096,7 +1096,7 @@ int do_macos_sysctl(int update_every, usec_t dt) {
if (likely(do_icmp6 || do_icmp6_redir || do_icmp6_errors || do_icmp6_echos || do_icmp6_router || do_icmp6_neighbor || do_icmp6_types)) {
if (unlikely(GETSYSCTL_BY_NAME("net.inet6.icmp6.stats", icmp6stat))) {
do_icmp6 = 0;
- error("DISABLED: ipv6.icmp");
+ collector_error("DISABLED: ipv6.icmp");
} else {
for (i = 0; i <= ICMP6_MAXTYPE; i++) {
icmp6_total.msgs_in += icmp6stat.icp6s_inhist[i];
@@ -1392,7 +1392,7 @@ int do_macos_sysctl(int update_every, usec_t dt) {
if (likely(do_uptime)) {
if (unlikely(GETSYSCTL_BY_NAME("kern.boottime", boot_time))) {
do_uptime = 0;
- error("DISABLED: system.uptime");
+ collector_error("DISABLED: system.uptime");
} else {
clock_gettime(CLOCK_REALTIME, &cur_time);
st = rrdset_find_active_localhost("system.uptime");
diff --git a/collectors/macos.plugin/plugin_macos.c b/collectors/macos.plugin/plugin_macos.c
index 10472bdb..f3b86051 100644
--- a/collectors/macos.plugin/plugin_macos.c
+++ b/collectors/macos.plugin/plugin_macos.c
@@ -32,7 +32,7 @@ static void macos_main_cleanup(void *ptr)
struct netdata_static_thread *static_thread = (struct netdata_static_thread *)ptr;
static_thread->enabled = NETDATA_MAIN_THREAD_EXITING;
- info("cleaning up...");
+ collector_info("cleaning up...");
static_thread->enabled = NETDATA_MAIN_THREAD_EXITED;
}
diff --git a/collectors/nfacct.plugin/README.md b/collectors/nfacct.plugin/README.md
index bacc8b70..f57625c8 100644
--- a/collectors/nfacct.plugin/README.md
+++ b/collectors/nfacct.plugin/README.md
@@ -1,6 +1,10 @@
<!--
-title: "nfacct.plugin"
-custom_edit_url: https://github.com/netdata/netdata/edit/master/collectors/nfacct.plugin/README.md
+title: "Monitor Netfilter statistics (nfacct.plugin)"
+custom_edit_url: "https://github.com/netdata/netdata/edit/master/collectors/nfacct.plugin/README.md"
+sidebar_label: "Netfilter statistics (nfacct.plugin)"
+learn_status: "Published"
+learn_topic_type: "References"
+learn_rel_path: "References/Collectors references/Networking"
-->
# nfacct.plugin
diff --git a/collectors/nfacct.plugin/plugin_nfacct.c b/collectors/nfacct.plugin/plugin_nfacct.c
index eeadb3cc..430ceab5 100644
--- a/collectors/nfacct.plugin/plugin_nfacct.c
+++ b/collectors/nfacct.plugin/plugin_nfacct.c
@@ -92,14 +92,14 @@ static int nfstat_init(int update_every) {
nfstat_root.mnl = mnl_socket_open(NETLINK_NETFILTER);
if(!nfstat_root.mnl) {
- error("NFSTAT: mnl_socket_open() failed");
+ collector_error("NFSTAT: mnl_socket_open() failed");
return 1;
}
nfstat_root.seq = (unsigned int)now_realtime_sec() - 1;
if(mnl_socket_bind(nfstat_root.mnl, 0, MNL_SOCKET_AUTOPID) < 0) {
- error("NFSTAT: mnl_socket_bind() failed");
+ collector_error("NFSTAT: mnl_socket_bind() failed");
return 1;
}
nfstat_root.portid = mnl_socket_get_portid(nfstat_root.mnl);
@@ -132,7 +132,7 @@ static int nfct_stats_attr_cb(const struct nlattr *attr, void *data) {
return MNL_CB_OK;
if (mnl_attr_validate(attr, MNL_TYPE_U32) < 0) {
- error("NFSTAT: mnl_attr_validate() failed");
+ collector_error("NFSTAT: mnl_attr_validate() failed");
return MNL_CB_ERROR;
}
@@ -173,7 +173,7 @@ static int nfstat_collect_conntrack() {
// send the request
if(mnl_socket_sendto(nfstat_root.mnl, nfstat_root.nlh, nfstat_root.nlh->nlmsg_len) < 0) {
- error("NFSTAT: mnl_socket_sendto() failed");
+ collector_error("NFSTAT: mnl_socket_sendto() failed");
return 1;
}
@@ -193,7 +193,7 @@ static int nfstat_collect_conntrack() {
// verify we run without issues
if (ret == -1) {
- error("NFSTAT: error communicating with kernel. This plugin can only work when netdata runs as root.");
+ collector_error("NFSTAT: error communicating with kernel. This plugin can only work when netdata runs as root.");
return 1;
}
@@ -209,7 +209,7 @@ static int nfexp_stats_attr_cb(const struct nlattr *attr, void *data)
return MNL_CB_OK;
if (mnl_attr_validate(attr, MNL_TYPE_U32) < 0) {
- error("NFSTAT EXP: mnl_attr_validate() failed");
+ collector_error("NFSTAT EXP: mnl_attr_validate() failed");
return MNL_CB_ERROR;
}
@@ -245,7 +245,7 @@ static int nfstat_collect_conntrack_expectations() {
// send the request
if(mnl_socket_sendto(nfstat_root.mnl, nfstat_root.nlh, nfstat_root.nlh->nlmsg_len) < 0) {
- error("NFSTAT: mnl_socket_sendto() failed");
+ collector_error("NFSTAT: mnl_socket_sendto() failed");
return 1;
}
@@ -265,7 +265,7 @@ static int nfstat_collect_conntrack_expectations() {
// verify we run without issues
if (ret == -1) {
- error("NFSTAT: error communicating with kernel. This plugin can only work when netdata runs as root.");
+ collector_error("NFSTAT: error communicating with kernel. This plugin can only work when netdata runs as root.");
return 1;
}
@@ -561,7 +561,7 @@ static int nfacct_init(int update_every) {
nfacct_root.nfacct_buffer = nfacct_alloc();
if(!nfacct_root.nfacct_buffer) {
- error("nfacct.plugin: nfacct_alloc() failed.");
+ collector_error("nfacct.plugin: nfacct_alloc() failed.");
return 0;
}
@@ -569,12 +569,12 @@ static int nfacct_init(int update_every) {
nfacct_root.mnl = mnl_socket_open(NETLINK_NETFILTER);
if(!nfacct_root.mnl) {
- error("nfacct.plugin: mnl_socket_open() failed");
+ collector_error("nfacct.plugin: mnl_socket_open() failed");
return 1;
}
if(mnl_socket_bind(nfacct_root.mnl, 0, MNL_SOCKET_AUTOPID) < 0) {
- error("nfacct.plugin: mnl_socket_bind() failed");
+ collector_error("nfacct.plugin: mnl_socket_bind() failed");
return 1;
}
nfacct_root.portid = mnl_socket_get_portid(nfacct_root.mnl);
@@ -586,7 +586,7 @@ static int nfacct_callback(const struct nlmsghdr *nlh, void *data) {
(void)data;
if(nfacct_nlmsg_parse_payload(nlh, nfacct_root.nfacct_buffer) < 0) {
- error("NFACCT: nfacct_nlmsg_parse_payload() failed.");
+ collector_error("NFACCT: nfacct_nlmsg_parse_payload() failed.");
return MNL_CB_OK;
}
@@ -612,13 +612,13 @@ static int nfacct_collect() {
nfacct_root.seq++;
nfacct_root.nlh = nfacct_nlmsg_build_hdr(nfacct_root.buf, NFNL_MSG_ACCT_GET, NLM_F_DUMP, (uint32_t)nfacct_root.seq);
if(!nfacct_root.nlh) {
- error("NFACCT: nfacct_nlmsg_build_hdr() failed");
+ collector_error("NFACCT: nfacct_nlmsg_build_hdr() failed");
return 1;
}
// send the request
if(mnl_socket_sendto(nfacct_root.mnl, nfacct_root.nlh, nfacct_root.nlh->nlmsg_len) < 0) {
- error("NFACCT: mnl_socket_sendto() failed");
+ collector_error("NFACCT: mnl_socket_sendto() failed");
return 1;
}
@@ -638,7 +638,7 @@ static int nfacct_collect() {
// verify we run without issues
if (ret == -1) {
- error("NFACCT: error communicating with kernel. This plugin can only work when netdata runs as root.");
+ collector_error("NFACCT: error communicating with kernel. This plugin can only work when netdata runs as root.");
return 1;
}
@@ -740,11 +740,12 @@ void nfacct_signals()
for (i = 0; signals[i]; i++) {
if(sigaction(signals[i], &sa, NULL) == -1)
- error("Cannot add the handler to signal %d", signals[i]);
+ collector_error("Cannot add the handler to signal %d", signals[i]);
}
}
int main(int argc, char **argv) {
+ stderror = stderr;
clocks_init();
// ------------------------------------------------------------------------
@@ -813,7 +814,7 @@ int main(int argc, char **argv) {
exit(1);
}
- error("nfacct.plugin: ignoring parameter '%s'", argv[i]);
+ collector_error("nfacct.plugin: ignoring parameter '%s'", argv[i]);
}
nfacct_signals();
@@ -823,7 +824,7 @@ int main(int argc, char **argv) {
if(freq >= netdata_update_every)
netdata_update_every = freq;
else if(freq)
- error("update frequency %d seconds is too small for NFACCT. Using %d.", freq, netdata_update_every);
+ collector_error("update frequency %d seconds is too small for NFACCT. Using %d.", freq, netdata_update_every);
if (debug)
fprintf(stderr, "nfacct.plugin: calling nfacct_init()\n");
@@ -882,5 +883,5 @@ int main(int argc, char **argv) {
if(now_monotonic_sec() - started_t > 14400) break;
}
- info("NFACCT process exiting");
+ collector_info("NFACCT process exiting");
}
diff --git a/collectors/perf.plugin/README.md b/collectors/perf.plugin/README.md
index a7a87aca..9e114363 100644
--- a/collectors/perf.plugin/README.md
+++ b/collectors/perf.plugin/README.md
@@ -1,6 +1,10 @@
<!--
-title: "perf.plugin"
-custom_edit_url: https://github.com/netdata/netdata/edit/master/collectors/perf.plugin/README.md
+title: "Monitor CPU performance statistics (perf.plugin)"
+custom_edit_url: "https://github.com/netdata/netdata/edit/master/collectors/perf.plugin/README.md"
+sidebar_label: "CPU performance statistics (perf.plugin)"
+learn_status: "Published"
+learn_topic_type: "References"
+learn_rel_path: "References/Collectors references/System metrics"
-->
# perf.plugin
diff --git a/collectors/perf.plugin/perf_plugin.c b/collectors/perf.plugin/perf_plugin.c
index b2f7d2e1..68c0f917 100644
--- a/collectors/perf.plugin/perf_plugin.c
+++ b/collectors/perf.plugin/perf_plugin.c
@@ -294,15 +294,15 @@ static int perf_init() {
if(unlikely(fd < 0)) {
switch errno {
case EACCES:
- error("Cannot access to the PMU: Permission denied");
+ collector_error("Cannot access to the PMU: Permission denied");
break;
case EBUSY:
- error("Another event already has exclusive access to the PMU");
+ collector_error("Another event already has exclusive access to the PMU");
break;
default:
- error("Cannot open perf event");
+ collector_error("Cannot open perf event");
}
- error("Disabling event %u", current_event->id);
+ collector_error("Disabling event %u", current_event->id);
current_event->disabled = 1;
}
@@ -346,7 +346,7 @@ static void reenable_events() {
if(ioctl(current_fd, PERF_EVENT_IOC_DISABLE, PERF_IOC_FLAG_GROUP) == -1
|| ioctl(current_fd, PERF_EVENT_IOC_ENABLE, PERF_IOC_FLAG_GROUP) == -1)
{
- error("Cannot reenable event group");
+ collector_error("Cannot reenable event group");
}
}
}
@@ -388,7 +388,7 @@ static int perf_collect() {
current_event->updated = 1;
}
else {
- error("Cannot update value for event %u", current_event->id);
+ collector_error("Cannot update value for event %u", current_event->id);
return 1;
}
}
@@ -1272,17 +1272,18 @@ void parse_command_line(int argc, char **argv) {
exit(1);
}
- error("ignoring parameter '%s'", argv[i]);
+ collector_error("ignoring parameter '%s'", argv[i]);
}
if(!plugin_enabled){
- info("no charts enabled - nothing to do.");
+ collector_info("no charts enabled - nothing to do.");
printf("DISABLE\n");
exit(1);
}
}
int main(int argc, char **argv) {
+ stderror = stderr;
clocks_init();
// ------------------------------------------------------------------------
@@ -1304,7 +1305,7 @@ int main(int argc, char **argv) {
if(freq >= update_every)
update_every = freq;
else if(freq)
- error("update frequency %d seconds is too small for PERF. Using %d.", freq, update_every);
+ collector_error("update frequency %d seconds is too small for PERF. Using %d.", freq, update_every);
if(unlikely(debug)) fprintf(stderr, "perf.plugin: calling perf_init()\n");
int perf = !perf_init();
@@ -1348,6 +1349,6 @@ int main(int argc, char **argv) {
if(now_monotonic_sec() - started_t > 14400) break;
}
- info("process exiting");
+ collector_info("process exiting");
perf_free();
}
diff --git a/collectors/plugins.d/README.md b/collectors/plugins.d/README.md
index 2ecf233f..8ad1d3a6 100644
--- a/collectors/plugins.d/README.md
+++ b/collectors/plugins.d/README.md
@@ -1,6 +1,10 @@
<!--
title: "External plugins overview"
-custom_edit_url: https://github.com/netdata/netdata/edit/master/collectors/plugins.d/README.md
+custom_edit_url: "https://github.com/netdata/netdata/edit/master/collectors/plugins.d/README.md"
+sidebar_label: "External plugins overview"
+learn_status: "Published"
+learn_topic_type: "References"
+learn_rel_path: "Developers"
-->
# External plugins overview
@@ -12,17 +16,18 @@ from external processes, thus allowing Netdata to use **external plugins**.
|plugin|language|O/S|description|
|:----:|:------:|:-:|:----------|
-|[apps.plugin](/collectors/apps.plugin/README.md)|`C`|linux, freebsd|monitors the whole process tree on Linux and FreeBSD and breaks down system resource usage by **process**, **user** and **user group**.|
-|[charts.d.plugin](/collectors/charts.d.plugin/README.md)|`BASH`|all|a **plugin orchestrator** for data collection modules written in `BASH` v4+.|
-|[cups.plugin](/collectors/cups.plugin/README.md)|`C`|all|monitors **CUPS**|
-|[fping.plugin](/collectors/fping.plugin/README.md)|`C`|all|measures network latency, jitter and packet loss between the monitored node and any number of remote network end points.|
-|[ioping.plugin](/collectors/ioping.plugin/README.md)|`C`|all|measures disk latency.|
-|[freeipmi.plugin](/collectors/freeipmi.plugin/README.md)|`C`|linux|collects metrics from enterprise hardware sensors, on Linux servers.|
-|[nfacct.plugin](/collectors/nfacct.plugin/README.md)|`C`|linux|collects netfilter firewall, connection tracker and accounting metrics using `libmnl` and `libnetfilter_acct`.|
-|[xenstat.plugin](/collectors/xenstat.plugin/README.md)|`C`|linux|collects XenServer and XCP-ng metrics using `lxenstat`.|
-|[perf.plugin](/collectors/perf.plugin/README.md)|`C`|linux|collects CPU performance metrics using performance monitoring units (PMU).|
-|[python.d.plugin](/collectors/python.d.plugin/README.md)|`python`|all|a **plugin orchestrator** for data collection modules written in `python` v2 or v3 (both are supported).|
-|[slabinfo.plugin](/collectors/slabinfo.plugin/README.md)|`C`|linux|collects kernel internal cache objects (SLAB) metrics.|
+|[apps.plugin](https://github.com/netdata/netdata/blob/master/collectors/apps.plugin/README.md)|`C`|linux, freebsd|monitors the whole process tree on Linux and FreeBSD and breaks down system resource usage by **process**, **user** and **user group**.|
+|[charts.d.plugin](https://github.com/netdata/netdata/blob/master/collectors/charts.d.plugin/README.md)|`BASH`|all|a **plugin orchestrator** for data collection modules written in `BASH` v4+.|
+|[cups.plugin](https://github.com/netdata/netdata/blob/master/collectors/cups.plugin/README.md)|`C`|all|monitors **CUPS**|
+|[ebpf.plugin](https://github.com/netdata/netdata/blob/master/collectors/ebpf.plugin/README.md)|`C`|linux|monitors different metrics on environments using kernel internal functions.|
+|[go.d.plugin](https://github.com/netdata/go.d.plugin/blob/master/README.md)|`GO`|all|collects metrics from the system, applications, or third-party APIs.|
+|[ioping.plugin](https://github.com/netdata/netdata/blob/master/collectors/ioping.plugin/README.md)|`C`|all|measures disk latency.|
+|[freeipmi.plugin](https://github.com/netdata/netdata/blob/master/collectors/freeipmi.plugin/README.md)|`C`|linux|collects metrics from enterprise hardware sensors, on Linux servers.|
+|[nfacct.plugin](https://github.com/netdata/netdata/blob/master/collectors/nfacct.plugin/README.md)|`C`|linux|collects netfilter firewall, connection tracker and accounting metrics using `libmnl` and `libnetfilter_acct`.|
+|[xenstat.plugin](https://github.com/netdata/netdata/blob/master/collectors/xenstat.plugin/README.md)|`C`|linux|collects XenServer and XCP-ng metrics using `lxenstat`.|
+|[perf.plugin](https://github.com/netdata/netdata/blob/master/collectors/perf.plugin/README.md)|`C`|linux|collects CPU performance metrics using performance monitoring units (PMU).|
+|[python.d.plugin](https://github.com/netdata/netdata/blob/master/collectors/python.d.plugin/README.md)|`python`|all|a **plugin orchestrator** for data collection modules written in `python` v2 or v3 (both are supported).|
+|[slabinfo.plugin](https://github.com/netdata/netdata/blob/master/collectors/slabinfo.plugin/README.md)|`C`|linux|collects kernel internal cache objects (SLAB) metrics.|
Plugin orchestrators may also be described as **modular plugins**. They are modular since they accept custom made modules to be included. Writing modules for these plugins is easier than accessing the native Netdata API directly. You will find modules already available for each orchestrator under the directory of the particular modular plugin (e.g. under python.d.plugin for the python orchestrator).
Each of these modular plugins has each own methods for defining modules. Please check the examples and their documentation.
@@ -71,7 +76,6 @@ Example:
# check for new plugins every = 60
# charts.d = yes
- # fping = yes
# ioping = yes
# python.d = yes
```
@@ -504,12 +508,12 @@ or do not output the line at all.
## Modular Plugins
1. **python**, use `python.d.plugin`, there are many examples in the [python.d
- directory](/collectors/python.d.plugin/README.md)
+ directory](https://github.com/netdata/netdata/blob/master/collectors/python.d.plugin/README.md)
python is ideal for Netdata plugins. It is a simple, yet powerful way to collect data, it has a very small memory footprint, although it is not the most CPU efficient way to do it.
2. **BASH**, use `charts.d.plugin`, there are many examples in the [charts.d
- directory](/collectors/charts.d.plugin/README.md)
+ directory](https://github.com/netdata/netdata/blob/master/collectors/charts.d.plugin/README.md)
BASH is the simplest scripting language for collecting values. It is the less efficient though in terms of CPU resources. You can use it to collect data quickly, but extensive use of it might use a lot of system resources.
diff --git a/collectors/plugins.d/plugins_d.c b/collectors/plugins.d/plugins_d.c
index 79abc707..7608f3af 100644
--- a/collectors/plugins.d/plugins_d.c
+++ b/collectors/plugins.d/plugins_d.c
@@ -21,23 +21,54 @@ inline size_t pluginsd_initialize_plugin_directories()
return quoted_strings_splitter(plugins_dir_list, plugin_directories, PLUGINSD_MAX_DIRECTORIES, config_isspace, NULL, NULL, 0);
}
+static inline void plugin_set_disabled(struct plugind *cd) {
+ netdata_spinlock_lock(&cd->unsafe.spinlock);
+ cd->unsafe.enabled = false;
+ netdata_spinlock_unlock(&cd->unsafe.spinlock);
+}
+
+bool plugin_is_enabled(struct plugind *cd) {
+ netdata_spinlock_lock(&cd->unsafe.spinlock);
+ bool ret = cd->unsafe.enabled;
+ netdata_spinlock_unlock(&cd->unsafe.spinlock);
+ return ret;
+}
+
+static inline void plugin_set_running(struct plugind *cd) {
+ netdata_spinlock_lock(&cd->unsafe.spinlock);
+ cd->unsafe.running = true;
+ netdata_spinlock_unlock(&cd->unsafe.spinlock);
+}
+
+static inline bool plugin_is_running(struct plugind *cd) {
+ netdata_spinlock_lock(&cd->unsafe.spinlock);
+ bool ret = cd->unsafe.running;
+ netdata_spinlock_unlock(&cd->unsafe.spinlock);
+ return ret;
+}
+
static void pluginsd_worker_thread_cleanup(void *arg)
{
struct plugind *cd = (struct plugind *)arg;
- if (cd->enabled && !cd->obsolete) {
- cd->obsolete = 1;
+ netdata_spinlock_lock(&cd->unsafe.spinlock);
+
+ cd->unsafe.running = false;
+ cd->unsafe.thread = 0;
+ pid_t pid = cd->unsafe.pid;
+ cd->unsafe.pid = 0;
+
+ netdata_spinlock_unlock(&cd->unsafe.spinlock);
+
+ if (pid) {
info("data collection thread exiting");
- if (cd->pid) {
- siginfo_t info;
- info("killing child process pid %d", cd->pid);
- if (killpid(cd->pid) != -1) {
- info("waiting for child process pid %d to exit...", cd->pid);
- waitid(P_PID, (id_t)cd->pid, &info, WEXITED);
- }
- cd->pid = 0;
+ siginfo_t info;
+ info("killing child process pid %d", pid);
+ if (killpid(pid) != -1) {
+ info("waiting for child process pid %d to exit...", pid);
+ waitid(P_PID, (id_t)pid, &info, WEXITED);
}
}
}
@@ -53,8 +84,8 @@ static void pluginsd_worker_thread_handle_success(struct plugind *cd)
if (likely(cd->serial_failures <= SERIAL_FAILURES_THRESHOLD)) {
info(
"'%s' (pid %d) does not generate useful output but it reports success (exits with 0). %s.",
- cd->fullfilename, cd->pid,
- cd->enabled ? "Waiting a bit before starting it again." : "Will not start it again - it is now disabled.");
+ cd->fullfilename, cd->unsafe.pid,
+ plugin_is_enabled(cd) ? "Waiting a bit before starting it again." : "Will not start it again - it is now disabled.");
sleep((unsigned int)(cd->update_every * 10));
return;
}
@@ -63,35 +94,33 @@ static void pluginsd_worker_thread_handle_success(struct plugind *cd)
error(
"'%s' (pid %d) does not generate useful output, although it reports success (exits with 0)."
"We have tried to collect something %zu times - unsuccessfully. Disabling it.",
- cd->fullfilename, cd->pid, cd->serial_failures);
- cd->enabled = 0;
+ cd->fullfilename, cd->unsafe.pid, cd->serial_failures);
+ plugin_set_disabled(cd);
return;
}
-
- return;
}
static void pluginsd_worker_thread_handle_error(struct plugind *cd, int worker_ret_code)
{
if (worker_ret_code == -1) {
- info("'%s' (pid %d) was killed with SIGTERM. Disabling it.", cd->fullfilename, cd->pid);
- cd->enabled = 0;
+ info("'%s' (pid %d) was killed with SIGTERM. Disabling it.", cd->fullfilename, cd->unsafe.pid);
+ plugin_set_disabled(cd);
return;
}
if (!cd->successful_collections) {
error(
"'%s' (pid %d) exited with error code %d and haven't collected any data. Disabling it.", cd->fullfilename,
- cd->pid, worker_ret_code);
- cd->enabled = 0;
+ cd->unsafe.pid, worker_ret_code);
+ plugin_set_disabled(cd);
return;
}
if (cd->serial_failures <= SERIAL_FAILURES_THRESHOLD) {
error(
"'%s' (pid %d) exited with error code %d, but has given useful output in the past (%zu times). %s",
- cd->fullfilename, cd->pid, worker_ret_code, cd->successful_collections,
- cd->enabled ? "Waiting a bit before starting it again." : "Will not start it again - it is disabled.");
+ cd->fullfilename, cd->unsafe.pid, worker_ret_code, cd->successful_collections,
+ plugin_is_enabled(cd) ? "Waiting a bit before starting it again." : "Will not start it again - it is disabled.");
sleep((unsigned int)(cd->update_every * 10));
return;
}
@@ -100,48 +129,47 @@ static void pluginsd_worker_thread_handle_error(struct plugind *cd, int worker_r
error(
"'%s' (pid %d) exited with error code %d, but has given useful output in the past (%zu times)."
"We tried to restart it %zu times, but it failed to generate data. Disabling it.",
- cd->fullfilename, cd->pid, worker_ret_code, cd->successful_collections, cd->serial_failures);
- cd->enabled = 0;
+ cd->fullfilename, cd->unsafe.pid, worker_ret_code, cd->successful_collections, cd->serial_failures);
+ plugin_set_disabled(cd);
return;
}
-
- return;
}
+
#undef SERIAL_FAILURES_THRESHOLD
-void *pluginsd_worker_thread(void *arg)
+static void *pluginsd_worker_thread(void *arg)
{
worker_register("PLUGINSD");
netdata_thread_cleanup_push(pluginsd_worker_thread_cleanup, arg);
struct plugind *cd = (struct plugind *)arg;
+ plugin_set_running(cd);
- cd->obsolete = 0;
size_t count = 0;
- while (!netdata_exit) {
+ while (service_running(SERVICE_COLLECTORS)) {
FILE *fp_child_input = NULL;
- FILE *fp_child_output = netdata_popen(cd->cmd, &cd->pid, &fp_child_input);
+ FILE *fp_child_output = netdata_popen(cd->cmd, &cd->unsafe.pid, &fp_child_input);
if (unlikely(!fp_child_input || !fp_child_output)) {
error("Cannot popen(\"%s\", \"r\").", cd->cmd);
break;
}
- info("connected to '%s' running on pid %d", cd->fullfilename, cd->pid);
+ info("connected to '%s' running on pid %d", cd->fullfilename, cd->unsafe.pid);
count = pluginsd_process(localhost, cd, fp_child_input, fp_child_output, 0);
- error("'%s' (pid %d) disconnected after %zu successful data collections (ENDs).", cd->fullfilename, cd->pid, count);
- killpid(cd->pid);
+ error("'%s' (pid %d) disconnected after %zu successful data collections (ENDs).", cd->fullfilename, cd->unsafe.pid, count);
+ killpid(cd->unsafe.pid);
- int worker_ret_code = netdata_pclose(fp_child_input, fp_child_output, cd->pid);
+ int worker_ret_code = netdata_pclose(fp_child_input, fp_child_output, cd->unsafe.pid);
if (likely(worker_ret_code == 0))
pluginsd_worker_thread_handle_success(cd);
else
pluginsd_worker_thread_handle_error(cd, worker_ret_code);
- cd->pid = 0;
- if (unlikely(!cd->enabled))
+ cd->unsafe.pid = 0;
+ if (unlikely(!plugin_is_enabled(cd)))
break;
}
worker_unregister();
@@ -158,10 +186,12 @@ static void pluginsd_main_cleanup(void *data)
struct plugind *cd;
for (cd = pluginsd_root; cd; cd = cd->next) {
- if (cd->enabled && !cd->obsolete) {
+ netdata_spinlock_lock(&cd->unsafe.spinlock);
+ if (cd->unsafe.enabled && cd->unsafe.running && cd->unsafe.thread != 0) {
info("stopping plugin thread: %s", cd->id);
- netdata_thread_cancel(cd->thread);
+ netdata_thread_cancel(cd->unsafe.thread);
}
+ netdata_spinlock_unlock(&cd->unsafe.spinlock);
}
info("cleanup completed.");
@@ -186,12 +216,12 @@ void *pluginsd_main(void *ptr)
// so that we don't log broken directories on each loop
int directory_errors[PLUGINSD_MAX_DIRECTORIES] = { 0 };
- while (!netdata_exit) {
+ while (service_running(SERVICE_COLLECTORS)) {
int idx;
const char *directory_name;
for (idx = 0; idx < PLUGINSD_MAX_DIRECTORIES && (directory_name = plugin_directories[idx]); idx++) {
- if (unlikely(netdata_exit))
+ if (unlikely(!service_running(SERVICE_COLLECTORS)))
break;
errno = 0;
@@ -206,7 +236,7 @@ void *pluginsd_main(void *ptr)
struct dirent *file = NULL;
while (likely((file = readdir(dir)))) {
- if (unlikely(netdata_exit))
+ if (unlikely(!service_running(SERVICE_COLLECTORS)))
break;
debug(D_PLUGINSD, "examining file '%s'", file->d_name);
@@ -237,7 +267,7 @@ void *pluginsd_main(void *ptr)
if (unlikely(strcmp(cd->filename, file->d_name) == 0))
break;
- if (likely(cd && !cd->obsolete)) {
+ if (likely(cd && plugin_is_running(cd))) {
debug(D_PLUGINSD, "plugin '%s' is already running", cd->filename);
continue;
}
@@ -252,7 +282,9 @@ void *pluginsd_main(void *ptr)
strncpyz(cd->filename, file->d_name, FILENAME_MAX);
snprintfz(cd->fullfilename, FILENAME_MAX, "%s/%s", directory_name, cd->filename);
- cd->enabled = enabled;
+ cd->unsafe.enabled = enabled;
+ cd->unsafe.running = false;
+
cd->update_every = (int)config_get_number(cd->id, "update every", localhost->rrd_update_every);
cd->started_t = now_realtime_sec();
@@ -266,15 +298,16 @@ void *pluginsd_main(void *ptr)
cd->next = pluginsd_root;
pluginsd_root = cd;
- // it is not currently running
- cd->obsolete = 1;
-
- if (cd->enabled) {
+ if (plugin_is_enabled(cd)) {
char tag[NETDATA_THREAD_TAG_MAX + 1];
- snprintfz(tag, NETDATA_THREAD_TAG_MAX, "PLUGINSD[%s]", pluginname);
+ snprintfz(tag, NETDATA_THREAD_TAG_MAX, "PD[%s]", pluginname);
+
// spawn a new thread for it
- netdata_thread_create(
- &cd->thread, tag, NETDATA_THREAD_OPTION_DEFAULT, pluginsd_worker_thread, cd);
+ netdata_thread_create(&cd->unsafe.thread,
+ tag,
+ NETDATA_THREAD_OPTION_DEFAULT,
+ pluginsd_worker_thread,
+ cd);
}
}
}
diff --git a/collectors/plugins.d/plugins_d.h b/collectors/plugins.d/plugins_d.h
index a8acf038..35af9fe5 100644
--- a/collectors/plugins.d/plugins_d.h
+++ b/collectors/plugins.d/plugins_d.h
@@ -50,9 +50,6 @@ struct plugind {
char fullfilename[FILENAME_MAX+1]; // with path
char cmd[PLUGINSD_CMD_MAX+1]; // the command that it executes
- volatile pid_t pid;
- netdata_thread_t thread;
-
size_t successful_collections; // the number of times we have seen
// values collected from this plugin
@@ -60,8 +57,14 @@ struct plugind {
// without collecting values
int update_every; // the plugin default data collection frequency
- volatile sig_atomic_t obsolete; // do not touch this structure after setting this to 1
- volatile sig_atomic_t enabled; // if this is enabled or not
+
+ struct {
+ SPINLOCK spinlock;
+ bool running; // do not touch this structure after setting this to 1
+ bool enabled; // if this is enabled or not
+ netdata_thread_t thread;
+ pid_t pid;
+ } unsafe;
time_t started_t;
uint32_t capabilities; // follows the same principles as streaming capabilities
diff --git a/collectors/plugins.d/pluginsd_parser.c b/collectors/plugins.d/pluginsd_parser.c
index 5501c12f..2c0f2cbc 100644
--- a/collectors/plugins.d/pluginsd_parser.c
+++ b/collectors/plugins.d/pluginsd_parser.c
@@ -148,8 +148,11 @@ PARSER_RC pluginsd_begin(char **words, size_t num_words, void *user)
((PARSER_USER_OBJECT *)user)->st = st;
usec_t microseconds = 0;
- if (microseconds_txt && *microseconds_txt)
- microseconds = str2ull(microseconds_txt);
+ if (microseconds_txt && *microseconds_txt) {
+ long long t = str2ll(microseconds_txt, NULL);
+ if(t >= 0)
+ microseconds = t;
+ }
#ifdef NETDATA_LOG_REPLICATION_REQUESTS
if(st->replay.log_next_data_collection) {
@@ -326,7 +329,7 @@ PARSER_RC pluginsd_chart_definition_end(char **words, size_t num_words, void *us
{
const char *first_entry_txt = get_word(words, num_words, 1);
const char *last_entry_txt = get_word(words, num_words, 2);
- const char *world_time_txt = get_word(words, num_words, 3);
+ const char *wall_clock_time_txt = get_word(words, num_words, 3);
RRDHOST *host = pluginsd_require_host_from_parent(user, PLUGINSD_KEYWORD_CHART_DEFINITION_END);
if(!host) return PLUGINSD_DISABLE_PLUGIN(user);
@@ -336,12 +339,7 @@ PARSER_RC pluginsd_chart_definition_end(char **words, size_t num_words, void *us
time_t first_entry_child = (first_entry_txt && *first_entry_txt) ? (time_t)str2ul(first_entry_txt) : 0;
time_t last_entry_child = (last_entry_txt && *last_entry_txt) ? (time_t)str2ul(last_entry_txt) : 0;
- time_t child_world_time = (world_time_txt && *world_time_txt) ? (time_t)str2ul(world_time_txt) : now_realtime_sec();
-
- if((first_entry_child != 0 || last_entry_child != 0) && (first_entry_child == 0 || last_entry_child == 0))
- error("PLUGINSD REPLAY ERROR: 'host:%s/chart:%s' got a " PLUGINSD_KEYWORD_CHART_DEFINITION_END " with malformed timings (first time %ld, last time %ld, world time %ld).",
- rrdhost_hostname(host), rrdset_id(st),
- first_entry_child, last_entry_child, child_world_time);
+ time_t child_wall_clock_time = (wall_clock_time_txt && *wall_clock_time_txt) ? (time_t)str2ul(wall_clock_time_txt) : now_realtime_sec();
bool ok = true;
if(!rrdset_flag_check(st, RRDSET_FLAG_RECEIVER_REPLICATION_IN_PROGRESS)) {
@@ -358,7 +356,7 @@ PARSER_RC pluginsd_chart_definition_end(char **words, size_t num_words, void *us
PARSER *parser = ((PARSER_USER_OBJECT *)user)->parser;
ok = replicate_chart_request(send_to_plugin, parser, host, st,
- first_entry_child, last_entry_child, child_world_time,
+ first_entry_child, last_entry_child, child_wall_clock_time,
0, 0);
}
#ifdef NETDATA_LOG_REPLICATION_REQUESTS
@@ -441,19 +439,20 @@ PARSER_RC pluginsd_dimension(char **words, size_t num_words, void *user)
} else
rrddim_isnot_obsolete(st, rd);
+ bool should_update_dimension = false;
+
if (likely(unhide_dimension)) {
rrddim_option_clear(rd, RRDDIM_OPTION_HIDDEN);
- if (rrddim_flag_check(rd, RRDDIM_FLAG_META_HIDDEN)) {
- rrddim_flag_clear(rd, RRDDIM_FLAG_META_HIDDEN);
- metaqueue_dimension_update_flags(rd);
- }
+ should_update_dimension = rrddim_flag_check(rd, RRDDIM_FLAG_META_HIDDEN);
}
else {
rrddim_option_set(rd, RRDDIM_OPTION_HIDDEN);
- if (!rrddim_flag_check(rd, RRDDIM_FLAG_META_HIDDEN)) {
- rrddim_flag_set(rd, RRDDIM_FLAG_META_HIDDEN);
- metaqueue_dimension_update_flags(rd);
- }
+ should_update_dimension = !rrddim_flag_check(rd, RRDDIM_FLAG_META_HIDDEN);
+ }
+
+ if (should_update_dimension) {
+ rrddim_flag_set(rd, RRDDIM_FLAG_METADATA_UPDATE);
+ rrdhost_flag_set(rd->rrdset->rrdhost, RRDHOST_FLAG_METADATA_UPDATE);
}
return PARSER_RC_OK;
@@ -529,7 +528,7 @@ static void inflight_functions_delete_callback(const DICTIONARY_ITEM *item __may
}
void inflight_functions_init(PARSER *parser) {
- parser->inflight.functions = dictionary_create(DICT_OPTION_DONT_OVERWRITE_VALUE);
+ parser->inflight.functions = dictionary_create_advanced(DICT_OPTION_DONT_OVERWRITE_VALUE, &dictionary_stats_category_functions, 0);
dictionary_register_insert_callback(parser->inflight.functions, inflight_functions_insert_callback, parser);
dictionary_register_delete_callback(parser->inflight.functions, inflight_functions_delete_callback, parser);
dictionary_register_conflict_callback(parser->inflight.functions, inflight_functions_conflict_callback, parser);
@@ -883,7 +882,7 @@ PARSER_RC pluginsd_overwrite(char **words __maybe_unused, size_t num_words __may
host->rrdlabels = rrdlabels_create();
rrdlabels_migrate_to_these(host->rrdlabels, (DICTIONARY *) (((PARSER_USER_OBJECT *)user)->new_host_labels));
- metaqueue_store_host_labels(host->machine_guid);
+ rrdhost_flag_set(host, RRDHOST_FLAG_METADATA_LABELS | RRDHOST_FLAG_METADATA_UPDATE);
rrdlabels_destroy(((PARSER_USER_OBJECT *)user)->new_host_labels);
((PARSER_USER_OBJECT *)user)->new_host_labels = NULL;
@@ -991,7 +990,7 @@ PARSER_RC pluginsd_replay_rrdset_begin(char **words, size_t num_words, void *use
if(start_time && end_time && start_time < wall_clock_time + tolerance && end_time < wall_clock_time + tolerance && start_time < end_time) {
if (unlikely(end_time - start_time != st->update_every))
- rrdset_set_update_every(st, end_time - start_time);
+ rrdset_set_update_every_s(st, end_time - start_time);
st->last_collected_time.tv_sec = end_time;
st->last_collected_time.tv_usec = 0;
@@ -1124,6 +1123,9 @@ PARSER_RC pluginsd_replay_set(char **words, size_t num_words, void *user)
PARSER_RC pluginsd_replay_rrddim_collection_state(char **words, size_t num_words, void *user)
{
+ if(((PARSER_USER_OBJECT *) user)->replay.rset_enabled == false)
+ return PARSER_RC_OK;
+
char *dimension = get_word(words, num_words, 1);
char *last_collected_ut_str = get_word(words, num_words, 2);
char *last_collected_value_str = get_word(words, num_words, 3);
@@ -1156,6 +1158,9 @@ PARSER_RC pluginsd_replay_rrddim_collection_state(char **words, size_t num_words
PARSER_RC pluginsd_replay_rrdset_collection_state(char **words, size_t num_words, void *user)
{
+ if(((PARSER_USER_OBJECT *) user)->replay.rset_enabled == false)
+ return PARSER_RC_OK;
+
char *last_collected_ut_str = get_word(words, num_words, 1);
char *last_updated_ut_str = get_word(words, num_words, 2);
@@ -1238,7 +1243,8 @@ PARSER_RC pluginsd_replay_end(char **words, size_t num_words, void *user)
time_t started = st->rrdhost->receiver->replication_first_time_t;
time_t current = ((PARSER_USER_OBJECT *) user)->replay.end_time;
- worker_set_metric(WORKER_RECEIVER_JOB_REPLICATION_COMPLETION,
+ if(started && current > started)
+ worker_set_metric(WORKER_RECEIVER_JOB_REPLICATION_COMPLETION,
(NETDATA_DOUBLE)(current - started) * 100.0 / (NETDATA_DOUBLE)(now - started));
}
@@ -1251,6 +1257,7 @@ PARSER_RC pluginsd_replay_end(char **words, size_t num_words, void *user)
st->counter++;
st->counter_done++;
+ store_metric_collection_completed();
#ifdef NETDATA_LOG_REPLICATION_REQUESTS
st->replay.start_streaming = false;
@@ -1262,7 +1269,7 @@ PARSER_RC pluginsd_replay_end(char **words, size_t num_words, void *user)
if (start_streaming) {
if (st->update_every != update_every_child)
- rrdset_set_update_every(st, update_every_child);
+ rrdset_set_update_every_s(st, update_every_child);
if(rrdset_flag_check(st, RRDSET_FLAG_RECEIVER_REPLICATION_IN_PROGRESS)) {
rrdset_flag_set(st, RRDSET_FLAG_RECEIVER_REPLICATION_FINISHED);
@@ -1298,10 +1305,10 @@ static void pluginsd_process_thread_cleanup(void *ptr) {
inline size_t pluginsd_process(RRDHOST *host, struct plugind *cd, FILE *fp_plugin_input, FILE *fp_plugin_output, int trust_durations)
{
- int enabled = cd->enabled;
+ int enabled = cd->unsafe.enabled;
if (!fp_plugin_input || !fp_plugin_output || !enabled) {
- cd->enabled = 0;
+ cd->unsafe.enabled = 0;
return 0;
}
@@ -1321,7 +1328,7 @@ inline size_t pluginsd_process(RRDHOST *host, struct plugind *cd, FILE *fp_plugi
clearerr(fp_plugin_output);
PARSER_USER_OBJECT user = {
- .enabled = cd->enabled,
+ .enabled = cd->unsafe.enabled,
.host = host,
.cd = cd,
.trust_durations = trust_durations
@@ -1339,14 +1346,14 @@ inline size_t pluginsd_process(RRDHOST *host, struct plugind *cd, FILE *fp_plugi
user.parser = parser;
while (likely(!parser_next(parser))) {
- if (unlikely(netdata_exit || parser_action(parser, NULL)))
+ if (unlikely(!service_running(SERVICE_COLLECTORS) || parser_action(parser, NULL)))
break;
}
// free parser with the pop function
netdata_thread_cleanup_pop(1);
- cd->enabled = user.enabled;
+ cd->unsafe.enabled = user.enabled;
size_t count = user.count;
if (likely(count)) {
diff --git a/collectors/proc.plugin/README.md b/collectors/proc.plugin/README.md
index 32e2112a..f0355060 100644
--- a/collectors/proc.plugin/README.md
+++ b/collectors/proc.plugin/README.md
@@ -1,6 +1,10 @@
<!--
-title: "proc.plugin"
-custom_edit_url: https://github.com/netdata/netdata/edit/master/collectors/proc.plugin/README.md
+title: "OS provided metrics (proc.plugin)"
+custom_edit_url: "https://github.com/netdata/netdata/edit/master/collectors/proc.plugin/README.md"
+sidebar_label: "OS provided metrics (proc.plugin)"
+learn_status: "Published"
+learn_topic_type: "References"
+learn_rel_path: "References/Collectors references/System metrics"
-->
# proc.plugin
@@ -400,7 +404,7 @@ You can set the following values for each configuration option:
There are several alarms defined in `health.d/net.conf`.
-The tricky ones are `inbound packets dropped` and `inbound packets dropped ratio`. They have quite a strict policy so that they warn users about possible issues. These alarms can be annoying for some network configurations. It is especially true for some bonding configurations if an interface is a child or a bonding interface itself. If it is expected to have a certain number of drops on an interface for a certain network configuration, a separate alarm with different triggering thresholds can be created or the existing one can be disabled for this specific interface. It can be done with the help of the [families](/health/REFERENCE.md#alarm-line-families) line in the alarm configuration. For example, if you want to disable the `inbound packets dropped` alarm for `eth0`, set `families: !eth0 *` in the alarm definition for `template: inbound_packets_dropped`.
+The tricky ones are `inbound packets dropped` and `inbound packets dropped ratio`. They have quite a strict policy so that they warn users about possible issues. These alarms can be annoying for some network configurations. It is especially true for some bonding configurations if an interface is a child or a bonding interface itself. If it is expected to have a certain number of drops on an interface for a certain network configuration, a separate alarm with different triggering thresholds can be created or the existing one can be disabled for this specific interface. It can be done with the help of the [families](https://github.com/netdata/netdata/blob/master/health/REFERENCE.md#alarm-line-families) line in the alarm configuration. For example, if you want to disable the `inbound packets dropped` alarm for `eth0`, set `families: !eth0 *` in the alarm definition for `template: inbound_packets_dropped`.
#### configuration
diff --git a/collectors/proc.plugin/ipc.c b/collectors/proc.plugin/ipc.c
index 7d3d2ecb..adfc15be 100644
--- a/collectors/proc.plugin/ipc.c
+++ b/collectors/proc.plugin/ipc.c
@@ -82,7 +82,7 @@ static inline int ipc_sem_get_limits(struct ipc_limits *lim) {
ff = procfile_open(filename, NULL, PROCFILE_FLAG_DEFAULT);
if(unlikely(!ff)) {
if(unlikely(!error_shown)) {
- error("IPC: Cannot open file '%s'.", filename);
+ collector_error("IPC: Cannot open file '%s'.", filename);
error_shown = 1;
}
goto ipc;
@@ -92,7 +92,7 @@ static inline int ipc_sem_get_limits(struct ipc_limits *lim) {
ff = procfile_readall(ff);
if(unlikely(!ff)) {
if(unlikely(!error_shown)) {
- error("IPC: Cannot read file '%s'.", filename);
+ collector_error("IPC: Cannot read file '%s'.", filename);
error_shown = 1;
}
goto ipc;
@@ -108,7 +108,7 @@ static inline int ipc_sem_get_limits(struct ipc_limits *lim) {
}
else {
if(unlikely(!error_shown)) {
- error("IPC: Invalid content in file '%s'.", filename);
+ collector_error("IPC: Invalid content in file '%s'.", filename);
error_shown = 1;
}
goto ipc;
@@ -122,7 +122,7 @@ ipc:
union semun arg = {.array = (ushort *) &seminfo};
if(unlikely(semctl(0, 0, IPC_INFO, arg) < 0)) {
- error("IPC: Failed to read '%s' and request IPC_INFO with semctl().", filename);
+ collector_error("IPC: Failed to read '%s' and request IPC_INFO with semctl().", filename);
goto error;
}
@@ -166,7 +166,7 @@ static inline int ipc_sem_get_status(struct ipc_status *st) {
/* kernel not configured for semaphores */
static int error_shown = 0;
if(unlikely(!error_shown)) {
- error("IPC: kernel is not configured for semaphores");
+ collector_error("IPC: kernel is not configured for semaphores");
error_shown = 1;
}
st->semusz = 0;
@@ -195,7 +195,7 @@ int ipc_msq_get_info(char *msg_filename, struct message_queue **message_queue_ro
size_t words = 0;
if(unlikely(lines < 2)) {
- error("Cannot read %s. Expected 2 or more lines, read %zu.", procfile_filename(ff), lines);
+ collector_error("Cannot read %s. Expected 2 or more lines, read %zu.", procfile_filename(ff), lines);
return 1;
}
@@ -205,7 +205,7 @@ int ipc_msq_get_info(char *msg_filename, struct message_queue **message_queue_ro
words = procfile_linewords(ff, l);
if(unlikely(words < 2)) continue;
if(unlikely(words < 14)) {
- error("Cannot read %s line. Expected 14 params, read %zu.", procfile_filename(ff), words);
+ collector_error("Cannot read %s line. Expected 14 params, read %zu.", procfile_filename(ff), words);
continue;
}
@@ -250,7 +250,7 @@ int ipc_shm_get_info(char *shm_filename, struct shm_stats *shm) {
size_t words = 0;
if(unlikely(lines < 2)) {
- error("Cannot read %s. Expected 2 or more lines, read %zu.", procfile_filename(ff), lines);
+ collector_error("Cannot read %s. Expected 2 or more lines, read %zu.", procfile_filename(ff), lines);
return 1;
}
@@ -263,7 +263,7 @@ int ipc_shm_get_info(char *shm_filename, struct shm_stats *shm) {
words = procfile_linewords(ff, l);
if(unlikely(words < 2)) continue;
if(unlikely(words < 16)) {
- error("Cannot read %s line. Expected 16 params, read %zu.", procfile_filename(ff), words);
+ collector_error("Cannot read %s line. Expected 16 params, read %zu.", procfile_filename(ff), words);
continue;
}
@@ -306,11 +306,11 @@ int do_ipc(int update_every, usec_t dt) {
// make sure it works
if(ipc_sem_get_limits(&limits) == -1) {
- error("unable to fetch semaphore limits");
+ collector_error("unable to fetch semaphore limits");
do_sem = CONFIG_BOOLEAN_NO;
}
else if(ipc_sem_get_status(&status) == -1) {
- error("unable to fetch semaphore statistics");
+ collector_error("unable to fetch semaphore statistics");
do_sem = CONFIG_BOOLEAN_NO;
}
else {
@@ -362,7 +362,7 @@ int do_ipc(int update_every, usec_t dt) {
}
if(unlikely(do_sem == CONFIG_BOOLEAN_NO && do_msg == CONFIG_BOOLEAN_NO)) {
- error("ipc module disabled");
+ collector_error("ipc module disabled");
return 1;
}
}
@@ -370,7 +370,7 @@ int do_ipc(int update_every, usec_t dt) {
if(likely(do_sem != CONFIG_BOOLEAN_NO)) {
if(unlikely(read_limits_next < 0)) {
if(unlikely(ipc_sem_get_limits(&limits) == -1)) {
- error("Unable to fetch semaphore limits.");
+ collector_error("Unable to fetch semaphore limits.");
}
else {
if(semaphores_max) rrdvar_custom_host_variable_set(localhost, semaphores_max, limits.semmns);
@@ -386,7 +386,7 @@ int do_ipc(int update_every, usec_t dt) {
read_limits_next--;
if(unlikely(ipc_sem_get_status(&status) == -1)) {
- error("Unable to get semaphore statistics");
+ collector_error("Unable to get semaphore statistics");
return 0;
}
@@ -478,8 +478,8 @@ int do_ipc(int update_every, usec_t dt) {
long long dimensions_num = rrdset_number_of_dimensions(st_msq_messages);
if(unlikely(dimensions_num > dimensions_limit)) {
- info("Message queue statistics has been disabled");
- info("There are %lld dimensions in memory but limit was set to %lld", dimensions_num, dimensions_limit);
+ collector_info("Message queue statistics has been disabled");
+ collector_info("There are %lld dimensions in memory but limit was set to %lld", dimensions_num, dimensions_limit);
rrdset_is_obsolete(st_msq_messages);
rrdset_is_obsolete(st_msq_bytes);
st_msq_messages = NULL;
@@ -487,11 +487,11 @@ int do_ipc(int update_every, usec_t dt) {
do_msg = CONFIG_BOOLEAN_NO;
}
else if(unlikely(!message_queue_root)) {
- info("Making chart %s (%s) obsolete since it does not have any dimensions", rrdset_name(st_msq_messages), rrdset_id(st_msq_messages));
+ collector_info("Making chart %s (%s) obsolete since it does not have any dimensions", rrdset_name(st_msq_messages), rrdset_id(st_msq_messages));
rrdset_is_obsolete(st_msq_messages);
st_msq_messages = NULL;
- info("Making chart %s (%s) obsolete since it does not have any dimensions", rrdset_name(st_msq_bytes), rrdset_id(st_msq_bytes));
+ collector_info("Making chart %s (%s) obsolete since it does not have any dimensions", rrdset_name(st_msq_bytes), rrdset_id(st_msq_bytes));
rrdset_is_obsolete(st_msq_bytes);
st_msq_bytes = NULL;
}
diff --git a/collectors/proc.plugin/plugin_proc.c b/collectors/proc.plugin/plugin_proc.c
index 1b24df45..1f52713c 100644
--- a/collectors/proc.plugin/plugin_proc.c
+++ b/collectors/proc.plugin/plugin_proc.c
@@ -86,7 +86,7 @@ static void proc_main_cleanup(void *ptr)
struct netdata_static_thread *static_thread = (struct netdata_static_thread *)ptr;
static_thread->enabled = NETDATA_MAIN_THREAD_EXITING;
- info("cleaning up...");
+ collector_info("cleaning up...");
if (netdev_thread) {
netdata_thread_join(*netdev_thread, NULL);
@@ -98,6 +98,41 @@ static void proc_main_cleanup(void *ptr)
worker_unregister();
}
+bool inside_lxc_container = false;
+
+static bool is_lxcfs_proc_mounted() {
+ procfile *ff = NULL;
+
+ if (unlikely(!ff)) {
+ char filename[FILENAME_MAX + 1];
+ snprintfz(filename, FILENAME_MAX, "/proc/self/mounts");
+ ff = procfile_open(filename, " \t", PROCFILE_FLAG_DEFAULT);
+ if (unlikely(!ff))
+ return false;
+ }
+
+ ff = procfile_readall(ff);
+ if (unlikely(!ff))
+ return false;
+
+ unsigned long l, lines = procfile_lines(ff);
+
+ for (l = 0; l < lines; l++) {
+ size_t words = procfile_linewords(ff, l);
+ if (words < 2) {
+ continue;
+ }
+ if (!strcmp(procfile_lineword(ff, l, 0), "lxcfs") && !strncmp(procfile_lineword(ff, l, 1), "/proc", 5)) {
+ procfile_close(ff);
+ return true;
+ }
+ }
+
+ procfile_close(ff);
+
+ return false;
+}
+
void *proc_main(void *ptr)
{
worker_register("PROC");
@@ -128,15 +163,17 @@ void *proc_main(void *ptr)
heartbeat_t hb;
heartbeat_init(&hb);
- while (!netdata_exit) {
+ inside_lxc_container = is_lxcfs_proc_mounted();
+
+ while (service_running(SERVICE_COLLECTORS)) {
worker_is_idle();
usec_t hb_dt = heartbeat_next(&hb, step);
- if (unlikely(netdata_exit))
+ if (unlikely(!service_running(SERVICE_COLLECTORS)))
break;
for (i = 0; proc_modules[i].name; i++) {
- if (unlikely(netdata_exit))
+ if (unlikely(!service_running(SERVICE_COLLECTORS)))
break;
struct proc_module *pm = &proc_modules[i];
diff --git a/collectors/proc.plugin/plugin_proc.h b/collectors/proc.plugin/plugin_proc.h
index d67ccd6e..e8746ba3 100644
--- a/collectors/proc.plugin/plugin_proc.h
+++ b/collectors/proc.plugin/plugin_proc.h
@@ -8,7 +8,7 @@
#define PLUGIN_PROC_CONFIG_NAME "proc"
#define PLUGIN_PROC_NAME PLUGIN_PROC_CONFIG_NAME ".plugin"
-#define THREAD_NETDEV_NAME "PLUGIN[proc netdev]"
+#define THREAD_NETDEV_NAME "P[proc netdev]"
void *netdev_main(void *ptr);
int do_proc_net_wireless(int update_every, usec_t dt);
@@ -48,6 +48,7 @@ int get_numa_node_count(void);
// metrics that need to be shared among data collectors
extern unsigned long long zfs_arcstats_shrinkable_cache_size_bytes;
+extern bool inside_lxc_container;
// netdev renames
void netdev_rename_device_add(
diff --git a/collectors/proc.plugin/proc_diskstats.c b/collectors/proc.plugin/proc_diskstats.c
index 28d0e758..b487f291 100644
--- a/collectors/proc.plugin/proc_diskstats.c
+++ b/collectors/proc.plugin/proc_diskstats.c
@@ -214,7 +214,7 @@ static unsigned long long int bcache_read_number_with_units(const char *filename
else if(*end == 'T')
return (unsigned long long int)(value * 1024.0 * 1024.0 * 1024.0 * 1024.0);
else if(unknown_units_error > 0) {
- error("bcache file '%s' provides value '%s' with unknown units '%s'", filename, buffer, end);
+ collector_error("bcache file '%s' provides value '%s' with unknown units '%s'", filename, buffer, end);
unknown_units_error--;
}
}
@@ -269,7 +269,7 @@ void bcache_read_priority_stats(struct disk *d, const char *family, int update_e
for(l = 0; l < lines ;l++) {
size_t words = procfile_linewords(ff, l);
if(unlikely(words < 2)) {
- if(unlikely(words)) error("Cannot read '%s' line %zu. Expected 2 params, read %zu.", d->bcache_filename_priority_stats, l, words);
+ if(unlikely(words)) collector_error("Cannot read '%s' line %zu. Expected 2 params, read %zu.", d->bcache_filename_priority_stats, l, words);
continue;
}
@@ -344,7 +344,7 @@ static inline int is_major_enabled(int major) {
}
static inline int get_disk_name_from_path(const char *path, char *result, size_t result_size, unsigned long major, unsigned long minor, char *disk, char *prefix, int depth) {
- //info("DEVICE-MAPPER ('%s', %lu:%lu): examining directory '%s' (allowed depth %d).", disk, major, minor, path, depth);
+ //collector_info("DEVICE-MAPPER ('%s', %lu:%lu): examining directory '%s' (allowed depth %d).", disk, major, minor, path, depth);
int found = 0, preferred = 0;
@@ -352,7 +352,7 @@ static inline int get_disk_name_from_path(const char *path, char *result, size_t
DIR *dir = opendir(path);
if (!dir) {
- error("DEVICE-MAPPER ('%s', %lu:%lu): Cannot open directory '%s'.", disk, major, minor, path);
+ collector_error("DEVICE-MAPPER ('%s', %lu:%lu): Cannot open directory '%s'.", disk, major, minor, path);
goto failed;
}
@@ -363,7 +363,7 @@ static inline int get_disk_name_from_path(const char *path, char *result, size_t
continue;
if(depth <= 0) {
- error("DEVICE-MAPPER ('%s', %lu:%lu): Depth limit reached for path '%s/%s'. Ignoring path.", disk, major, minor, path, de->d_name);
+ collector_error("DEVICE-MAPPER ('%s', %lu:%lu): Depth limit reached for path '%s/%s'. Ignoring path.", disk, major, minor, path, de->d_name);
break;
}
else {
@@ -393,7 +393,7 @@ static inline int get_disk_name_from_path(const char *path, char *result, size_t
snprintfz(filename, FILENAME_MAX, "%s/%s", path, de->d_name);
ssize_t len = readlink(filename, result, result_size - 1);
if(len <= 0) {
- error("DEVICE-MAPPER ('%s', %lu:%lu): Cannot read link '%s'.", disk, major, minor, filename);
+ collector_error("DEVICE-MAPPER ('%s', %lu:%lu): Cannot read link '%s'.", disk, major, minor, filename);
continue;
}
@@ -409,21 +409,21 @@ static inline int get_disk_name_from_path(const char *path, char *result, size_t
struct stat sb;
if(stat(filename, &sb) == -1) {
- error("DEVICE-MAPPER ('%s', %lu:%lu): Cannot stat() file '%s'.", disk, major, minor, filename);
+ collector_error("DEVICE-MAPPER ('%s', %lu:%lu): Cannot stat() file '%s'.", disk, major, minor, filename);
continue;
}
if((sb.st_mode & S_IFMT) != S_IFBLK) {
- //info("DEVICE-MAPPER ('%s', %lu:%lu): file '%s' is not a block device.", disk, major, minor, filename);
+ //collector_info("DEVICE-MAPPER ('%s', %lu:%lu): file '%s' is not a block device.", disk, major, minor, filename);
continue;
}
if(major(sb.st_rdev) != major || minor(sb.st_rdev) != minor || strcmp(basename(filename), disk)) {
- //info("DEVICE-MAPPER ('%s', %lu:%lu): filename '%s' does not match %lu:%lu.", disk, major, minor, filename, (unsigned long)major(sb.st_rdev), (unsigned long)minor(sb.st_rdev));
+ //collector_info("DEVICE-MAPPER ('%s', %lu:%lu): filename '%s' does not match %lu:%lu.", disk, major, minor, filename, (unsigned long)major(sb.st_rdev), (unsigned long)minor(sb.st_rdev));
continue;
}
- //info("DEVICE-MAPPER ('%s', %lu:%lu): filename '%s' matches.", disk, major, minor, filename);
+ //collector_info("DEVICE-MAPPER ('%s', %lu:%lu): filename '%s' matches.", disk, major, minor, filename);
snprintfz(result, result_size - 1, "%s%s%s", (prefix)?prefix:"", (prefix)?"_":"", de->d_name);
@@ -672,7 +672,7 @@ static struct disk *get_disk(unsigned long major, unsigned long minor, char *dis
break;
}
if (unlikely(closedir(dirp) == -1))
- error("Unable to close dir %s", buffer);
+ collector_error("Unable to close dir %s", buffer);
}
}
}
@@ -721,15 +721,15 @@ static struct disk *get_disk(unsigned long major, unsigned long minor, char *dis
if(likely(tmp)) {
d->sector_size = str2i(tmp);
if(unlikely(d->sector_size <= 0)) {
- error("Invalid sector size %d for device %s in %s. Assuming 512.", d->sector_size, d->device, buffer);
+ collector_error("Invalid sector size %d for device %s in %s. Assuming 512.", d->sector_size, d->device, buffer);
d->sector_size = 512;
}
}
- else error("Cannot read data for sector size for device %s from %s. Assuming 512.", d->device, buffer);
+ else collector_error("Cannot read data for sector size for device %s from %s. Assuming 512.", d->device, buffer);
fclose(fpss);
}
- else error("Cannot read sector size for device %s from %s. Assuming 512.", d->device, buffer);
+ else collector_error("Cannot read sector size for device %s from %s. Assuming 512.", d->device, buffer);
}
*/
@@ -748,103 +748,103 @@ static struct disk *get_disk(unsigned long major, unsigned long minor, char *dis
if(access(buffer2, R_OK) == 0)
d->bcache_filename_cache_congested = strdupz(buffer2);
else
- error("bcache file '%s' cannot be read.", buffer2);
+ collector_error("bcache file '%s' cannot be read.", buffer2);
snprintfz(buffer2, FILENAME_MAX, "%s/readahead", buffer);
if(access(buffer2, R_OK) == 0)
d->bcache_filename_stats_total_cache_readaheads = strdupz(buffer2);
else
- error("bcache file '%s' cannot be read.", buffer2);
+ collector_error("bcache file '%s' cannot be read.", buffer2);
snprintfz(buffer2, FILENAME_MAX, "%s/cache/cache0/priority_stats", buffer); // only one cache is supported by bcache
if(access(buffer2, R_OK) == 0)
d->bcache_filename_priority_stats = strdupz(buffer2);
else
- error("bcache file '%s' cannot be read.", buffer2);
+ collector_error("bcache file '%s' cannot be read.", buffer2);
snprintfz(buffer2, FILENAME_MAX, "%s/cache/internal/cache_read_races", buffer);
if(access(buffer2, R_OK) == 0)
d->bcache_filename_cache_read_races = strdupz(buffer2);
else
- error("bcache file '%s' cannot be read.", buffer2);
+ collector_error("bcache file '%s' cannot be read.", buffer2);
snprintfz(buffer2, FILENAME_MAX, "%s/cache/cache0/io_errors", buffer);
if(access(buffer2, R_OK) == 0)
d->bcache_filename_cache_io_errors = strdupz(buffer2);
else
- error("bcache file '%s' cannot be read.", buffer2);
+ collector_error("bcache file '%s' cannot be read.", buffer2);
snprintfz(buffer2, FILENAME_MAX, "%s/dirty_data", buffer);
if(access(buffer2, R_OK) == 0)
d->bcache_filename_dirty_data = strdupz(buffer2);
else
- error("bcache file '%s' cannot be read.", buffer2);
+ collector_error("bcache file '%s' cannot be read.", buffer2);
snprintfz(buffer2, FILENAME_MAX, "%s/writeback_rate", buffer);
if(access(buffer2, R_OK) == 0)
d->bcache_filename_writeback_rate = strdupz(buffer2);
else
- error("bcache file '%s' cannot be read.", buffer2);
+ collector_error("bcache file '%s' cannot be read.", buffer2);
snprintfz(buffer2, FILENAME_MAX, "%s/cache/cache_available_percent", buffer);
if(access(buffer2, R_OK) == 0)
d->bcache_filename_cache_available_percent = strdupz(buffer2);
else
- error("bcache file '%s' cannot be read.", buffer2);
+ collector_error("bcache file '%s' cannot be read.", buffer2);
snprintfz(buffer2, FILENAME_MAX, "%s/stats_total/cache_hits", buffer);
if(access(buffer2, R_OK) == 0)
d->bcache_filename_stats_total_cache_hits = strdupz(buffer2);
else
- error("bcache file '%s' cannot be read.", buffer2);
+ collector_error("bcache file '%s' cannot be read.", buffer2);
snprintfz(buffer2, FILENAME_MAX, "%s/stats_five_minute/cache_hit_ratio", buffer);
if(access(buffer2, R_OK) == 0)
d->bcache_filename_stats_five_minute_cache_hit_ratio = strdupz(buffer2);
else
- error("bcache file '%s' cannot be read.", buffer2);
+ collector_error("bcache file '%s' cannot be read.", buffer2);
snprintfz(buffer2, FILENAME_MAX, "%s/stats_hour/cache_hit_ratio", buffer);
if(access(buffer2, R_OK) == 0)
d->bcache_filename_stats_hour_cache_hit_ratio = strdupz(buffer2);
else
- error("bcache file '%s' cannot be read.", buffer2);
+ collector_error("bcache file '%s' cannot be read.", buffer2);
snprintfz(buffer2, FILENAME_MAX, "%s/stats_day/cache_hit_ratio", buffer);
if(access(buffer2, R_OK) == 0)
d->bcache_filename_stats_day_cache_hit_ratio = strdupz(buffer2);
else
- error("bcache file '%s' cannot be read.", buffer2);
+ collector_error("bcache file '%s' cannot be read.", buffer2);
snprintfz(buffer2, FILENAME_MAX, "%s/stats_total/cache_hit_ratio", buffer);
if(access(buffer2, R_OK) == 0)
d->bcache_filename_stats_total_cache_hit_ratio = strdupz(buffer2);
else
- error("bcache file '%s' cannot be read.", buffer2);
+ collector_error("bcache file '%s' cannot be read.", buffer2);
snprintfz(buffer2, FILENAME_MAX, "%s/stats_total/cache_misses", buffer);
if(access(buffer2, R_OK) == 0)
d->bcache_filename_stats_total_cache_misses = strdupz(buffer2);
else
- error("bcache file '%s' cannot be read.", buffer2);
+ collector_error("bcache file '%s' cannot be read.", buffer2);
snprintfz(buffer2, FILENAME_MAX, "%s/stats_total/cache_bypass_hits", buffer);
if(access(buffer2, R_OK) == 0)
d->bcache_filename_stats_total_cache_bypass_hits = strdupz(buffer2);
else
- error("bcache file '%s' cannot be read.", buffer2);
+ collector_error("bcache file '%s' cannot be read.", buffer2);
snprintfz(buffer2, FILENAME_MAX, "%s/stats_total/cache_bypass_misses", buffer);
if(access(buffer2, R_OK) == 0)
d->bcache_filename_stats_total_cache_bypass_misses = strdupz(buffer2);
else
- error("bcache file '%s' cannot be read.", buffer2);
+ collector_error("bcache file '%s' cannot be read.", buffer2);
snprintfz(buffer2, FILENAME_MAX, "%s/stats_total/cache_miss_collisions", buffer);
if(access(buffer2, R_OK) == 0)
d->bcache_filename_stats_total_cache_miss_collisions = strdupz(buffer2);
else
- error("bcache file '%s' cannot be read.", buffer2);
+ collector_error("bcache file '%s' cannot be read.", buffer2);
}
get_disk_config(d);
diff --git a/collectors/proc.plugin/proc_interrupts.c b/collectors/proc.plugin/proc_interrupts.c
index f8768475..04d8c73a 100644
--- a/collectors/proc.plugin/proc_interrupts.c
+++ b/collectors/proc.plugin/proc_interrupts.c
@@ -78,7 +78,7 @@ int do_proc_interrupts(int update_every, usec_t dt) {
size_t words = procfile_linewords(ff, 0);
if(unlikely(!lines)) {
- error("Cannot read /proc/interrupts, zero lines reported.");
+ collector_error("Cannot read /proc/interrupts, zero lines reported.");
return 1;
}
@@ -93,7 +93,7 @@ int do_proc_interrupts(int update_every, usec_t dt) {
}
if(unlikely(!cpus)) {
- error("PLUGIN: PROC_INTERRUPTS: Cannot find the number of CPUs in /proc/interrupts");
+ collector_error("PLUGIN: PROC_INTERRUPTS: Cannot find the number of CPUs in /proc/interrupts");
return 1;
}
diff --git a/collectors/proc.plugin/proc_loadavg.c b/collectors/proc.plugin/proc_loadavg.c
index d928c861..e833f69d 100644
--- a/collectors/proc.plugin/proc_loadavg.c
+++ b/collectors/proc.plugin/proc_loadavg.c
@@ -32,11 +32,11 @@ int do_proc_loadavg(int update_every, usec_t dt) {
}
if(unlikely(procfile_lines(ff) < 1)) {
- error("/proc/loadavg has no lines.");
+ collector_error("/proc/loadavg has no lines.");
return 1;
}
if(unlikely(procfile_linewords(ff, 0) < 6)) {
- error("/proc/loadavg has less than 6 words in it.");
+ collector_error("/proc/loadavg has less than 6 words in it.");
return 1;
}
diff --git a/collectors/proc.plugin/proc_mdstat.c b/collectors/proc.plugin/proc_mdstat.c
index 63e0c68e..d6e87fd2 100644
--- a/collectors/proc.plugin/proc_mdstat.c
+++ b/collectors/proc.plugin/proc_mdstat.c
@@ -135,7 +135,7 @@ int do_proc_mdstat(int update_every, usec_t dt)
size_t words = 0;
if (unlikely(lines < 2)) {
- error("Cannot read /proc/mdstat. Expected 2 or more lines, read %zu.", lines);
+ collector_error("Cannot read /proc/mdstat. Expected 2 or more lines, read %zu.", lines);
return 1;
}
@@ -212,7 +212,7 @@ int do_proc_mdstat(int update_every, usec_t dt)
s = procfile_lineword(ff, l, words - 2);
if (unlikely(s[0] != '[')) {
- error("Cannot read /proc/mdstat raid health status. Unexpected format: missing opening bracket.");
+ collector_error("Cannot read /proc/mdstat raid health status. Unexpected format: missing opening bracket.");
continue;
}
str_total = ++s;
@@ -227,7 +227,7 @@ int do_proc_mdstat(int update_every, usec_t dt)
s++;
}
if (unlikely(str_total[0] == '\0' || !str_inuse || str_inuse[0] == '\0')) {
- error("Cannot read /proc/mdstat raid health status. Unexpected format.");
+ collector_error("Cannot read /proc/mdstat raid health status. Unexpected format.");
continue;
}
@@ -260,7 +260,7 @@ int do_proc_mdstat(int update_every, usec_t dt)
continue;
if (unlikely(words < 7)) {
- error("Cannot read /proc/mdstat line. Expected 7 params, read %zu.", words);
+ collector_error("Cannot read /proc/mdstat line. Expected 7 params, read %zu.", words);
continue;
}
@@ -326,9 +326,9 @@ int do_proc_mdstat(int update_every, usec_t dt)
raid->mismatch_cnt_filename = strdupz(filename);
}
if (unlikely(read_single_number_file(raid->mismatch_cnt_filename, &raid->mismatch_cnt))) {
- error("Cannot read file '%s'", raid->mismatch_cnt_filename);
+ collector_error("Cannot read file '%s'", raid->mismatch_cnt_filename);
do_mismatch = CONFIG_BOOLEAN_NO;
- error("Monitoring for mismatch count has been disabled");
+ collector_error("Monitoring for mismatch count has been disabled");
break;
}
}
diff --git a/collectors/proc.plugin/proc_meminfo.c b/collectors/proc.plugin/proc_meminfo.c
index 2f390c65..6988c70e 100644
--- a/collectors/proc.plugin/proc_meminfo.c
+++ b/collectors/proc.plugin/proc_meminfo.c
@@ -158,9 +158,11 @@ int do_proc_meminfo(int update_every, usec_t dt) {
unsigned long long MemCached = Cached + SReclaimable - Shmem;
unsigned long long MemUsed = MemTotal - MemFree - MemCached - Buffers;
// The Linux kernel doesn't report ZFS ARC usage as cache memory (the ARC is included in the total used system memory)
- MemCached += (zfs_arcstats_shrinkable_cache_size_bytes / 1024);
- MemUsed -= (zfs_arcstats_shrinkable_cache_size_bytes / 1024);
- MemAvailable += (zfs_arcstats_shrinkable_cache_size_bytes / 1024);
+ if (!inside_lxc_container) {
+ MemCached += (zfs_arcstats_shrinkable_cache_size_bytes / 1024);
+ MemUsed -= (zfs_arcstats_shrinkable_cache_size_bytes / 1024);
+ MemAvailable += (zfs_arcstats_shrinkable_cache_size_bytes / 1024);
+ }
if(do_ram) {
{
diff --git a/collectors/proc.plugin/proc_net_dev.c b/collectors/proc.plugin/proc_net_dev.c
index e124f631..3ec8783b 100644
--- a/collectors/proc.plugin/proc_net_dev.c
+++ b/collectors/proc.plugin/proc_net_dev.c
@@ -391,7 +391,7 @@ void netdev_rename_device_add(
r->processed = 0;
netdev_rename_root = r;
netdev_pending_renames++;
- info("CGROUP: registered network interface rename for '%s' as '%s' under '%s'", r->host_device, r->container_device, r->container_name);
+ collector_info("CGROUP: registered network interface rename for '%s' as '%s' under '%s'", r->host_device, r->container_device, r->container_name);
}
else {
if(strcmp(r->container_device, container_device) != 0 || strcmp(r->container_name, container_name) != 0) {
@@ -405,7 +405,7 @@ void netdev_rename_device_add(
r->processed = 0;
netdev_pending_renames++;
- info("CGROUP: altered network interface rename for '%s' as '%s' under '%s'", r->host_device, r->container_device, r->container_name);
+ collector_info("CGROUP: altered network interface rename for '%s' as '%s' under '%s'", r->host_device, r->container_device, r->container_name);
}
}
@@ -429,7 +429,7 @@ void netdev_rename_device_del(const char *host_device) {
if(!r->processed)
netdev_pending_renames--;
- info("CGROUP: unregistered network interface rename for '%s' as '%s' under '%s'", r->host_device, r->container_device, r->container_name);
+ collector_info("CGROUP: unregistered network interface rename for '%s' as '%s' under '%s'", r->host_device, r->container_device, r->container_name);
freez((void *) r->host_device);
freez((void *) r->container_name);
@@ -445,7 +445,7 @@ void netdev_rename_device_del(const char *host_device) {
}
static inline void netdev_rename_cgroup(struct netdev *d, struct netdev_rename *r) {
- info("CGROUP: renaming network interface '%s' as '%s' under '%s'", r->host_device, r->container_device, r->container_name);
+ collector_info("CGROUP: renaming network interface '%s' as '%s' under '%s'", r->host_device, r->container_device, r->container_name);
netdev_charts_release(d);
netdev_free_chart_strings(d);
@@ -516,8 +516,7 @@ static inline void netdev_rename_cgroup(struct netdev *d, struct netdev_rename *
snprintfz(buffer, RRD_ID_LENGTH_MAX, "%scgroup.net_mtu", r->ctx_prefix);
d->chart_ctx_net_mtu = strdupz(buffer);
- snprintfz(buffer, RRD_ID_LENGTH_MAX, "net %s", r->container_device);
- d->chart_family = strdupz(buffer);
+ d->chart_family = strdupz("net");
rrdlabels_copy(d->chart_labels, r->chart_labels);
@@ -561,7 +560,7 @@ static void netdev_cleanup() {
struct netdev *d = netdev_root, *last = NULL;
while(d) {
if(unlikely(!d->updated)) {
- // info("Removing network device '%s', linked after '%s'", d->name, last?last->name:"ROOT");
+ // collector_info("Removing network device '%s', linked after '%s'", d->name, last?last->name:"ROOT");
if(netdev_last_used == d)
netdev_last_used = last;
@@ -875,7 +874,7 @@ int do_proc_net_dev(int update_every, usec_t dt) {
now_monotonic_sec() - d->carrier_file_lost_time > READ_RETRY_PERIOD)) {
if (read_single_number_file(d->filename_carrier, &d->carrier)) {
if (d->carrier_file_exists)
- error(
+ collector_error(
"Cannot refresh interface %s carrier state by reading '%s'. Next update is in %d seconds.",
d->name,
d->filename_carrier,
@@ -897,7 +896,7 @@ int do_proc_net_dev(int update_every, usec_t dt) {
if (read_file(d->filename_duplex, buffer, STATE_LENGTH_MAX)) {
if (d->duplex_file_exists)
- error("Cannot refresh interface %s duplex state by reading '%s'.", d->name, d->filename_duplex);
+ collector_error("Cannot refresh interface %s duplex state by reading '%s'.", d->name, d->filename_duplex);
d->duplex_file_exists = 0;
d->duplex_file_lost_time = now_monotonic_sec();
d->duplex = NETDEV_DUPLEX_UNKNOWN;
@@ -920,7 +919,7 @@ int do_proc_net_dev(int update_every, usec_t dt) {
char buffer[STATE_LENGTH_MAX + 1], *trimmed_buffer;
if (read_file(d->filename_operstate, buffer, STATE_LENGTH_MAX)) {
- error(
+ collector_error(
"Cannot refresh %s operstate by reading '%s'. Will not update its status anymore.",
d->name, d->filename_operstate);
freez(d->filename_operstate);
@@ -933,14 +932,14 @@ int do_proc_net_dev(int update_every, usec_t dt) {
if (d->do_mtu != CONFIG_BOOLEAN_NO && d->filename_mtu) {
if (read_single_number_file(d->filename_mtu, &d->mtu)) {
- error(
+ collector_error(
"Cannot refresh mtu for interface %s by reading '%s'. Stop updating it.", d->name, d->filename_mtu);
freez(d->filename_mtu);
d->filename_mtu = NULL;
}
}
- //info("PROC_NET_DEV: %s speed %zu, bytes %zu/%zu, packets %zu/%zu/%zu, errors %zu/%zu, drops %zu/%zu, fifo %zu/%zu, compressed %zu/%zu, rframe %zu, tcollisions %zu, tcarrier %zu"
+ //collector_info("PROC_NET_DEV: %s speed %zu, bytes %zu/%zu, packets %zu/%zu/%zu, errors %zu/%zu, drops %zu/%zu, fifo %zu/%zu, compressed %zu/%zu, rframe %zu, tcollisions %zu, tcarrier %zu"
// , d->name, d->speed
// , d->rbytes, d->tbytes
// , d->rpackets, d->tpackets, d->rmulticast
@@ -997,7 +996,7 @@ int do_proc_net_dev(int update_every, usec_t dt) {
d->chart_var_speed =
rrdsetvar_custom_chart_variable_add_and_acquire(d->st_bandwidth, "nic_speed_max");
if(!d->chart_var_speed) {
- error(
+ collector_error(
"Cannot create interface %s chart variable 'nic_speed_max'. Will not update its speed anymore.",
d->name);
freez(d->filename_speed);
@@ -1017,7 +1016,7 @@ int do_proc_net_dev(int update_every, usec_t dt) {
if(ret) {
if (d->speed_file_exists)
- error("Cannot refresh interface %s speed by reading '%s'.", d->name, d->filename_speed);
+ collector_error("Cannot refresh interface %s speed by reading '%s'.", d->name, d->filename_speed);
d->speed_file_exists = 0;
d->speed_file_lost_time = now_monotonic_sec();
}
@@ -1489,7 +1488,7 @@ static void netdev_main_cleanup(void *ptr)
{
UNUSED(ptr);
- info("cleaning up...");
+ collector_info("cleaning up...");
worker_unregister();
}
@@ -1505,11 +1504,11 @@ void *netdev_main(void *ptr)
heartbeat_t hb;
heartbeat_init(&hb);
- while (!netdata_exit) {
+ while (service_running(SERVICE_COLLECTORS)) {
worker_is_idle();
usec_t hb_dt = heartbeat_next(&hb, step);
- if (unlikely(netdata_exit))
+ if (unlikely(!service_running(SERVICE_COLLECTORS)))
break;
worker_is_busy(0);
diff --git a/collectors/proc.plugin/proc_net_netstat.c b/collectors/proc.plugin/proc_net_netstat.c
index f7635e3d..ce3068c0 100644
--- a/collectors/proc.plugin/proc_net_netstat.c
+++ b/collectors/proc.plugin/proc_net_netstat.c
@@ -97,7 +97,7 @@ static void parse_line_pair(procfile *ff_netstat, ARL_BASE *base, size_t header_
size_t w;
if(unlikely(vwords > hwords)) {
- error("File /proc/net/netstat on header line %zu has %zu words, but on value line %zu has %zu words.", header_line, hwords, values_line, vwords);
+ collector_error("File /proc/net/netstat on header line %zu has %zu words, but on value line %zu has %zu words.", header_line, hwords, values_line, vwords);
vwords = hwords;
}
@@ -107,16 +107,12 @@ static void parse_line_pair(procfile *ff_netstat, ARL_BASE *base, size_t header_
}
}
-int do_proc_net_netstat(int update_every, usec_t dt) {
- (void)dt;
-
- static int do_bandwidth = -1, do_inerrors = -1, do_mcast = -1, do_bcast = -1, do_mcast_p = -1, do_bcast_p = -1, do_ecn = -1, \
- do_tcpext_reorder = -1, do_tcpext_syscookies = -1, do_tcpext_ofo = -1, do_tcpext_connaborts = -1, do_tcpext_memory = -1,
- do_tcpext_syn_queue = -1, do_tcpext_accept_queue = -1;
+static void do_proc_net_snmp6(int update_every) {
+ static bool do_snmp6 = true;
- static int do_ip_packets = -1, do_ip_fragsout = -1, do_ip_fragsin = -1, do_ip_errors = -1,
- do_tcp_sockets = -1, do_tcp_packets = -1, do_tcp_errors = -1, do_tcp_handshake = -1, do_tcp_opens = -1,
- do_udp_packets = -1, do_udp_errors = -1, do_icmp_packets = -1, do_icmpmsg = -1, do_udplite_packets = -1;
+ if (!do_snmp6) {
+ return;
+ }
static int do_ip6_packets = -1, do_ip6_fragsout = -1, do_ip6_fragsin = -1, do_ip6_errors = -1,
do_ip6_udplite_packets = -1, do_ip6_udplite_errors = -1, do_ip6_udp_packets = -1, do_ip6_udp_errors = -1,
@@ -125,104 +121,10 @@ int do_proc_net_netstat(int update_every, usec_t dt) {
do_ip6_icmp_router = -1, do_ip6_icmp_neighbor = -1, do_ip6_icmp_mldv2 = -1, do_ip6_icmp_types = -1,
do_ip6_ect = -1;
- static uint32_t hash_ipext = 0, hash_tcpext = 0;
- static uint32_t hash_ip = 0, hash_icmp = 0, hash_tcp = 0, hash_udp = 0, hash_icmpmsg = 0, hash_udplite = 0;
-
- static procfile *ff_netstat = NULL;
- static procfile *ff_snmp = NULL;
static procfile *ff_snmp6 = NULL;
- static ARL_BASE *arl_tcpext = NULL;
- static ARL_BASE *arl_ipext = NULL;
-
- static ARL_BASE *arl_ip = NULL;
- static ARL_BASE *arl_icmp = NULL;
- static ARL_BASE *arl_icmpmsg = NULL;
- static ARL_BASE *arl_tcp = NULL;
- static ARL_BASE *arl_udp = NULL;
- static ARL_BASE *arl_udplite = NULL;
-
static ARL_BASE *arl_ipv6 = NULL;
- static const RRDVAR_ACQUIRED *tcp_max_connections_var = NULL;
-
- // --------------------------------------------------------------------
- // IP
-
- // IP bandwidth
- static unsigned long long ipext_InOctets = 0;
- static unsigned long long ipext_OutOctets = 0;
-
- // IP input errors
- static unsigned long long ipext_InNoRoutes = 0;
- static unsigned long long ipext_InTruncatedPkts = 0;
- static unsigned long long ipext_InCsumErrors = 0;
-
- // IP multicast bandwidth
- static unsigned long long ipext_InMcastOctets = 0;
- static unsigned long long ipext_OutMcastOctets = 0;
-
- // IP multicast packets
- static unsigned long long ipext_InMcastPkts = 0;
- static unsigned long long ipext_OutMcastPkts = 0;
-
- // IP broadcast bandwidth
- static unsigned long long ipext_InBcastOctets = 0;
- static unsigned long long ipext_OutBcastOctets = 0;
-
- // IP broadcast packets
- static unsigned long long ipext_InBcastPkts = 0;
- static unsigned long long ipext_OutBcastPkts = 0;
-
- // IP ECN
- static unsigned long long ipext_InNoECTPkts = 0;
- static unsigned long long ipext_InECT1Pkts = 0;
- static unsigned long long ipext_InECT0Pkts = 0;
- static unsigned long long ipext_InCEPkts = 0;
-
- // --------------------------------------------------------------------
- // IP TCP
-
- // IP TCP Reordering
- static unsigned long long tcpext_TCPRenoReorder = 0;
- static unsigned long long tcpext_TCPFACKReorder = 0;
- static unsigned long long tcpext_TCPSACKReorder = 0;
- static unsigned long long tcpext_TCPTSReorder = 0;
-
- // IP TCP SYN Cookies
- static unsigned long long tcpext_SyncookiesSent = 0;
- static unsigned long long tcpext_SyncookiesRecv = 0;
- static unsigned long long tcpext_SyncookiesFailed = 0;
-
- // IP TCP Out Of Order Queue
- // http://www.spinics.net/lists/netdev/msg204696.html
- static unsigned long long tcpext_TCPOFOQueue = 0; // Number of packets queued in OFO queue
- static unsigned long long tcpext_TCPOFODrop = 0; // Number of packets meant to be queued in OFO but dropped because socket rcvbuf limit hit.
- static unsigned long long tcpext_TCPOFOMerge = 0; // Number of packets in OFO that were merged with other packets.
- static unsigned long long tcpext_OfoPruned = 0; // packets dropped from out-of-order queue because of socket buffer overrun
-
- // IP TCP connection resets
- // https://github.com/ecki/net-tools/blob/bd8bceaed2311651710331a7f8990c3e31be9840/statistics.c
- static unsigned long long tcpext_TCPAbortOnData = 0; // connections reset due to unexpected data
- static unsigned long long tcpext_TCPAbortOnClose = 0; // connections reset due to early user close
- static unsigned long long tcpext_TCPAbortOnMemory = 0; // connections aborted due to memory pressure
- static unsigned long long tcpext_TCPAbortOnTimeout = 0; // connections aborted due to timeout
- static unsigned long long tcpext_TCPAbortOnLinger = 0; // connections aborted after user close in linger timeout
- static unsigned long long tcpext_TCPAbortFailed = 0; // times unable to send RST due to no memory
-
- // https://perfchron.com/2015/12/26/investigating-linux-network-issues-with-netstat-and-nstat/
- static unsigned long long tcpext_ListenOverflows = 0; // times the listen queue of a socket overflowed
- static unsigned long long tcpext_ListenDrops = 0; // SYNs to LISTEN sockets ignored
-
- // IP TCP memory pressures
- static unsigned long long tcpext_TCPMemoryPressures = 0;
-
- static unsigned long long tcpext_TCPReqQFullDrop = 0;
- static unsigned long long tcpext_TCPReqQFullDoCookies = 0;
-
- static unsigned long long tcpext_TCPSynRetrans = 0;
-
- // IPv6
static unsigned long long Ip6InReceives = 0ULL;
static unsigned long long Ip6InHdrErrors = 0ULL;
static unsigned long long Ip6InTooBigErrors = 0ULL;
@@ -316,6 +218,1207 @@ int do_proc_net_netstat(int update_every, usec_t dt) {
static unsigned long long UdpLite6SndbufErrors = 0ULL;
static unsigned long long UdpLite6InCsumErrors = 0ULL;
+ // prepare for /proc/net/snmp6 parsing
+
+ if(unlikely(!arl_ipv6)) {
+ do_ip6_packets = config_get_boolean_ondemand("plugin:proc:/proc/net/snmp6", "ipv6 packets", CONFIG_BOOLEAN_AUTO);
+ do_ip6_fragsout = config_get_boolean_ondemand("plugin:proc:/proc/net/snmp6", "ipv6 fragments sent", CONFIG_BOOLEAN_AUTO);
+ do_ip6_fragsin = config_get_boolean_ondemand("plugin:proc:/proc/net/snmp6", "ipv6 fragments assembly", CONFIG_BOOLEAN_AUTO);
+ do_ip6_errors = config_get_boolean_ondemand("plugin:proc:/proc/net/snmp6", "ipv6 errors", CONFIG_BOOLEAN_AUTO);
+ do_ip6_udp_packets = config_get_boolean_ondemand("plugin:proc:/proc/net/snmp6", "ipv6 UDP packets", CONFIG_BOOLEAN_AUTO);
+ do_ip6_udp_errors = config_get_boolean_ondemand("plugin:proc:/proc/net/snmp6", "ipv6 UDP errors", CONFIG_BOOLEAN_AUTO);
+ do_ip6_udplite_packets = config_get_boolean_ondemand("plugin:proc:/proc/net/snmp6", "ipv6 UDPlite packets", CONFIG_BOOLEAN_AUTO);
+ do_ip6_udplite_errors = config_get_boolean_ondemand("plugin:proc:/proc/net/snmp6", "ipv6 UDPlite errors", CONFIG_BOOLEAN_AUTO);
+ do_ip6_bandwidth = config_get_boolean_ondemand("plugin:proc:/proc/net/snmp6", "bandwidth", CONFIG_BOOLEAN_AUTO);
+ do_ip6_mcast = config_get_boolean_ondemand("plugin:proc:/proc/net/snmp6", "multicast bandwidth", CONFIG_BOOLEAN_AUTO);
+ do_ip6_bcast = config_get_boolean_ondemand("plugin:proc:/proc/net/snmp6", "broadcast bandwidth", CONFIG_BOOLEAN_AUTO);
+ do_ip6_mcast_p = config_get_boolean_ondemand("plugin:proc:/proc/net/snmp6", "multicast packets", CONFIG_BOOLEAN_AUTO);
+ do_ip6_icmp = config_get_boolean_ondemand("plugin:proc:/proc/net/snmp6", "icmp", CONFIG_BOOLEAN_AUTO);
+ do_ip6_icmp_redir = config_get_boolean_ondemand("plugin:proc:/proc/net/snmp6", "icmp redirects", CONFIG_BOOLEAN_AUTO);
+ do_ip6_icmp_errors = config_get_boolean_ondemand("plugin:proc:/proc/net/snmp6", "icmp errors", CONFIG_BOOLEAN_AUTO);
+ do_ip6_icmp_echos = config_get_boolean_ondemand("plugin:proc:/proc/net/snmp6", "icmp echos", CONFIG_BOOLEAN_AUTO);
+ do_ip6_icmp_groupmemb = config_get_boolean_ondemand("plugin:proc:/proc/net/snmp6", "icmp group membership", CONFIG_BOOLEAN_AUTO);
+ do_ip6_icmp_router = config_get_boolean_ondemand("plugin:proc:/proc/net/snmp6", "icmp router", CONFIG_BOOLEAN_AUTO);
+ do_ip6_icmp_neighbor = config_get_boolean_ondemand("plugin:proc:/proc/net/snmp6", "icmp neighbor", CONFIG_BOOLEAN_AUTO);
+ do_ip6_icmp_mldv2 = config_get_boolean_ondemand("plugin:proc:/proc/net/snmp6", "icmp mldv2", CONFIG_BOOLEAN_AUTO);
+ do_ip6_icmp_types = config_get_boolean_ondemand("plugin:proc:/proc/net/snmp6", "icmp types", CONFIG_BOOLEAN_AUTO);
+ do_ip6_ect = config_get_boolean_ondemand("plugin:proc:/proc/net/snmp6", "ect", CONFIG_BOOLEAN_AUTO);
+
+ arl_ipv6 = arl_create("snmp6", NULL, 60);
+ arl_expect(arl_ipv6, "Ip6InReceives", &Ip6InReceives);
+ arl_expect(arl_ipv6, "Ip6InHdrErrors", &Ip6InHdrErrors);
+ arl_expect(arl_ipv6, "Ip6InTooBigErrors", &Ip6InTooBigErrors);
+ arl_expect(arl_ipv6, "Ip6InNoRoutes", &Ip6InNoRoutes);
+ arl_expect(arl_ipv6, "Ip6InAddrErrors", &Ip6InAddrErrors);
+ arl_expect(arl_ipv6, "Ip6InUnknownProtos", &Ip6InUnknownProtos);
+ arl_expect(arl_ipv6, "Ip6InTruncatedPkts", &Ip6InTruncatedPkts);
+ arl_expect(arl_ipv6, "Ip6InDiscards", &Ip6InDiscards);
+ arl_expect(arl_ipv6, "Ip6InDelivers", &Ip6InDelivers);
+ arl_expect(arl_ipv6, "Ip6OutForwDatagrams", &Ip6OutForwDatagrams);
+ arl_expect(arl_ipv6, "Ip6OutRequests", &Ip6OutRequests);
+ arl_expect(arl_ipv6, "Ip6OutDiscards", &Ip6OutDiscards);
+ arl_expect(arl_ipv6, "Ip6OutNoRoutes", &Ip6OutNoRoutes);
+ arl_expect(arl_ipv6, "Ip6ReasmTimeout", &Ip6ReasmTimeout);
+ arl_expect(arl_ipv6, "Ip6ReasmReqds", &Ip6ReasmReqds);
+ arl_expect(arl_ipv6, "Ip6ReasmOKs", &Ip6ReasmOKs);
+ arl_expect(arl_ipv6, "Ip6ReasmFails", &Ip6ReasmFails);
+ arl_expect(arl_ipv6, "Ip6FragOKs", &Ip6FragOKs);
+ arl_expect(arl_ipv6, "Ip6FragFails", &Ip6FragFails);
+ arl_expect(arl_ipv6, "Ip6FragCreates", &Ip6FragCreates);
+ arl_expect(arl_ipv6, "Ip6InMcastPkts", &Ip6InMcastPkts);
+ arl_expect(arl_ipv6, "Ip6OutMcastPkts", &Ip6OutMcastPkts);
+ arl_expect(arl_ipv6, "Ip6InOctets", &Ip6InOctets);
+ arl_expect(arl_ipv6, "Ip6OutOctets", &Ip6OutOctets);
+ arl_expect(arl_ipv6, "Ip6InMcastOctets", &Ip6InMcastOctets);
+ arl_expect(arl_ipv6, "Ip6OutMcastOctets", &Ip6OutMcastOctets);
+ arl_expect(arl_ipv6, "Ip6InBcastOctets", &Ip6InBcastOctets);
+ arl_expect(arl_ipv6, "Ip6OutBcastOctets", &Ip6OutBcastOctets);
+ arl_expect(arl_ipv6, "Ip6InNoECTPkts", &Ip6InNoECTPkts);
+ arl_expect(arl_ipv6, "Ip6InECT1Pkts", &Ip6InECT1Pkts);
+ arl_expect(arl_ipv6, "Ip6InECT0Pkts", &Ip6InECT0Pkts);
+ arl_expect(arl_ipv6, "Ip6InCEPkts", &Ip6InCEPkts);
+ arl_expect(arl_ipv6, "Icmp6InMsgs", &Icmp6InMsgs);
+ arl_expect(arl_ipv6, "Icmp6InErrors", &Icmp6InErrors);
+ arl_expect(arl_ipv6, "Icmp6OutMsgs", &Icmp6OutMsgs);
+ arl_expect(arl_ipv6, "Icmp6OutErrors", &Icmp6OutErrors);
+ arl_expect(arl_ipv6, "Icmp6InCsumErrors", &Icmp6InCsumErrors);
+ arl_expect(arl_ipv6, "Icmp6InDestUnreachs", &Icmp6InDestUnreachs);
+ arl_expect(arl_ipv6, "Icmp6InPktTooBigs", &Icmp6InPktTooBigs);
+ arl_expect(arl_ipv6, "Icmp6InTimeExcds", &Icmp6InTimeExcds);
+ arl_expect(arl_ipv6, "Icmp6InParmProblems", &Icmp6InParmProblems);
+ arl_expect(arl_ipv6, "Icmp6InEchos", &Icmp6InEchos);
+ arl_expect(arl_ipv6, "Icmp6InEchoReplies", &Icmp6InEchoReplies);
+ arl_expect(arl_ipv6, "Icmp6InGroupMembQueries", &Icmp6InGroupMembQueries);
+ arl_expect(arl_ipv6, "Icmp6InGroupMembResponses", &Icmp6InGroupMembResponses);
+ arl_expect(arl_ipv6, "Icmp6InGroupMembReductions", &Icmp6InGroupMembReductions);
+ arl_expect(arl_ipv6, "Icmp6InRouterSolicits", &Icmp6InRouterSolicits);
+ arl_expect(arl_ipv6, "Icmp6InRouterAdvertisements", &Icmp6InRouterAdvertisements);
+ arl_expect(arl_ipv6, "Icmp6InNeighborSolicits", &Icmp6InNeighborSolicits);
+ arl_expect(arl_ipv6, "Icmp6InNeighborAdvertisements", &Icmp6InNeighborAdvertisements);
+ arl_expect(arl_ipv6, "Icmp6InRedirects", &Icmp6InRedirects);
+ arl_expect(arl_ipv6, "Icmp6InMLDv2Reports", &Icmp6InMLDv2Reports);
+ arl_expect(arl_ipv6, "Icmp6OutDestUnreachs", &Icmp6OutDestUnreachs);
+ arl_expect(arl_ipv6, "Icmp6OutPktTooBigs", &Icmp6OutPktTooBigs);
+ arl_expect(arl_ipv6, "Icmp6OutTimeExcds", &Icmp6OutTimeExcds);
+ arl_expect(arl_ipv6, "Icmp6OutParmProblems", &Icmp6OutParmProblems);
+ arl_expect(arl_ipv6, "Icmp6OutEchos", &Icmp6OutEchos);
+ arl_expect(arl_ipv6, "Icmp6OutEchoReplies", &Icmp6OutEchoReplies);
+ arl_expect(arl_ipv6, "Icmp6OutGroupMembQueries", &Icmp6OutGroupMembQueries);
+ arl_expect(arl_ipv6, "Icmp6OutGroupMembResponses", &Icmp6OutGroupMembResponses);
+ arl_expect(arl_ipv6, "Icmp6OutGroupMembReductions", &Icmp6OutGroupMembReductions);
+ arl_expect(arl_ipv6, "Icmp6OutRouterSolicits", &Icmp6OutRouterSolicits);
+ arl_expect(arl_ipv6, "Icmp6OutRouterAdvertisements", &Icmp6OutRouterAdvertisements);
+ arl_expect(arl_ipv6, "Icmp6OutNeighborSolicits", &Icmp6OutNeighborSolicits);
+ arl_expect(arl_ipv6, "Icmp6OutNeighborAdvertisements", &Icmp6OutNeighborAdvertisements);
+ arl_expect(arl_ipv6, "Icmp6OutRedirects", &Icmp6OutRedirects);
+ arl_expect(arl_ipv6, "Icmp6OutMLDv2Reports", &Icmp6OutMLDv2Reports);
+ arl_expect(arl_ipv6, "Icmp6InType1", &Icmp6InType1);
+ arl_expect(arl_ipv6, "Icmp6InType128", &Icmp6InType128);
+ arl_expect(arl_ipv6, "Icmp6InType129", &Icmp6InType129);
+ arl_expect(arl_ipv6, "Icmp6InType136", &Icmp6InType136);
+ arl_expect(arl_ipv6, "Icmp6OutType1", &Icmp6OutType1);
+ arl_expect(arl_ipv6, "Icmp6OutType128", &Icmp6OutType128);
+ arl_expect(arl_ipv6, "Icmp6OutType129", &Icmp6OutType129);
+ arl_expect(arl_ipv6, "Icmp6OutType133", &Icmp6OutType133);
+ arl_expect(arl_ipv6, "Icmp6OutType135", &Icmp6OutType135);
+ arl_expect(arl_ipv6, "Icmp6OutType143", &Icmp6OutType143);
+ arl_expect(arl_ipv6, "Udp6InDatagrams", &Udp6InDatagrams);
+ arl_expect(arl_ipv6, "Udp6NoPorts", &Udp6NoPorts);
+ arl_expect(arl_ipv6, "Udp6InErrors", &Udp6InErrors);
+ arl_expect(arl_ipv6, "Udp6OutDatagrams", &Udp6OutDatagrams);
+ arl_expect(arl_ipv6, "Udp6RcvbufErrors", &Udp6RcvbufErrors);
+ arl_expect(arl_ipv6, "Udp6SndbufErrors", &Udp6SndbufErrors);
+ arl_expect(arl_ipv6, "Udp6InCsumErrors", &Udp6InCsumErrors);
+ arl_expect(arl_ipv6, "Udp6IgnoredMulti", &Udp6IgnoredMulti);
+ arl_expect(arl_ipv6, "UdpLite6InDatagrams", &UdpLite6InDatagrams);
+ arl_expect(arl_ipv6, "UdpLite6NoPorts", &UdpLite6NoPorts);
+ arl_expect(arl_ipv6, "UdpLite6InErrors", &UdpLite6InErrors);
+ arl_expect(arl_ipv6, "UdpLite6OutDatagrams", &UdpLite6OutDatagrams);
+ arl_expect(arl_ipv6, "UdpLite6RcvbufErrors", &UdpLite6RcvbufErrors);
+ arl_expect(arl_ipv6, "UdpLite6SndbufErrors", &UdpLite6SndbufErrors);
+ arl_expect(arl_ipv6, "UdpLite6InCsumErrors", &UdpLite6InCsumErrors);
+ }
+
+ // parse /proc/net/snmp
+
+ if (unlikely(!ff_snmp6)) {
+ char filename[FILENAME_MAX + 1];
+ snprintfz(filename, FILENAME_MAX, "%s%s", netdata_configured_host_prefix, "/proc/net/snmp6");
+ ff_snmp6 = procfile_open(
+ config_get("plugin:proc:/proc/net/snmp6", "filename to monitor", filename), " \t:", PROCFILE_FLAG_DEFAULT);
+ if (unlikely(!ff_snmp6)) {
+ do_snmp6 = false;
+ return;
+ }
+ }
+
+ ff_snmp6 = procfile_readall(ff_snmp6);
+ if (unlikely(!ff_snmp6))
+ return;
+
+ size_t lines, l;
+
+ lines = procfile_lines(ff_snmp6);
+
+ arl_begin(arl_ipv6);
+
+ for (l = 0; l < lines; l++) {
+ size_t words = procfile_linewords(ff_snmp6, l);
+ if (unlikely(words < 2)) {
+ if (unlikely(words)) {
+ collector_error("Cannot read /proc/net/snmp6 line %zu. Expected 2 params, read %zu.", l, words);
+ continue;
+ }
+ }
+
+ if (unlikely(arl_check(arl_ipv6, procfile_lineword(ff_snmp6, l, 0), procfile_lineword(ff_snmp6, l, 1))))
+ break;
+ }
+
+ if(do_ip6_bandwidth == CONFIG_BOOLEAN_YES || (do_ip6_bandwidth == CONFIG_BOOLEAN_AUTO &&
+ (Ip6InOctets ||
+ Ip6OutOctets ||
+ netdata_zero_metrics_enabled == CONFIG_BOOLEAN_YES))) {
+ do_ip6_bandwidth = CONFIG_BOOLEAN_YES;
+ static RRDSET *st = NULL;
+ static RRDDIM *rd_received = NULL,
+ *rd_sent = NULL;
+
+ if(unlikely(!st)) {
+ st = rrdset_create_localhost(
+ "system"
+ , "ipv6"
+ , NULL
+ , "network"
+ , NULL
+ , "IPv6 Bandwidth"
+ , "kilobits/s"
+ , PLUGIN_PROC_NAME
+ , PLUGIN_PROC_MODULE_NETSTAT_NAME
+ , NETDATA_CHART_PRIO_SYSTEM_IPV6
+ , update_every
+ , RRDSET_TYPE_AREA
+ );
+
+ rd_received = rrddim_add(st, "InOctets", "received", 8, BITS_IN_A_KILOBIT, RRD_ALGORITHM_INCREMENTAL);
+ rd_sent = rrddim_add(st, "OutOctets", "sent", -8, BITS_IN_A_KILOBIT, RRD_ALGORITHM_INCREMENTAL);
+ }
+
+ rrddim_set_by_pointer(st, rd_received, Ip6InOctets);
+ rrddim_set_by_pointer(st, rd_sent, Ip6OutOctets);
+ rrdset_done(st);
+ }
+
+ if(do_ip6_packets == CONFIG_BOOLEAN_YES || (do_ip6_packets == CONFIG_BOOLEAN_AUTO &&
+ (Ip6InReceives ||
+ Ip6OutRequests ||
+ Ip6InDelivers ||
+ Ip6OutForwDatagrams ||
+ netdata_zero_metrics_enabled == CONFIG_BOOLEAN_YES))) {
+ do_ip6_packets = CONFIG_BOOLEAN_YES;
+ static RRDSET *st = NULL;
+ static RRDDIM *rd_received = NULL,
+ *rd_sent = NULL,
+ *rd_forwarded = NULL,
+ *rd_delivers = NULL;
+
+ if(unlikely(!st)) {
+ st = rrdset_create_localhost(
+ RRD_TYPE_NET_SNMP6
+ , "packets"
+ , NULL
+ , "packets"
+ , NULL
+ , "IPv6 Packets"
+ , "packets/s"
+ , PLUGIN_PROC_NAME
+ , PLUGIN_PROC_MODULE_NETSTAT_NAME
+ , NETDATA_CHART_PRIO_IPV6_PACKETS
+ , update_every
+ , RRDSET_TYPE_LINE
+ );
+
+ rd_received = rrddim_add(st, "InReceives", "received", 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_sent = rrddim_add(st, "OutRequests", "sent", -1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_forwarded = rrddim_add(st, "OutForwDatagrams", "forwarded", -1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_delivers = rrddim_add(st, "InDelivers", "delivers", 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ }
+
+ rrddim_set_by_pointer(st, rd_received, Ip6InReceives);
+ rrddim_set_by_pointer(st, rd_sent, Ip6OutRequests);
+ rrddim_set_by_pointer(st, rd_forwarded, Ip6OutForwDatagrams);
+ rrddim_set_by_pointer(st, rd_delivers, Ip6InDelivers);
+ rrdset_done(st);
+ }
+
+ if(do_ip6_fragsout == CONFIG_BOOLEAN_YES || (do_ip6_fragsout == CONFIG_BOOLEAN_AUTO &&
+ (Ip6FragOKs ||
+ Ip6FragFails ||
+ Ip6FragCreates ||
+ netdata_zero_metrics_enabled == CONFIG_BOOLEAN_YES))) {
+ do_ip6_fragsout = CONFIG_BOOLEAN_YES;
+ static RRDSET *st = NULL;
+ static RRDDIM *rd_ok = NULL,
+ *rd_failed = NULL,
+ *rd_all = NULL;
+
+ if(unlikely(!st)) {
+ st = rrdset_create_localhost(
+ RRD_TYPE_NET_SNMP6
+ , "fragsout"
+ , NULL
+ , "fragments6"
+ , NULL
+ , "IPv6 Fragments Sent"
+ , "packets/s"
+ , PLUGIN_PROC_NAME
+ , PLUGIN_PROC_MODULE_NETSTAT_NAME
+ , NETDATA_CHART_PRIO_IPV6_FRAGSOUT
+ , update_every
+ , RRDSET_TYPE_LINE
+ );
+ rrdset_flag_set(st, RRDSET_FLAG_DETAIL);
+
+ rd_ok = rrddim_add(st, "FragOKs", "ok", 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_failed = rrddim_add(st, "FragFails", "failed", -1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_all = rrddim_add(st, "FragCreates", "all", 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ }
+
+ rrddim_set_by_pointer(st, rd_ok, Ip6FragOKs);
+ rrddim_set_by_pointer(st, rd_failed, Ip6FragFails);
+ rrddim_set_by_pointer(st, rd_all, Ip6FragCreates);
+ rrdset_done(st);
+ }
+
+ if(do_ip6_fragsin == CONFIG_BOOLEAN_YES || (do_ip6_fragsin == CONFIG_BOOLEAN_AUTO &&
+ (Ip6ReasmOKs ||
+ Ip6ReasmFails ||
+ Ip6ReasmTimeout ||
+ Ip6ReasmReqds ||
+ netdata_zero_metrics_enabled == CONFIG_BOOLEAN_YES))) {
+ do_ip6_fragsin = CONFIG_BOOLEAN_YES;
+
+ static RRDSET *st = NULL;
+ static RRDDIM *rd_ok = NULL,
+ *rd_failed = NULL,
+ *rd_timeout = NULL,
+ *rd_all = NULL;
+
+ if(unlikely(!st)) {
+ st = rrdset_create_localhost(
+ RRD_TYPE_NET_SNMP6
+ , "fragsin"
+ , NULL
+ , "fragments6"
+ , NULL
+ , "IPv6 Fragments Reassembly"
+ , "packets/s"
+ , PLUGIN_PROC_NAME
+ , PLUGIN_PROC_MODULE_NETSTAT_NAME
+ , NETDATA_CHART_PRIO_IPV6_FRAGSIN
+ , update_every
+ , RRDSET_TYPE_LINE);
+ rrdset_flag_set(st, RRDSET_FLAG_DETAIL);
+
+ rd_ok = rrddim_add(st, "ReasmOKs", "ok", 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_failed = rrddim_add(st, "ReasmFails", "failed", -1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_timeout = rrddim_add(st, "ReasmTimeout", "timeout", -1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_all = rrddim_add(st, "ReasmReqds", "all", 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ }
+
+ rrddim_set_by_pointer(st, rd_ok, Ip6ReasmOKs);
+ rrddim_set_by_pointer(st, rd_failed, Ip6ReasmFails);
+ rrddim_set_by_pointer(st, rd_timeout, Ip6ReasmTimeout);
+ rrddim_set_by_pointer(st, rd_all, Ip6ReasmReqds);
+ rrdset_done(st);
+ }
+
+ if(do_ip6_errors == CONFIG_BOOLEAN_YES || (do_ip6_errors == CONFIG_BOOLEAN_AUTO &&
+ (Ip6InDiscards ||
+ Ip6OutDiscards ||
+ Ip6InHdrErrors ||
+ Ip6InAddrErrors ||
+ Ip6InUnknownProtos ||
+ Ip6InTooBigErrors ||
+ Ip6InTruncatedPkts ||
+ Ip6InNoRoutes ||
+ netdata_zero_metrics_enabled == CONFIG_BOOLEAN_YES))) {
+ do_ip6_errors = CONFIG_BOOLEAN_YES;
+ static RRDSET *st = NULL;
+ static RRDDIM *rd_InDiscards = NULL,
+ *rd_OutDiscards = NULL,
+ *rd_InHdrErrors = NULL,
+ *rd_InAddrErrors = NULL,
+ *rd_InUnknownProtos = NULL,
+ *rd_InTooBigErrors = NULL,
+ *rd_InTruncatedPkts = NULL,
+ *rd_InNoRoutes = NULL,
+ *rd_OutNoRoutes = NULL;
+
+ if(unlikely(!st)) {
+ st = rrdset_create_localhost(
+ RRD_TYPE_NET_SNMP6
+ , "errors"
+ , NULL
+ , "errors"
+ , NULL
+ , "IPv6 Errors"
+ , "packets/s"
+ , PLUGIN_PROC_NAME
+ , PLUGIN_PROC_MODULE_NETSTAT_NAME
+ , NETDATA_CHART_PRIO_IPV6_ERRORS
+ , update_every
+ , RRDSET_TYPE_LINE
+ );
+ rrdset_flag_set(st, RRDSET_FLAG_DETAIL);
+
+ rd_InDiscards = rrddim_add(st, "InDiscards", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_OutDiscards = rrddim_add(st, "OutDiscards", NULL, -1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_InHdrErrors = rrddim_add(st, "InHdrErrors", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_InAddrErrors = rrddim_add(st, "InAddrErrors", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_InUnknownProtos = rrddim_add(st, "InUnknownProtos", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_InTooBigErrors = rrddim_add(st, "InTooBigErrors", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_InTruncatedPkts = rrddim_add(st, "InTruncatedPkts", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_InNoRoutes = rrddim_add(st, "InNoRoutes", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_OutNoRoutes = rrddim_add(st, "OutNoRoutes", NULL, -1, 1, RRD_ALGORITHM_INCREMENTAL);
+ }
+
+ rrddim_set_by_pointer(st, rd_InDiscards, Ip6InDiscards);
+ rrddim_set_by_pointer(st, rd_OutDiscards, Ip6OutDiscards);
+ rrddim_set_by_pointer(st, rd_InHdrErrors, Ip6InHdrErrors);
+ rrddim_set_by_pointer(st, rd_InAddrErrors, Ip6InAddrErrors);
+ rrddim_set_by_pointer(st, rd_InUnknownProtos, Ip6InUnknownProtos);
+ rrddim_set_by_pointer(st, rd_InTooBigErrors, Ip6InTooBigErrors);
+ rrddim_set_by_pointer(st, rd_InTruncatedPkts, Ip6InTruncatedPkts);
+ rrddim_set_by_pointer(st, rd_InNoRoutes, Ip6InNoRoutes);
+ rrddim_set_by_pointer(st, rd_OutNoRoutes, Ip6OutNoRoutes);
+ rrdset_done(st);
+ }
+
+ if(do_ip6_udp_packets == CONFIG_BOOLEAN_YES || (do_ip6_udp_packets == CONFIG_BOOLEAN_AUTO &&
+ (Udp6InDatagrams ||
+ Udp6OutDatagrams ||
+ netdata_zero_metrics_enabled == CONFIG_BOOLEAN_YES))) {
+ static RRDSET *st = NULL;
+ static RRDDIM *rd_received = NULL,
+ *rd_sent = NULL;
+
+ if(unlikely(!st)) {
+ st = rrdset_create_localhost(
+ RRD_TYPE_NET_SNMP6
+ , "udppackets"
+ , NULL
+ , "udp6"
+ , NULL
+ , "IPv6 UDP Packets"
+ , "packets/s"
+ , PLUGIN_PROC_NAME
+ , PLUGIN_PROC_MODULE_NETSTAT_NAME
+ , NETDATA_CHART_PRIO_IPV6_UDP_PACKETS
+ , update_every
+ , RRDSET_TYPE_LINE
+ );
+
+ rd_received = rrddim_add(st, "InDatagrams", "received", 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_sent = rrddim_add(st, "OutDatagrams", "sent", -1, 1, RRD_ALGORITHM_INCREMENTAL);
+ }
+
+ rrddim_set_by_pointer(st, rd_received, Udp6InDatagrams);
+ rrddim_set_by_pointer(st, rd_sent, Udp6OutDatagrams);
+ rrdset_done(st);
+ }
+
+ if(do_ip6_udp_errors == CONFIG_BOOLEAN_YES || (do_ip6_udp_errors == CONFIG_BOOLEAN_AUTO &&
+ (Udp6InErrors ||
+ Udp6NoPorts ||
+ Udp6RcvbufErrors ||
+ Udp6SndbufErrors ||
+ Udp6InCsumErrors ||
+ Udp6IgnoredMulti ||
+ netdata_zero_metrics_enabled == CONFIG_BOOLEAN_YES))) {
+ do_ip6_udp_errors = CONFIG_BOOLEAN_YES;
+ static RRDSET *st = NULL;
+ static RRDDIM *rd_RcvbufErrors = NULL,
+ *rd_SndbufErrors = NULL,
+ *rd_InErrors = NULL,
+ *rd_NoPorts = NULL,
+ *rd_InCsumErrors = NULL,
+ *rd_IgnoredMulti = NULL;
+
+ if(unlikely(!st)) {
+ st = rrdset_create_localhost(
+ RRD_TYPE_NET_SNMP6
+ , "udperrors"
+ , NULL
+ , "udp6"
+ , NULL
+ , "IPv6 UDP Errors"
+ , "events/s"
+ , PLUGIN_PROC_NAME
+ , PLUGIN_PROC_MODULE_NETSTAT_NAME
+ , NETDATA_CHART_PRIO_IPV6_UDP_ERRORS
+ , update_every
+ , RRDSET_TYPE_LINE
+ );
+ rrdset_flag_set(st, RRDSET_FLAG_DETAIL);
+
+ rd_RcvbufErrors = rrddim_add(st, "RcvbufErrors", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_SndbufErrors = rrddim_add(st, "SndbufErrors", NULL, -1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_InErrors = rrddim_add(st, "InErrors", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_NoPorts = rrddim_add(st, "NoPorts", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_InCsumErrors = rrddim_add(st, "InCsumErrors", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_IgnoredMulti = rrddim_add(st, "IgnoredMulti", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ }
+
+ rrddim_set_by_pointer(st, rd_RcvbufErrors, Udp6RcvbufErrors);
+ rrddim_set_by_pointer(st, rd_SndbufErrors, Udp6SndbufErrors);
+ rrddim_set_by_pointer(st, rd_InErrors, Udp6InErrors);
+ rrddim_set_by_pointer(st, rd_NoPorts, Udp6NoPorts);
+ rrddim_set_by_pointer(st, rd_InCsumErrors, Udp6InCsumErrors);
+ rrddim_set_by_pointer(st, rd_IgnoredMulti, Udp6IgnoredMulti);
+ rrdset_done(st);
+ }
+
+ if(do_ip6_udplite_packets == CONFIG_BOOLEAN_YES || (do_ip6_udplite_packets == CONFIG_BOOLEAN_AUTO &&
+ (UdpLite6InDatagrams ||
+ UdpLite6OutDatagrams ||
+ netdata_zero_metrics_enabled == CONFIG_BOOLEAN_YES))) {
+ static RRDSET *st = NULL;
+ static RRDDIM *rd_received = NULL,
+ *rd_sent = NULL;
+
+ if(unlikely(!st)) {
+ st = rrdset_create_localhost(
+ RRD_TYPE_NET_SNMP6
+ , "udplitepackets"
+ , NULL
+ , "udplite6"
+ , NULL
+ , "IPv6 UDPlite Packets"
+ , "packets/s"
+ , PLUGIN_PROC_NAME
+ , PLUGIN_PROC_MODULE_NETSTAT_NAME
+ , NETDATA_CHART_PRIO_IPV6_UDPLITE_PACKETS
+ , update_every
+ , RRDSET_TYPE_LINE
+ );
+
+ rd_received = rrddim_add(st, "InDatagrams", "received", 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_sent = rrddim_add(st, "OutDatagrams", "sent", -1, 1, RRD_ALGORITHM_INCREMENTAL);
+ }
+
+ rrddim_set_by_pointer(st, rd_received, UdpLite6InDatagrams);
+ rrddim_set_by_pointer(st, rd_sent, UdpLite6OutDatagrams);
+ rrdset_done(st);
+ }
+
+ if(do_ip6_udplite_errors == CONFIG_BOOLEAN_YES || (do_ip6_udplite_errors == CONFIG_BOOLEAN_AUTO &&
+ (UdpLite6InErrors ||
+ UdpLite6NoPorts ||
+ UdpLite6RcvbufErrors ||
+ UdpLite6SndbufErrors ||
+ Udp6InCsumErrors ||
+ UdpLite6InCsumErrors ||
+ netdata_zero_metrics_enabled == CONFIG_BOOLEAN_YES))) {
+ do_ip6_udplite_errors = CONFIG_BOOLEAN_YES;
+ static RRDSET *st = NULL;
+ static RRDDIM *rd_RcvbufErrors = NULL,
+ *rd_SndbufErrors = NULL,
+ *rd_InErrors = NULL,
+ *rd_NoPorts = NULL,
+ *rd_InCsumErrors = NULL;
+
+ if(unlikely(!st)) {
+ st = rrdset_create_localhost(
+ RRD_TYPE_NET_SNMP6
+ , "udpliteerrors"
+ , NULL
+ , "udplite6"
+ , NULL
+ , "IPv6 UDP Lite Errors"
+ , "events/s"
+ , PLUGIN_PROC_NAME
+ , PLUGIN_PROC_MODULE_NETSTAT_NAME
+ , NETDATA_CHART_PRIO_IPV6_UDPLITE_ERRORS
+ , update_every
+ , RRDSET_TYPE_LINE
+ );
+ rrdset_flag_set(st, RRDSET_FLAG_DETAIL);
+
+ rd_RcvbufErrors = rrddim_add(st, "RcvbufErrors", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_SndbufErrors = rrddim_add(st, "SndbufErrors", NULL, -1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_InErrors = rrddim_add(st, "InErrors", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_NoPorts = rrddim_add(st, "NoPorts", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_InCsumErrors = rrddim_add(st, "InCsumErrors", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ }
+
+ rrddim_set_by_pointer(st, rd_InErrors, UdpLite6InErrors);
+ rrddim_set_by_pointer(st, rd_NoPorts, UdpLite6NoPorts);
+ rrddim_set_by_pointer(st, rd_RcvbufErrors, UdpLite6RcvbufErrors);
+ rrddim_set_by_pointer(st, rd_SndbufErrors, UdpLite6SndbufErrors);
+ rrddim_set_by_pointer(st, rd_InCsumErrors, UdpLite6InCsumErrors);
+ rrdset_done(st);
+ }
+
+ if(do_ip6_mcast == CONFIG_BOOLEAN_YES || (do_ip6_mcast == CONFIG_BOOLEAN_AUTO &&
+ (Ip6OutMcastOctets ||
+ Ip6InMcastOctets ||
+ netdata_zero_metrics_enabled == CONFIG_BOOLEAN_YES))) {
+ do_ip6_mcast = CONFIG_BOOLEAN_YES;
+ static RRDSET *st = NULL;
+ static RRDDIM *rd_Ip6InMcastOctets = NULL,
+ *rd_Ip6OutMcastOctets = NULL;
+
+ if(unlikely(!st)) {
+ st = rrdset_create_localhost(
+ RRD_TYPE_NET_SNMP6
+ , "mcast"
+ , NULL
+ , "multicast6"
+ , NULL
+ , "IPv6 Multicast Bandwidth"
+ , "kilobits/s"
+ , PLUGIN_PROC_NAME
+ , PLUGIN_PROC_MODULE_NETSTAT_NAME
+ , NETDATA_CHART_PRIO_IPV6_MCAST
+ , update_every
+ , RRDSET_TYPE_AREA
+ );
+ rrdset_flag_set(st, RRDSET_FLAG_DETAIL);
+
+ rd_Ip6InMcastOctets = rrddim_add(st, "InMcastOctets", "received", 8, BITS_IN_A_KILOBIT, RRD_ALGORITHM_INCREMENTAL);
+ rd_Ip6OutMcastOctets = rrddim_add(st, "OutMcastOctets", "sent", -8, BITS_IN_A_KILOBIT, RRD_ALGORITHM_INCREMENTAL);
+ }
+
+ rrddim_set_by_pointer(st, rd_Ip6InMcastOctets, Ip6InMcastOctets);
+ rrddim_set_by_pointer(st, rd_Ip6OutMcastOctets, Ip6OutMcastOctets);
+ rrdset_done(st);
+ }
+
+ if(do_ip6_bcast == CONFIG_BOOLEAN_YES || (do_ip6_bcast == CONFIG_BOOLEAN_AUTO &&
+ (Ip6OutBcastOctets ||
+ Ip6InBcastOctets ||
+ netdata_zero_metrics_enabled == CONFIG_BOOLEAN_YES))) {
+ do_ip6_bcast = CONFIG_BOOLEAN_YES;
+ static RRDSET *st = NULL;
+ static RRDDIM *rd_Ip6InBcastOctets = NULL,
+ *rd_Ip6OutBcastOctets = NULL;
+
+ if(unlikely(!st)) {
+ st = rrdset_create_localhost(
+ RRD_TYPE_NET_SNMP6
+ , "bcast"
+ , NULL
+ , "broadcast6"
+ , NULL
+ , "IPv6 Broadcast Bandwidth"
+ , "kilobits/s"
+ , PLUGIN_PROC_NAME
+ , PLUGIN_PROC_MODULE_NETSTAT_NAME
+ , NETDATA_CHART_PRIO_IPV6_BCAST
+ , update_every
+ , RRDSET_TYPE_AREA
+ );
+ rrdset_flag_set(st, RRDSET_FLAG_DETAIL);
+
+ rd_Ip6InBcastOctets = rrddim_add(st, "InBcastOctets", "received", 8, BITS_IN_A_KILOBIT, RRD_ALGORITHM_INCREMENTAL);
+ rd_Ip6OutBcastOctets = rrddim_add(st, "OutBcastOctets", "sent", -8, BITS_IN_A_KILOBIT, RRD_ALGORITHM_INCREMENTAL);
+ }
+
+ rrddim_set_by_pointer(st, rd_Ip6InBcastOctets, Ip6InBcastOctets);
+ rrddim_set_by_pointer(st, rd_Ip6OutBcastOctets, Ip6OutBcastOctets);
+ rrdset_done(st);
+ }
+
+ if(do_ip6_mcast_p == CONFIG_BOOLEAN_YES || (do_ip6_mcast_p == CONFIG_BOOLEAN_AUTO &&
+ (Ip6OutMcastPkts ||
+ Ip6InMcastPkts ||
+ netdata_zero_metrics_enabled == CONFIG_BOOLEAN_YES))) {
+ do_ip6_mcast_p = CONFIG_BOOLEAN_YES;
+ static RRDSET *st = NULL;
+ static RRDDIM *rd_Ip6InMcastPkts = NULL,
+ *rd_Ip6OutMcastPkts = NULL;
+
+ if(unlikely(!st)) {
+ st = rrdset_create_localhost(
+ RRD_TYPE_NET_SNMP6
+ , "mcastpkts"
+ , NULL
+ , "multicast6"
+ , NULL
+ , "IPv6 Multicast Packets"
+ , "packets/s"
+ , PLUGIN_PROC_NAME
+ , PLUGIN_PROC_MODULE_NETSTAT_NAME
+ , NETDATA_CHART_PRIO_IPV6_MCAST_PACKETS
+ , update_every
+ , RRDSET_TYPE_LINE
+ );
+ rrdset_flag_set(st, RRDSET_FLAG_DETAIL);
+
+ rd_Ip6InMcastPkts = rrddim_add(st, "InMcastPkts", "received", 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_Ip6OutMcastPkts = rrddim_add(st, "OutMcastPkts", "sent", -1, 1, RRD_ALGORITHM_INCREMENTAL);
+ }
+
+ rrddim_set_by_pointer(st, rd_Ip6InMcastPkts, Ip6InMcastPkts);
+ rrddim_set_by_pointer(st, rd_Ip6OutMcastPkts, Ip6OutMcastPkts);
+ rrdset_done(st);
+ }
+
+ if(do_ip6_icmp == CONFIG_BOOLEAN_YES || (do_ip6_icmp == CONFIG_BOOLEAN_AUTO &&
+ (Icmp6InMsgs ||
+ Icmp6OutMsgs ||
+ netdata_zero_metrics_enabled == CONFIG_BOOLEAN_YES))) {
+ do_ip6_icmp = CONFIG_BOOLEAN_YES;
+ static RRDSET *st = NULL;
+ static RRDDIM *rd_Icmp6InMsgs = NULL,
+ *rd_Icmp6OutMsgs = NULL;
+
+ if(unlikely(!st)) {
+ st = rrdset_create_localhost(
+ RRD_TYPE_NET_SNMP6
+ , "icmp"
+ , NULL
+ , "icmp6"
+ , NULL
+ , "IPv6 ICMP Messages"
+ , "messages/s"
+ , PLUGIN_PROC_NAME
+ , PLUGIN_PROC_MODULE_NETSTAT_NAME
+ , NETDATA_CHART_PRIO_IPV6_ICMP
+ , update_every
+ , RRDSET_TYPE_LINE
+ );
+
+ rd_Icmp6InMsgs = rrddim_add(st, "InMsgs", "received", 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_Icmp6OutMsgs = rrddim_add(st, "OutMsgs", "sent", -1, 1, RRD_ALGORITHM_INCREMENTAL);
+ }
+
+ rrddim_set_by_pointer(st, rd_Icmp6InMsgs, Icmp6InMsgs);
+ rrddim_set_by_pointer(st, rd_Icmp6OutMsgs, Icmp6OutMsgs);
+ rrdset_done(st);
+ }
+
+ if(do_ip6_icmp_redir == CONFIG_BOOLEAN_YES || (do_ip6_icmp_redir == CONFIG_BOOLEAN_AUTO &&
+ (Icmp6InRedirects ||
+ Icmp6OutRedirects ||
+ netdata_zero_metrics_enabled == CONFIG_BOOLEAN_YES))) {
+ do_ip6_icmp_redir = CONFIG_BOOLEAN_YES;
+ static RRDSET *st = NULL;
+ static RRDDIM *rd_Icmp6InRedirects = NULL,
+ *rd_Icmp6OutRedirects = NULL;
+
+ if(unlikely(!st)) {
+ st = rrdset_create_localhost(
+ RRD_TYPE_NET_SNMP6
+ , "icmpredir"
+ , NULL
+ , "icmp6"
+ , NULL
+ , "IPv6 ICMP Redirects"
+ , "redirects/s"
+ , PLUGIN_PROC_NAME
+ , PLUGIN_PROC_MODULE_NETSTAT_NAME
+ , NETDATA_CHART_PRIO_IPV6_ICMP_REDIR
+ , update_every
+ , RRDSET_TYPE_LINE
+ );
+
+ rd_Icmp6InRedirects = rrddim_add(st, "InRedirects", "received", 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_Icmp6OutRedirects = rrddim_add(st, "OutRedirects", "sent", -1, 1, RRD_ALGORITHM_INCREMENTAL);
+ }
+
+ rrddim_set_by_pointer(st, rd_Icmp6InRedirects, Icmp6InRedirects);
+ rrddim_set_by_pointer(st, rd_Icmp6OutRedirects, Icmp6OutRedirects);
+ rrdset_done(st);
+ }
+
+ if(do_ip6_icmp_errors == CONFIG_BOOLEAN_YES || (do_ip6_icmp_errors == CONFIG_BOOLEAN_AUTO &&
+ (Icmp6InErrors ||
+ Icmp6OutErrors ||
+ Icmp6InCsumErrors ||
+ Icmp6InDestUnreachs ||
+ Icmp6InPktTooBigs ||
+ Icmp6InTimeExcds ||
+ Icmp6InParmProblems ||
+ Icmp6OutDestUnreachs ||
+ Icmp6OutPktTooBigs ||
+ Icmp6OutTimeExcds ||
+ Icmp6OutParmProblems ||
+ netdata_zero_metrics_enabled == CONFIG_BOOLEAN_YES))) {
+ do_ip6_icmp_errors = CONFIG_BOOLEAN_YES;
+ static RRDSET *st = NULL;
+ static RRDDIM *rd_InErrors = NULL,
+ *rd_OutErrors = NULL,
+ *rd_InCsumErrors = NULL,
+ *rd_InDestUnreachs = NULL,
+ *rd_InPktTooBigs = NULL,
+ *rd_InTimeExcds = NULL,
+ *rd_InParmProblems = NULL,
+ *rd_OutDestUnreachs = NULL,
+ *rd_OutPktTooBigs = NULL,
+ *rd_OutTimeExcds = NULL,
+ *rd_OutParmProblems = NULL;
+
+ if(unlikely(!st)) {
+ st = rrdset_create_localhost(
+ RRD_TYPE_NET_SNMP6
+ , "icmperrors"
+ , NULL
+ , "icmp6"
+ , NULL
+ , "IPv6 ICMP Errors"
+ , "errors/s"
+ , PLUGIN_PROC_NAME
+ , PLUGIN_PROC_MODULE_NETSTAT_NAME
+ , NETDATA_CHART_PRIO_IPV6_ICMP_ERRORS
+ , update_every
+ , RRDSET_TYPE_LINE
+ );
+
+ rd_InErrors = rrddim_add(st, "InErrors", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_OutErrors = rrddim_add(st, "OutErrors", NULL, -1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_InCsumErrors = rrddim_add(st, "InCsumErrors", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_InDestUnreachs = rrddim_add(st, "InDestUnreachs", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_InPktTooBigs = rrddim_add(st, "InPktTooBigs", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_InTimeExcds = rrddim_add(st, "InTimeExcds", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_InParmProblems = rrddim_add(st, "InParmProblems", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_OutDestUnreachs = rrddim_add(st, "OutDestUnreachs", NULL, -1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_OutPktTooBigs = rrddim_add(st, "OutPktTooBigs", NULL, -1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_OutTimeExcds = rrddim_add(st, "OutTimeExcds", NULL, -1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_OutParmProblems = rrddim_add(st, "OutParmProblems", NULL, -1, 1, RRD_ALGORITHM_INCREMENTAL);
+ }
+
+ rrddim_set_by_pointer(st, rd_InErrors, Icmp6InErrors);
+ rrddim_set_by_pointer(st, rd_OutErrors, Icmp6OutErrors);
+ rrddim_set_by_pointer(st, rd_InCsumErrors, Icmp6InCsumErrors);
+ rrddim_set_by_pointer(st, rd_InDestUnreachs, Icmp6InDestUnreachs);
+ rrddim_set_by_pointer(st, rd_InPktTooBigs, Icmp6InPktTooBigs);
+ rrddim_set_by_pointer(st, rd_InTimeExcds, Icmp6InTimeExcds);
+ rrddim_set_by_pointer(st, rd_InParmProblems, Icmp6InParmProblems);
+ rrddim_set_by_pointer(st, rd_OutDestUnreachs, Icmp6OutDestUnreachs);
+ rrddim_set_by_pointer(st, rd_OutPktTooBigs, Icmp6OutPktTooBigs);
+ rrddim_set_by_pointer(st, rd_OutTimeExcds, Icmp6OutTimeExcds);
+ rrddim_set_by_pointer(st, rd_OutParmProblems, Icmp6OutParmProblems);
+ rrdset_done(st);
+ }
+
+ if(do_ip6_icmp_echos == CONFIG_BOOLEAN_YES || (do_ip6_icmp_echos == CONFIG_BOOLEAN_AUTO &&
+ (Icmp6InEchos ||
+ Icmp6OutEchos ||
+ Icmp6InEchoReplies ||
+ Icmp6OutEchoReplies ||
+ netdata_zero_metrics_enabled == CONFIG_BOOLEAN_YES))) {
+ do_ip6_icmp_echos = CONFIG_BOOLEAN_YES;
+ static RRDSET *st = NULL;
+ static RRDDIM *rd_InEchos = NULL,
+ *rd_OutEchos = NULL,
+ *rd_InEchoReplies = NULL,
+ *rd_OutEchoReplies = NULL;
+
+ if(unlikely(!st)) {
+ st = rrdset_create_localhost(
+ RRD_TYPE_NET_SNMP6
+ , "icmpechos"
+ , NULL
+ , "icmp6"
+ , NULL
+ , "IPv6 ICMP Echo"
+ , "messages/s"
+ , PLUGIN_PROC_NAME
+ , PLUGIN_PROC_MODULE_NETSTAT_NAME
+ , NETDATA_CHART_PRIO_IPV6_ICMP_ECHOS
+ , update_every
+ , RRDSET_TYPE_LINE
+ );
+
+ rd_InEchos = rrddim_add(st, "InEchos", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_OutEchos = rrddim_add(st, "OutEchos", NULL, -1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_InEchoReplies = rrddim_add(st, "InEchoReplies", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_OutEchoReplies = rrddim_add(st, "OutEchoReplies", NULL, -1, 1, RRD_ALGORITHM_INCREMENTAL);
+ }
+
+ rrddim_set_by_pointer(st, rd_InEchos, Icmp6InEchos);
+ rrddim_set_by_pointer(st, rd_OutEchos, Icmp6OutEchos);
+ rrddim_set_by_pointer(st, rd_InEchoReplies, Icmp6InEchoReplies);
+ rrddim_set_by_pointer(st, rd_OutEchoReplies, Icmp6OutEchoReplies);
+ rrdset_done(st);
+ }
+
+ if(do_ip6_icmp_groupmemb == CONFIG_BOOLEAN_YES || (do_ip6_icmp_groupmemb == CONFIG_BOOLEAN_AUTO &&
+ (Icmp6InGroupMembQueries ||
+ Icmp6OutGroupMembQueries ||
+ Icmp6InGroupMembResponses ||
+ Icmp6OutGroupMembResponses ||
+ Icmp6InGroupMembReductions ||
+ Icmp6OutGroupMembReductions ||
+ netdata_zero_metrics_enabled == CONFIG_BOOLEAN_YES))) {
+ do_ip6_icmp_groupmemb = CONFIG_BOOLEAN_YES;
+ static RRDSET *st = NULL;
+ static RRDDIM *rd_InQueries = NULL,
+ *rd_OutQueries = NULL,
+ *rd_InResponses = NULL,
+ *rd_OutResponses = NULL,
+ *rd_InReductions = NULL,
+ *rd_OutReductions = NULL;
+
+ if(unlikely(!st)) {
+ st = rrdset_create_localhost(
+ RRD_TYPE_NET_SNMP6
+ , "groupmemb"
+ , NULL
+ , "icmp6"
+ , NULL
+ , "IPv6 ICMP Group Membership"
+ , "messages/s"
+ , PLUGIN_PROC_NAME
+ , PLUGIN_PROC_MODULE_NETSTAT_NAME
+ , NETDATA_CHART_PRIO_IPV6_ICMP_GROUPMEMB
+ , update_every
+ , RRDSET_TYPE_LINE);
+
+ rd_InQueries = rrddim_add(st, "InQueries", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_OutQueries = rrddim_add(st, "OutQueries", NULL, -1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_InResponses = rrddim_add(st, "InResponses", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_OutResponses = rrddim_add(st, "OutResponses", NULL, -1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_InReductions = rrddim_add(st, "InReductions", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_OutReductions = rrddim_add(st, "OutReductions", NULL, -1, 1, RRD_ALGORITHM_INCREMENTAL);
+ }
+
+ rrddim_set_by_pointer(st, rd_InQueries, Icmp6InGroupMembQueries);
+ rrddim_set_by_pointer(st, rd_OutQueries, Icmp6OutGroupMembQueries);
+ rrddim_set_by_pointer(st, rd_InResponses, Icmp6InGroupMembResponses);
+ rrddim_set_by_pointer(st, rd_OutResponses, Icmp6OutGroupMembResponses);
+ rrddim_set_by_pointer(st, rd_InReductions, Icmp6InGroupMembReductions);
+ rrddim_set_by_pointer(st, rd_OutReductions, Icmp6OutGroupMembReductions);
+ rrdset_done(st);
+ }
+
+ if(do_ip6_icmp_router == CONFIG_BOOLEAN_YES || (do_ip6_icmp_router == CONFIG_BOOLEAN_AUTO &&
+ (Icmp6InRouterSolicits ||
+ Icmp6OutRouterSolicits ||
+ Icmp6InRouterAdvertisements ||
+ Icmp6OutRouterAdvertisements ||
+ netdata_zero_metrics_enabled == CONFIG_BOOLEAN_YES))) {
+ do_ip6_icmp_router = CONFIG_BOOLEAN_YES;
+ static RRDSET *st = NULL;
+ static RRDDIM *rd_InSolicits = NULL,
+ *rd_OutSolicits = NULL,
+ *rd_InAdvertisements = NULL,
+ *rd_OutAdvertisements = NULL;
+
+ if(unlikely(!st)) {
+ st = rrdset_create_localhost(
+ RRD_TYPE_NET_SNMP6
+ , "icmprouter"
+ , NULL
+ , "icmp6"
+ , NULL
+ , "IPv6 Router Messages"
+ , "messages/s"
+ , PLUGIN_PROC_NAME
+ , PLUGIN_PROC_MODULE_NETSTAT_NAME
+ , NETDATA_CHART_PRIO_IPV6_ICMP_ROUTER
+ , update_every
+ , RRDSET_TYPE_LINE
+ );
+
+ rd_InSolicits = rrddim_add(st, "InSolicits", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_OutSolicits = rrddim_add(st, "OutSolicits", NULL, -1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_InAdvertisements = rrddim_add(st, "InAdvertisements", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_OutAdvertisements = rrddim_add(st, "OutAdvertisements", NULL, -1, 1, RRD_ALGORITHM_INCREMENTAL);
+ }
+
+ rrddim_set_by_pointer(st, rd_InSolicits, Icmp6InRouterSolicits);
+ rrddim_set_by_pointer(st, rd_OutSolicits, Icmp6OutRouterSolicits);
+ rrddim_set_by_pointer(st, rd_InAdvertisements, Icmp6InRouterAdvertisements);
+ rrddim_set_by_pointer(st, rd_OutAdvertisements, Icmp6OutRouterAdvertisements);
+ rrdset_done(st);
+ }
+
+ if(do_ip6_icmp_neighbor == CONFIG_BOOLEAN_YES || (do_ip6_icmp_neighbor == CONFIG_BOOLEAN_AUTO &&
+ (Icmp6InNeighborSolicits ||
+ Icmp6OutNeighborSolicits ||
+ Icmp6InNeighborAdvertisements ||
+ Icmp6OutNeighborAdvertisements ||
+ netdata_zero_metrics_enabled == CONFIG_BOOLEAN_YES))) {
+ do_ip6_icmp_neighbor = CONFIG_BOOLEAN_YES;
+ static RRDSET *st = NULL;
+ static RRDDIM *rd_InSolicits = NULL,
+ *rd_OutSolicits = NULL,
+ *rd_InAdvertisements = NULL,
+ *rd_OutAdvertisements = NULL;
+
+ if(unlikely(!st)) {
+ st = rrdset_create_localhost(
+ RRD_TYPE_NET_SNMP6
+ , "icmpneighbor"
+ , NULL
+ , "icmp6"
+ , NULL
+ , "IPv6 Neighbor Messages"
+ , "messages/s"
+ , PLUGIN_PROC_NAME
+ , PLUGIN_PROC_MODULE_NETSTAT_NAME
+ , NETDATA_CHART_PRIO_IPV6_ICMP_NEIGHBOR
+ , update_every
+ , RRDSET_TYPE_LINE
+ );
+
+ rd_InSolicits = rrddim_add(st, "InSolicits", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_OutSolicits = rrddim_add(st, "OutSolicits", NULL, -1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_InAdvertisements = rrddim_add(st, "InAdvertisements", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_OutAdvertisements = rrddim_add(st, "OutAdvertisements", NULL, -1, 1, RRD_ALGORITHM_INCREMENTAL);
+ }
+
+ rrddim_set_by_pointer(st, rd_InSolicits, Icmp6InNeighborSolicits);
+ rrddim_set_by_pointer(st, rd_OutSolicits, Icmp6OutNeighborSolicits);
+ rrddim_set_by_pointer(st, rd_InAdvertisements, Icmp6InNeighborAdvertisements);
+ rrddim_set_by_pointer(st, rd_OutAdvertisements, Icmp6OutNeighborAdvertisements);
+ rrdset_done(st);
+ }
+
+ if(do_ip6_icmp_mldv2 == CONFIG_BOOLEAN_YES || (do_ip6_icmp_mldv2 == CONFIG_BOOLEAN_AUTO &&
+ (Icmp6InMLDv2Reports ||
+ Icmp6OutMLDv2Reports ||
+ netdata_zero_metrics_enabled == CONFIG_BOOLEAN_YES))) {
+ do_ip6_icmp_mldv2 = CONFIG_BOOLEAN_YES;
+ static RRDSET *st = NULL;
+ static RRDDIM *rd_InMLDv2Reports = NULL,
+ *rd_OutMLDv2Reports = NULL;
+
+ if(unlikely(!st)) {
+ st = rrdset_create_localhost(
+ RRD_TYPE_NET_SNMP6
+ , "icmpmldv2"
+ , NULL
+ , "icmp6"
+ , NULL
+ , "IPv6 ICMP MLDv2 Reports"
+ , "reports/s"
+ , PLUGIN_PROC_NAME
+ , PLUGIN_PROC_MODULE_NETSTAT_NAME
+ , NETDATA_CHART_PRIO_IPV6_ICMP_LDV2
+ , update_every
+ , RRDSET_TYPE_LINE
+ );
+
+ rd_InMLDv2Reports = rrddim_add(st, "InMLDv2Reports", "received", 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_OutMLDv2Reports = rrddim_add(st, "OutMLDv2Reports", "sent", -1, 1, RRD_ALGORITHM_INCREMENTAL);
+ }
+
+ rrddim_set_by_pointer(st, rd_InMLDv2Reports, Icmp6InMLDv2Reports);
+ rrddim_set_by_pointer(st, rd_OutMLDv2Reports, Icmp6OutMLDv2Reports);
+ rrdset_done(st);
+ }
+
+ if(do_ip6_icmp_types == CONFIG_BOOLEAN_YES || (do_ip6_icmp_types == CONFIG_BOOLEAN_AUTO &&
+ (Icmp6InType1 ||
+ Icmp6InType128 ||
+ Icmp6InType129 ||
+ Icmp6InType136 ||
+ Icmp6OutType1 ||
+ Icmp6OutType128 ||
+ Icmp6OutType129 ||
+ Icmp6OutType133 ||
+ Icmp6OutType135 ||
+ Icmp6OutType143 ||
+ netdata_zero_metrics_enabled == CONFIG_BOOLEAN_YES))) {
+ do_ip6_icmp_types = CONFIG_BOOLEAN_YES;
+ static RRDSET *st = NULL;
+ static RRDDIM *rd_InType1 = NULL,
+ *rd_InType128 = NULL,
+ *rd_InType129 = NULL,
+ *rd_InType136 = NULL,
+ *rd_OutType1 = NULL,
+ *rd_OutType128 = NULL,
+ *rd_OutType129 = NULL,
+ *rd_OutType133 = NULL,
+ *rd_OutType135 = NULL,
+ *rd_OutType143 = NULL;
+
+ if(unlikely(!st)) {
+ st = rrdset_create_localhost(
+ RRD_TYPE_NET_SNMP6
+ , "icmptypes"
+ , NULL
+ , "icmp6"
+ , NULL
+ , "IPv6 ICMP Types"
+ , "messages/s"
+ , PLUGIN_PROC_NAME
+ , PLUGIN_PROC_MODULE_NETSTAT_NAME
+ , NETDATA_CHART_PRIO_IPV6_ICMP_TYPES
+ , update_every
+ , RRDSET_TYPE_LINE
+ );
+
+ rd_InType1 = rrddim_add(st, "InType1", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_InType128 = rrddim_add(st, "InType128", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_InType129 = rrddim_add(st, "InType129", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_InType136 = rrddim_add(st, "InType136", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_OutType1 = rrddim_add(st, "OutType1", NULL, -1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_OutType128 = rrddim_add(st, "OutType128", NULL, -1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_OutType129 = rrddim_add(st, "OutType129", NULL, -1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_OutType133 = rrddim_add(st, "OutType133", NULL, -1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_OutType135 = rrddim_add(st, "OutType135", NULL, -1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_OutType143 = rrddim_add(st, "OutType143", NULL, -1, 1, RRD_ALGORITHM_INCREMENTAL);
+ }
+
+ rrddim_set_by_pointer(st, rd_InType1, Icmp6InType1);
+ rrddim_set_by_pointer(st, rd_InType128, Icmp6InType128);
+ rrddim_set_by_pointer(st, rd_InType129, Icmp6InType129);
+ rrddim_set_by_pointer(st, rd_InType136, Icmp6InType136);
+ rrddim_set_by_pointer(st, rd_OutType1, Icmp6OutType1);
+ rrddim_set_by_pointer(st, rd_OutType128, Icmp6OutType128);
+ rrddim_set_by_pointer(st, rd_OutType129, Icmp6OutType129);
+ rrddim_set_by_pointer(st, rd_OutType133, Icmp6OutType133);
+ rrddim_set_by_pointer(st, rd_OutType135, Icmp6OutType135);
+ rrddim_set_by_pointer(st, rd_OutType143, Icmp6OutType143);
+ rrdset_done(st);
+ }
+
+ if (do_ip6_ect == CONFIG_BOOLEAN_YES ||
+ (do_ip6_ect == CONFIG_BOOLEAN_AUTO && (Ip6InNoECTPkts || Ip6InECT1Pkts || Ip6InECT0Pkts || Ip6InCEPkts ||
+ netdata_zero_metrics_enabled == CONFIG_BOOLEAN_YES))) {
+ do_ip6_ect = CONFIG_BOOLEAN_YES;
+ static RRDSET *st = NULL;
+ static RRDDIM *rd_InNoECTPkts = NULL, *rd_InECT1Pkts = NULL, *rd_InECT0Pkts = NULL, *rd_InCEPkts = NULL;
+
+ if (unlikely(!st)) {
+ st = rrdset_create_localhost(
+ RRD_TYPE_NET_SNMP6,
+ "ect",
+ NULL,
+ "packets",
+ NULL,
+ "IPv6 ECT Packets",
+ "packets/s",
+ PLUGIN_PROC_NAME,
+ PLUGIN_PROC_MODULE_NETSTAT_NAME,
+ NETDATA_CHART_PRIO_IPV6_ECT,
+ update_every,
+ RRDSET_TYPE_LINE);
+
+ rd_InNoECTPkts = rrddim_add(st, "InNoECTPkts", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_InECT1Pkts = rrddim_add(st, "InECT1Pkts", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_InECT0Pkts = rrddim_add(st, "InECT0Pkts", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_InCEPkts = rrddim_add(st, "InCEPkts", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ }
+
+ rrddim_set_by_pointer(st, rd_InNoECTPkts, Ip6InNoECTPkts);
+ rrddim_set_by_pointer(st, rd_InECT1Pkts, Ip6InECT1Pkts);
+ rrddim_set_by_pointer(st, rd_InECT0Pkts, Ip6InECT0Pkts);
+ rrddim_set_by_pointer(st, rd_InCEPkts, Ip6InCEPkts);
+ rrdset_done(st);
+ }
+}
+
+int do_proc_net_netstat(int update_every, usec_t dt) {
+ (void)dt;
+
+ static int do_bandwidth = -1, do_inerrors = -1, do_mcast = -1, do_bcast = -1, do_mcast_p = -1, do_bcast_p = -1, do_ecn = -1, \
+ do_tcpext_reorder = -1, do_tcpext_syscookies = -1, do_tcpext_ofo = -1, do_tcpext_connaborts = -1, do_tcpext_memory = -1,
+ do_tcpext_syn_queue = -1, do_tcpext_accept_queue = -1;
+
+ static int do_ip_packets = -1, do_ip_fragsout = -1, do_ip_fragsin = -1, do_ip_errors = -1,
+ do_tcp_sockets = -1, do_tcp_packets = -1, do_tcp_errors = -1, do_tcp_handshake = -1, do_tcp_opens = -1,
+ do_udp_packets = -1, do_udp_errors = -1, do_icmp_packets = -1, do_icmpmsg = -1, do_udplite_packets = -1;
+
+ static uint32_t hash_ipext = 0, hash_tcpext = 0;
+ static uint32_t hash_ip = 0, hash_icmp = 0, hash_tcp = 0, hash_udp = 0, hash_icmpmsg = 0, hash_udplite = 0;
+
+ static procfile *ff_netstat = NULL;
+ static procfile *ff_snmp = NULL;
+
+ static ARL_BASE *arl_tcpext = NULL;
+ static ARL_BASE *arl_ipext = NULL;
+
+ static ARL_BASE *arl_ip = NULL;
+ static ARL_BASE *arl_icmp = NULL;
+ static ARL_BASE *arl_icmpmsg = NULL;
+ static ARL_BASE *arl_tcp = NULL;
+ static ARL_BASE *arl_udp = NULL;
+ static ARL_BASE *arl_udplite = NULL;
+
+ static const RRDVAR_ACQUIRED *tcp_max_connections_var = NULL;
+
+ // --------------------------------------------------------------------
+ // IP
+
+ // IP bandwidth
+ static unsigned long long ipext_InOctets = 0;
+ static unsigned long long ipext_OutOctets = 0;
+
+ // IP input errors
+ static unsigned long long ipext_InNoRoutes = 0;
+ static unsigned long long ipext_InTruncatedPkts = 0;
+ static unsigned long long ipext_InCsumErrors = 0;
+
+ // IP multicast bandwidth
+ static unsigned long long ipext_InMcastOctets = 0;
+ static unsigned long long ipext_OutMcastOctets = 0;
+
+ // IP multicast packets
+ static unsigned long long ipext_InMcastPkts = 0;
+ static unsigned long long ipext_OutMcastPkts = 0;
+
+ // IP broadcast bandwidth
+ static unsigned long long ipext_InBcastOctets = 0;
+ static unsigned long long ipext_OutBcastOctets = 0;
+
+ // IP broadcast packets
+ static unsigned long long ipext_InBcastPkts = 0;
+ static unsigned long long ipext_OutBcastPkts = 0;
+
+ // IP ECN
+ static unsigned long long ipext_InNoECTPkts = 0;
+ static unsigned long long ipext_InECT1Pkts = 0;
+ static unsigned long long ipext_InECT0Pkts = 0;
+ static unsigned long long ipext_InCEPkts = 0;
+
+ // --------------------------------------------------------------------
+ // IP TCP
+
+ // IP TCP Reordering
+ static unsigned long long tcpext_TCPRenoReorder = 0;
+ static unsigned long long tcpext_TCPFACKReorder = 0;
+ static unsigned long long tcpext_TCPSACKReorder = 0;
+ static unsigned long long tcpext_TCPTSReorder = 0;
+
+ // IP TCP SYN Cookies
+ static unsigned long long tcpext_SyncookiesSent = 0;
+ static unsigned long long tcpext_SyncookiesRecv = 0;
+ static unsigned long long tcpext_SyncookiesFailed = 0;
+
+ // IP TCP Out Of Order Queue
+ // http://www.spinics.net/lists/netdev/msg204696.html
+ static unsigned long long tcpext_TCPOFOQueue = 0; // Number of packets queued in OFO queue
+ static unsigned long long tcpext_TCPOFODrop = 0; // Number of packets meant to be queued in OFO but dropped because socket rcvbuf limit hit.
+ static unsigned long long tcpext_TCPOFOMerge = 0; // Number of packets in OFO that were merged with other packets.
+ static unsigned long long tcpext_OfoPruned = 0; // packets dropped from out-of-order queue because of socket buffer overrun
+
+ // IP TCP connection resets
+ // https://github.com/ecki/net-tools/blob/bd8bceaed2311651710331a7f8990c3e31be9840/statistics.c
+ static unsigned long long tcpext_TCPAbortOnData = 0; // connections reset due to unexpected data
+ static unsigned long long tcpext_TCPAbortOnClose = 0; // connections reset due to early user close
+ static unsigned long long tcpext_TCPAbortOnMemory = 0; // connections aborted due to memory pressure
+ static unsigned long long tcpext_TCPAbortOnTimeout = 0; // connections aborted due to timeout
+ static unsigned long long tcpext_TCPAbortOnLinger = 0; // connections aborted after user close in linger timeout
+ static unsigned long long tcpext_TCPAbortFailed = 0; // times unable to send RST due to no memory
+
+ // https://perfchron.com/2015/12/26/investigating-linux-network-issues-with-netstat-and-nstat/
+ static unsigned long long tcpext_ListenOverflows = 0; // times the listen queue of a socket overflowed
+ static unsigned long long tcpext_ListenDrops = 0; // SYNs to LISTEN sockets ignored
+
+ // IP TCP memory pressures
+ static unsigned long long tcpext_TCPMemoryPressures = 0;
+
+ static unsigned long long tcpext_TCPReqQFullDrop = 0;
+ static unsigned long long tcpext_TCPReqQFullDoCookies = 0;
+
+ static unsigned long long tcpext_TCPSynRetrans = 0;
+
// prepare for /proc/net/netstat parsing
if(unlikely(!arl_ipext)) {
@@ -547,127 +1650,6 @@ int do_proc_net_netstat(int update_every, usec_t dt) {
tcp_max_connections_var = rrdvar_custom_host_variable_add_and_acquire(localhost, "tcp_max_connections");
}
- // prepare for /proc/net/snmp6 parsing
-
- if(unlikely(!arl_ipv6)) {
- do_ip6_packets = config_get_boolean_ondemand("plugin:proc:/proc/net/snmp6", "ipv6 packets", CONFIG_BOOLEAN_AUTO);
- do_ip6_fragsout = config_get_boolean_ondemand("plugin:proc:/proc/net/snmp6", "ipv6 fragments sent", CONFIG_BOOLEAN_AUTO);
- do_ip6_fragsin = config_get_boolean_ondemand("plugin:proc:/proc/net/snmp6", "ipv6 fragments assembly", CONFIG_BOOLEAN_AUTO);
- do_ip6_errors = config_get_boolean_ondemand("plugin:proc:/proc/net/snmp6", "ipv6 errors", CONFIG_BOOLEAN_AUTO);
- do_ip6_udp_packets = config_get_boolean_ondemand("plugin:proc:/proc/net/snmp6", "ipv6 UDP packets", CONFIG_BOOLEAN_AUTO);
- do_ip6_udp_errors = config_get_boolean_ondemand("plugin:proc:/proc/net/snmp6", "ipv6 UDP errors", CONFIG_BOOLEAN_AUTO);
- do_ip6_udplite_packets = config_get_boolean_ondemand("plugin:proc:/proc/net/snmp6", "ipv6 UDPlite packets", CONFIG_BOOLEAN_AUTO);
- do_ip6_udplite_errors = config_get_boolean_ondemand("plugin:proc:/proc/net/snmp6", "ipv6 UDPlite errors", CONFIG_BOOLEAN_AUTO);
- do_ip6_bandwidth = config_get_boolean_ondemand("plugin:proc:/proc/net/snmp6", "bandwidth", CONFIG_BOOLEAN_AUTO);
- do_ip6_mcast = config_get_boolean_ondemand("plugin:proc:/proc/net/snmp6", "multicast bandwidth", CONFIG_BOOLEAN_AUTO);
- do_ip6_bcast = config_get_boolean_ondemand("plugin:proc:/proc/net/snmp6", "broadcast bandwidth", CONFIG_BOOLEAN_AUTO);
- do_ip6_mcast_p = config_get_boolean_ondemand("plugin:proc:/proc/net/snmp6", "multicast packets", CONFIG_BOOLEAN_AUTO);
- do_ip6_icmp = config_get_boolean_ondemand("plugin:proc:/proc/net/snmp6", "icmp", CONFIG_BOOLEAN_AUTO);
- do_ip6_icmp_redir = config_get_boolean_ondemand("plugin:proc:/proc/net/snmp6", "icmp redirects", CONFIG_BOOLEAN_AUTO);
- do_ip6_icmp_errors = config_get_boolean_ondemand("plugin:proc:/proc/net/snmp6", "icmp errors", CONFIG_BOOLEAN_AUTO);
- do_ip6_icmp_echos = config_get_boolean_ondemand("plugin:proc:/proc/net/snmp6", "icmp echos", CONFIG_BOOLEAN_AUTO);
- do_ip6_icmp_groupmemb = config_get_boolean_ondemand("plugin:proc:/proc/net/snmp6", "icmp group membership", CONFIG_BOOLEAN_AUTO);
- do_ip6_icmp_router = config_get_boolean_ondemand("plugin:proc:/proc/net/snmp6", "icmp router", CONFIG_BOOLEAN_AUTO);
- do_ip6_icmp_neighbor = config_get_boolean_ondemand("plugin:proc:/proc/net/snmp6", "icmp neighbor", CONFIG_BOOLEAN_AUTO);
- do_ip6_icmp_mldv2 = config_get_boolean_ondemand("plugin:proc:/proc/net/snmp6", "icmp mldv2", CONFIG_BOOLEAN_AUTO);
- do_ip6_icmp_types = config_get_boolean_ondemand("plugin:proc:/proc/net/snmp6", "icmp types", CONFIG_BOOLEAN_AUTO);
- do_ip6_ect = config_get_boolean_ondemand("plugin:proc:/proc/net/snmp6", "ect", CONFIG_BOOLEAN_AUTO);
-
- arl_ipv6 = arl_create("snmp6", NULL, 60);
- arl_expect(arl_ipv6, "Ip6InReceives", &Ip6InReceives);
- arl_expect(arl_ipv6, "Ip6InHdrErrors", &Ip6InHdrErrors);
- arl_expect(arl_ipv6, "Ip6InTooBigErrors", &Ip6InTooBigErrors);
- arl_expect(arl_ipv6, "Ip6InNoRoutes", &Ip6InNoRoutes);
- arl_expect(arl_ipv6, "Ip6InAddrErrors", &Ip6InAddrErrors);
- arl_expect(arl_ipv6, "Ip6InUnknownProtos", &Ip6InUnknownProtos);
- arl_expect(arl_ipv6, "Ip6InTruncatedPkts", &Ip6InTruncatedPkts);
- arl_expect(arl_ipv6, "Ip6InDiscards", &Ip6InDiscards);
- arl_expect(arl_ipv6, "Ip6InDelivers", &Ip6InDelivers);
- arl_expect(arl_ipv6, "Ip6OutForwDatagrams", &Ip6OutForwDatagrams);
- arl_expect(arl_ipv6, "Ip6OutRequests", &Ip6OutRequests);
- arl_expect(arl_ipv6, "Ip6OutDiscards", &Ip6OutDiscards);
- arl_expect(arl_ipv6, "Ip6OutNoRoutes", &Ip6OutNoRoutes);
- arl_expect(arl_ipv6, "Ip6ReasmTimeout", &Ip6ReasmTimeout);
- arl_expect(arl_ipv6, "Ip6ReasmReqds", &Ip6ReasmReqds);
- arl_expect(arl_ipv6, "Ip6ReasmOKs", &Ip6ReasmOKs);
- arl_expect(arl_ipv6, "Ip6ReasmFails", &Ip6ReasmFails);
- arl_expect(arl_ipv6, "Ip6FragOKs", &Ip6FragOKs);
- arl_expect(arl_ipv6, "Ip6FragFails", &Ip6FragFails);
- arl_expect(arl_ipv6, "Ip6FragCreates", &Ip6FragCreates);
- arl_expect(arl_ipv6, "Ip6InMcastPkts", &Ip6InMcastPkts);
- arl_expect(arl_ipv6, "Ip6OutMcastPkts", &Ip6OutMcastPkts);
- arl_expect(arl_ipv6, "Ip6InOctets", &Ip6InOctets);
- arl_expect(arl_ipv6, "Ip6OutOctets", &Ip6OutOctets);
- arl_expect(arl_ipv6, "Ip6InMcastOctets", &Ip6InMcastOctets);
- arl_expect(arl_ipv6, "Ip6OutMcastOctets", &Ip6OutMcastOctets);
- arl_expect(arl_ipv6, "Ip6InBcastOctets", &Ip6InBcastOctets);
- arl_expect(arl_ipv6, "Ip6OutBcastOctets", &Ip6OutBcastOctets);
- arl_expect(arl_ipv6, "Ip6InNoECTPkts", &Ip6InNoECTPkts);
- arl_expect(arl_ipv6, "Ip6InECT1Pkts", &Ip6InECT1Pkts);
- arl_expect(arl_ipv6, "Ip6InECT0Pkts", &Ip6InECT0Pkts);
- arl_expect(arl_ipv6, "Ip6InCEPkts", &Ip6InCEPkts);
- arl_expect(arl_ipv6, "Icmp6InMsgs", &Icmp6InMsgs);
- arl_expect(arl_ipv6, "Icmp6InErrors", &Icmp6InErrors);
- arl_expect(arl_ipv6, "Icmp6OutMsgs", &Icmp6OutMsgs);
- arl_expect(arl_ipv6, "Icmp6OutErrors", &Icmp6OutErrors);
- arl_expect(arl_ipv6, "Icmp6InCsumErrors", &Icmp6InCsumErrors);
- arl_expect(arl_ipv6, "Icmp6InDestUnreachs", &Icmp6InDestUnreachs);
- arl_expect(arl_ipv6, "Icmp6InPktTooBigs", &Icmp6InPktTooBigs);
- arl_expect(arl_ipv6, "Icmp6InTimeExcds", &Icmp6InTimeExcds);
- arl_expect(arl_ipv6, "Icmp6InParmProblems", &Icmp6InParmProblems);
- arl_expect(arl_ipv6, "Icmp6InEchos", &Icmp6InEchos);
- arl_expect(arl_ipv6, "Icmp6InEchoReplies", &Icmp6InEchoReplies);
- arl_expect(arl_ipv6, "Icmp6InGroupMembQueries", &Icmp6InGroupMembQueries);
- arl_expect(arl_ipv6, "Icmp6InGroupMembResponses", &Icmp6InGroupMembResponses);
- arl_expect(arl_ipv6, "Icmp6InGroupMembReductions", &Icmp6InGroupMembReductions);
- arl_expect(arl_ipv6, "Icmp6InRouterSolicits", &Icmp6InRouterSolicits);
- arl_expect(arl_ipv6, "Icmp6InRouterAdvertisements", &Icmp6InRouterAdvertisements);
- arl_expect(arl_ipv6, "Icmp6InNeighborSolicits", &Icmp6InNeighborSolicits);
- arl_expect(arl_ipv6, "Icmp6InNeighborAdvertisements", &Icmp6InNeighborAdvertisements);
- arl_expect(arl_ipv6, "Icmp6InRedirects", &Icmp6InRedirects);
- arl_expect(arl_ipv6, "Icmp6InMLDv2Reports", &Icmp6InMLDv2Reports);
- arl_expect(arl_ipv6, "Icmp6OutDestUnreachs", &Icmp6OutDestUnreachs);
- arl_expect(arl_ipv6, "Icmp6OutPktTooBigs", &Icmp6OutPktTooBigs);
- arl_expect(arl_ipv6, "Icmp6OutTimeExcds", &Icmp6OutTimeExcds);
- arl_expect(arl_ipv6, "Icmp6OutParmProblems", &Icmp6OutParmProblems);
- arl_expect(arl_ipv6, "Icmp6OutEchos", &Icmp6OutEchos);
- arl_expect(arl_ipv6, "Icmp6OutEchoReplies", &Icmp6OutEchoReplies);
- arl_expect(arl_ipv6, "Icmp6OutGroupMembQueries", &Icmp6OutGroupMembQueries);
- arl_expect(arl_ipv6, "Icmp6OutGroupMembResponses", &Icmp6OutGroupMembResponses);
- arl_expect(arl_ipv6, "Icmp6OutGroupMembReductions", &Icmp6OutGroupMembReductions);
- arl_expect(arl_ipv6, "Icmp6OutRouterSolicits", &Icmp6OutRouterSolicits);
- arl_expect(arl_ipv6, "Icmp6OutRouterAdvertisements", &Icmp6OutRouterAdvertisements);
- arl_expect(arl_ipv6, "Icmp6OutNeighborSolicits", &Icmp6OutNeighborSolicits);
- arl_expect(arl_ipv6, "Icmp6OutNeighborAdvertisements", &Icmp6OutNeighborAdvertisements);
- arl_expect(arl_ipv6, "Icmp6OutRedirects", &Icmp6OutRedirects);
- arl_expect(arl_ipv6, "Icmp6OutMLDv2Reports", &Icmp6OutMLDv2Reports);
- arl_expect(arl_ipv6, "Icmp6InType1", &Icmp6InType1);
- arl_expect(arl_ipv6, "Icmp6InType128", &Icmp6InType128);
- arl_expect(arl_ipv6, "Icmp6InType129", &Icmp6InType129);
- arl_expect(arl_ipv6, "Icmp6InType136", &Icmp6InType136);
- arl_expect(arl_ipv6, "Icmp6OutType1", &Icmp6OutType1);
- arl_expect(arl_ipv6, "Icmp6OutType128", &Icmp6OutType128);
- arl_expect(arl_ipv6, "Icmp6OutType129", &Icmp6OutType129);
- arl_expect(arl_ipv6, "Icmp6OutType133", &Icmp6OutType133);
- arl_expect(arl_ipv6, "Icmp6OutType135", &Icmp6OutType135);
- arl_expect(arl_ipv6, "Icmp6OutType143", &Icmp6OutType143);
- arl_expect(arl_ipv6, "Udp6InDatagrams", &Udp6InDatagrams);
- arl_expect(arl_ipv6, "Udp6NoPorts", &Udp6NoPorts);
- arl_expect(arl_ipv6, "Udp6InErrors", &Udp6InErrors);
- arl_expect(arl_ipv6, "Udp6OutDatagrams", &Udp6OutDatagrams);
- arl_expect(arl_ipv6, "Udp6RcvbufErrors", &Udp6RcvbufErrors);
- arl_expect(arl_ipv6, "Udp6SndbufErrors", &Udp6SndbufErrors);
- arl_expect(arl_ipv6, "Udp6InCsumErrors", &Udp6InCsumErrors);
- arl_expect(arl_ipv6, "Udp6IgnoredMulti", &Udp6IgnoredMulti);
- arl_expect(arl_ipv6, "UdpLite6InDatagrams", &UdpLite6InDatagrams);
- arl_expect(arl_ipv6, "UdpLite6NoPorts", &UdpLite6NoPorts);
- arl_expect(arl_ipv6, "UdpLite6InErrors", &UdpLite6InErrors);
- arl_expect(arl_ipv6, "UdpLite6OutDatagrams", &UdpLite6OutDatagrams);
- arl_expect(arl_ipv6, "UdpLite6RcvbufErrors", &UdpLite6RcvbufErrors);
- arl_expect(arl_ipv6, "UdpLite6SndbufErrors", &UdpLite6SndbufErrors);
- arl_expect(arl_ipv6, "UdpLite6InCsumErrors", &UdpLite6InCsumErrors);
- }
-
size_t lines, l, words;
// parse /proc/net/netstat
@@ -696,7 +1678,7 @@ int do_proc_net_netstat(int update_every, usec_t dt) {
words = procfile_linewords(ff_netstat, l);
if(unlikely(words < 2)) {
- error("Cannot read /proc/net/netstat IpExt line. Expected 2+ params, read %zu.", words);
+ collector_error("Cannot read /proc/net/netstat IpExt line. Expected 2+ params, read %zu.", words);
continue;
}
@@ -708,7 +1690,7 @@ int do_proc_net_netstat(int update_every, usec_t dt) {
words = procfile_linewords(ff_netstat, l);
if(unlikely(words < 2)) {
- error("Cannot read /proc/net/netstat TcpExt line. Expected 2+ params, read %zu.", words);
+ collector_error("Cannot read /proc/net/netstat TcpExt line. Expected 2+ params, read %zu.", words);
continue;
}
@@ -739,13 +1721,13 @@ int do_proc_net_netstat(int update_every, usec_t dt) {
size_t h = l++;
if(strcmp(procfile_lineword(ff_snmp, l, 0), "Ip") != 0) {
- error("Cannot read Ip line from /proc/net/snmp.");
+ collector_error("Cannot read Ip line from /proc/net/snmp.");
break;
}
words = procfile_linewords(ff_snmp, l);
if(words < 3) {
- error("Cannot read /proc/net/snmp Ip line. Expected 3+ params, read %zu.", words);
+ collector_error("Cannot read /proc/net/snmp Ip line. Expected 3+ params, read %zu.", words);
continue;
}
@@ -759,13 +1741,13 @@ int do_proc_net_netstat(int update_every, usec_t dt) {
size_t h = l++;
if(strcmp(procfile_lineword(ff_snmp, l, 0), "Icmp") != 0) {
- error("Cannot read Icmp line from /proc/net/snmp.");
+ collector_error("Cannot read Icmp line from /proc/net/snmp.");
break;
}
words = procfile_linewords(ff_snmp, l);
if(words < 3) {
- error("Cannot read /proc/net/snmp Icmp line. Expected 3+ params, read %zu.", words);
+ collector_error("Cannot read /proc/net/snmp Icmp line. Expected 3+ params, read %zu.", words);
continue;
}
@@ -779,13 +1761,13 @@ int do_proc_net_netstat(int update_every, usec_t dt) {
size_t h = l++;
if(strcmp(procfile_lineword(ff_snmp, l, 0), "IcmpMsg") != 0) {
- error("Cannot read IcmpMsg line from /proc/net/snmp.");
+ collector_error("Cannot read IcmpMsg line from /proc/net/snmp.");
break;
}
words = procfile_linewords(ff_snmp, l);
if(words < 2) {
- error("Cannot read /proc/net/snmp IcmpMsg line. Expected 2+ params, read %zu.", words);
+ collector_error("Cannot read /proc/net/snmp IcmpMsg line. Expected 2+ params, read %zu.", words);
continue;
}
@@ -799,13 +1781,13 @@ int do_proc_net_netstat(int update_every, usec_t dt) {
size_t h = l++;
if(strcmp(procfile_lineword(ff_snmp, l, 0), "Tcp") != 0) {
- error("Cannot read Tcp line from /proc/net/snmp.");
+ collector_error("Cannot read Tcp line from /proc/net/snmp.");
break;
}
words = procfile_linewords(ff_snmp, l);
if(words < 3) {
- error("Cannot read /proc/net/snmp Tcp line. Expected 3+ params, read %zu.", words);
+ collector_error("Cannot read /proc/net/snmp Tcp line. Expected 3+ params, read %zu.", words);
continue;
}
@@ -819,13 +1801,13 @@ int do_proc_net_netstat(int update_every, usec_t dt) {
size_t h = l++;
if(strcmp(procfile_lineword(ff_snmp, l, 0), "Udp") != 0) {
- error("Cannot read Udp line from /proc/net/snmp.");
+ collector_error("Cannot read Udp line from /proc/net/snmp.");
break;
}
words = procfile_linewords(ff_snmp, l);
if(words < 3) {
- error("Cannot read /proc/net/snmp Udp line. Expected 3+ params, read %zu.", words);
+ collector_error("Cannot read /proc/net/snmp Udp line. Expected 3+ params, read %zu.", words);
continue;
}
@@ -839,13 +1821,13 @@ int do_proc_net_netstat(int update_every, usec_t dt) {
size_t h = l++;
if(strcmp(procfile_lineword(ff_snmp, l, 0), "UdpLite") != 0) {
- error("Cannot read UdpLite line from /proc/net/snmp.");
+ collector_error("Cannot read UdpLite line from /proc/net/snmp.");
break;
}
words = procfile_linewords(ff_snmp, l);
if(words < 3) {
- error("Cannot read /proc/net/snmp UdpLite line. Expected 3+ params, read %zu.", words);
+ collector_error("Cannot read /proc/net/snmp UdpLite line. Expected 3+ params, read %zu.", words);
continue;
}
@@ -857,36 +1839,6 @@ int do_proc_net_netstat(int update_every, usec_t dt) {
}
}
- // parse /proc/net/snmp
-
- if(unlikely(!ff_snmp6)) {
- char filename[FILENAME_MAX + 1];
- snprintfz(filename, FILENAME_MAX, "%s%s", netdata_configured_host_prefix, "/proc/net/snmp6");
- ff_snmp6 = procfile_open(config_get("plugin:proc:/proc/net/snmp6", "filename to monitor", filename), " \t:", PROCFILE_FLAG_DEFAULT);
- if(unlikely(!ff_snmp6))
- return 1;
- }
-
- ff_snmp6 = procfile_readall(ff_snmp6);
- if(unlikely(!ff_snmp6))
- return 0; // we return 0, so that we will retry to open it next time
-
- lines = procfile_lines(ff_snmp6);
-
- arl_begin(arl_ipv6);
-
- for(l = 0; l < lines ;l++) {
- size_t words = procfile_linewords(ff_snmp6, l);
- if(unlikely(words < 2)) {
- if(unlikely(words)) error("Cannot read /proc/net/snmp6 line %zu. Expected 2 params, read %zu.", l, words);
- continue;
- }
-
- if(unlikely(arl_check(arl_ipv6,
- procfile_lineword(ff_snmp6, l, 0),
- procfile_lineword(ff_snmp6, l, 1)))) break;
- }
-
// netstat IpExt charts
if(do_bandwidth == CONFIG_BOOLEAN_YES || (do_bandwidth == CONFIG_BOOLEAN_AUTO &&
@@ -2157,954 +3109,7 @@ int do_proc_net_netstat(int update_every, usec_t dt) {
}
}
- // snmp6 charts
-
- if(do_ip6_bandwidth == CONFIG_BOOLEAN_YES || (do_ip6_bandwidth == CONFIG_BOOLEAN_AUTO &&
- (Ip6InOctets ||
- Ip6OutOctets ||
- netdata_zero_metrics_enabled == CONFIG_BOOLEAN_YES))) {
- do_ip6_bandwidth = CONFIG_BOOLEAN_YES;
- static RRDSET *st = NULL;
- static RRDDIM *rd_received = NULL,
- *rd_sent = NULL;
-
- if(unlikely(!st)) {
- st = rrdset_create_localhost(
- "system"
- , "ipv6"
- , NULL
- , "network"
- , NULL
- , "IPv6 Bandwidth"
- , "kilobits/s"
- , PLUGIN_PROC_NAME
- , PLUGIN_PROC_MODULE_NETSTAT_NAME
- , NETDATA_CHART_PRIO_SYSTEM_IPV6
- , update_every
- , RRDSET_TYPE_AREA
- );
-
- rd_received = rrddim_add(st, "InOctets", "received", 8, BITS_IN_A_KILOBIT, RRD_ALGORITHM_INCREMENTAL);
- rd_sent = rrddim_add(st, "OutOctets", "sent", -8, BITS_IN_A_KILOBIT, RRD_ALGORITHM_INCREMENTAL);
- }
-
- rrddim_set_by_pointer(st, rd_received, Ip6InOctets);
- rrddim_set_by_pointer(st, rd_sent, Ip6OutOctets);
- rrdset_done(st);
- }
-
- if(do_ip6_packets == CONFIG_BOOLEAN_YES || (do_ip6_packets == CONFIG_BOOLEAN_AUTO &&
- (Ip6InReceives ||
- Ip6OutRequests ||
- Ip6InDelivers ||
- Ip6OutForwDatagrams ||
- netdata_zero_metrics_enabled == CONFIG_BOOLEAN_YES))) {
- do_ip6_packets = CONFIG_BOOLEAN_YES;
- static RRDSET *st = NULL;
- static RRDDIM *rd_received = NULL,
- *rd_sent = NULL,
- *rd_forwarded = NULL,
- *rd_delivers = NULL;
-
- if(unlikely(!st)) {
- st = rrdset_create_localhost(
- RRD_TYPE_NET_SNMP6
- , "packets"
- , NULL
- , "packets"
- , NULL
- , "IPv6 Packets"
- , "packets/s"
- , PLUGIN_PROC_NAME
- , PLUGIN_PROC_MODULE_NETSTAT_NAME
- , NETDATA_CHART_PRIO_IPV6_PACKETS
- , update_every
- , RRDSET_TYPE_LINE
- );
-
- rd_received = rrddim_add(st, "InReceives", "received", 1, 1, RRD_ALGORITHM_INCREMENTAL);
- rd_sent = rrddim_add(st, "OutRequests", "sent", -1, 1, RRD_ALGORITHM_INCREMENTAL);
- rd_forwarded = rrddim_add(st, "OutForwDatagrams", "forwarded", -1, 1, RRD_ALGORITHM_INCREMENTAL);
- rd_delivers = rrddim_add(st, "InDelivers", "delivers", 1, 1, RRD_ALGORITHM_INCREMENTAL);
- }
-
- rrddim_set_by_pointer(st, rd_received, Ip6InReceives);
- rrddim_set_by_pointer(st, rd_sent, Ip6OutRequests);
- rrddim_set_by_pointer(st, rd_forwarded, Ip6OutForwDatagrams);
- rrddim_set_by_pointer(st, rd_delivers, Ip6InDelivers);
- rrdset_done(st);
- }
-
- if(do_ip6_fragsout == CONFIG_BOOLEAN_YES || (do_ip6_fragsout == CONFIG_BOOLEAN_AUTO &&
- (Ip6FragOKs ||
- Ip6FragFails ||
- Ip6FragCreates ||
- netdata_zero_metrics_enabled == CONFIG_BOOLEAN_YES))) {
- do_ip6_fragsout = CONFIG_BOOLEAN_YES;
- static RRDSET *st = NULL;
- static RRDDIM *rd_ok = NULL,
- *rd_failed = NULL,
- *rd_all = NULL;
-
- if(unlikely(!st)) {
- st = rrdset_create_localhost(
- RRD_TYPE_NET_SNMP6
- , "fragsout"
- , NULL
- , "fragments6"
- , NULL
- , "IPv6 Fragments Sent"
- , "packets/s"
- , PLUGIN_PROC_NAME
- , PLUGIN_PROC_MODULE_NETSTAT_NAME
- , NETDATA_CHART_PRIO_IPV6_FRAGSOUT
- , update_every
- , RRDSET_TYPE_LINE
- );
- rrdset_flag_set(st, RRDSET_FLAG_DETAIL);
-
- rd_ok = rrddim_add(st, "FragOKs", "ok", 1, 1, RRD_ALGORITHM_INCREMENTAL);
- rd_failed = rrddim_add(st, "FragFails", "failed", -1, 1, RRD_ALGORITHM_INCREMENTAL);
- rd_all = rrddim_add(st, "FragCreates", "all", 1, 1, RRD_ALGORITHM_INCREMENTAL);
- }
-
- rrddim_set_by_pointer(st, rd_ok, Ip6FragOKs);
- rrddim_set_by_pointer(st, rd_failed, Ip6FragFails);
- rrddim_set_by_pointer(st, rd_all, Ip6FragCreates);
- rrdset_done(st);
- }
-
- if(do_ip6_fragsin == CONFIG_BOOLEAN_YES || (do_ip6_fragsin == CONFIG_BOOLEAN_AUTO &&
- (Ip6ReasmOKs ||
- Ip6ReasmFails ||
- Ip6ReasmTimeout ||
- Ip6ReasmReqds ||
- netdata_zero_metrics_enabled == CONFIG_BOOLEAN_YES))) {
- do_ip6_fragsin = CONFIG_BOOLEAN_YES;
-
- static RRDSET *st = NULL;
- static RRDDIM *rd_ok = NULL,
- *rd_failed = NULL,
- *rd_timeout = NULL,
- *rd_all = NULL;
-
- if(unlikely(!st)) {
- st = rrdset_create_localhost(
- RRD_TYPE_NET_SNMP6
- , "fragsin"
- , NULL
- , "fragments6"
- , NULL
- , "IPv6 Fragments Reassembly"
- , "packets/s"
- , PLUGIN_PROC_NAME
- , PLUGIN_PROC_MODULE_NETSTAT_NAME
- , NETDATA_CHART_PRIO_IPV6_FRAGSIN
- , update_every
- , RRDSET_TYPE_LINE);
- rrdset_flag_set(st, RRDSET_FLAG_DETAIL);
-
- rd_ok = rrddim_add(st, "ReasmOKs", "ok", 1, 1, RRD_ALGORITHM_INCREMENTAL);
- rd_failed = rrddim_add(st, "ReasmFails", "failed", -1, 1, RRD_ALGORITHM_INCREMENTAL);
- rd_timeout = rrddim_add(st, "ReasmTimeout", "timeout", -1, 1, RRD_ALGORITHM_INCREMENTAL);
- rd_all = rrddim_add(st, "ReasmReqds", "all", 1, 1, RRD_ALGORITHM_INCREMENTAL);
- }
-
- rrddim_set_by_pointer(st, rd_ok, Ip6ReasmOKs);
- rrddim_set_by_pointer(st, rd_failed, Ip6ReasmFails);
- rrddim_set_by_pointer(st, rd_timeout, Ip6ReasmTimeout);
- rrddim_set_by_pointer(st, rd_all, Ip6ReasmReqds);
- rrdset_done(st);
- }
-
- if(do_ip6_errors == CONFIG_BOOLEAN_YES || (do_ip6_errors == CONFIG_BOOLEAN_AUTO &&
- (Ip6InDiscards ||
- Ip6OutDiscards ||
- Ip6InHdrErrors ||
- Ip6InAddrErrors ||
- Ip6InUnknownProtos ||
- Ip6InTooBigErrors ||
- Ip6InTruncatedPkts ||
- Ip6InNoRoutes ||
- netdata_zero_metrics_enabled == CONFIG_BOOLEAN_YES))) {
- do_ip6_errors = CONFIG_BOOLEAN_YES;
- static RRDSET *st = NULL;
- static RRDDIM *rd_InDiscards = NULL,
- *rd_OutDiscards = NULL,
- *rd_InHdrErrors = NULL,
- *rd_InAddrErrors = NULL,
- *rd_InUnknownProtos = NULL,
- *rd_InTooBigErrors = NULL,
- *rd_InTruncatedPkts = NULL,
- *rd_InNoRoutes = NULL,
- *rd_OutNoRoutes = NULL;
-
- if(unlikely(!st)) {
- st = rrdset_create_localhost(
- RRD_TYPE_NET_SNMP6
- , "errors"
- , NULL
- , "errors"
- , NULL
- , "IPv6 Errors"
- , "packets/s"
- , PLUGIN_PROC_NAME
- , PLUGIN_PROC_MODULE_NETSTAT_NAME
- , NETDATA_CHART_PRIO_IPV6_ERRORS
- , update_every
- , RRDSET_TYPE_LINE
- );
- rrdset_flag_set(st, RRDSET_FLAG_DETAIL);
-
- rd_InDiscards = rrddim_add(st, "InDiscards", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
- rd_OutDiscards = rrddim_add(st, "OutDiscards", NULL, -1, 1, RRD_ALGORITHM_INCREMENTAL);
- rd_InHdrErrors = rrddim_add(st, "InHdrErrors", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
- rd_InAddrErrors = rrddim_add(st, "InAddrErrors", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
- rd_InUnknownProtos = rrddim_add(st, "InUnknownProtos", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
- rd_InTooBigErrors = rrddim_add(st, "InTooBigErrors", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
- rd_InTruncatedPkts = rrddim_add(st, "InTruncatedPkts", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
- rd_InNoRoutes = rrddim_add(st, "InNoRoutes", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
- rd_OutNoRoutes = rrddim_add(st, "OutNoRoutes", NULL, -1, 1, RRD_ALGORITHM_INCREMENTAL);
- }
-
- rrddim_set_by_pointer(st, rd_InDiscards, Ip6InDiscards);
- rrddim_set_by_pointer(st, rd_OutDiscards, Ip6OutDiscards);
- rrddim_set_by_pointer(st, rd_InHdrErrors, Ip6InHdrErrors);
- rrddim_set_by_pointer(st, rd_InAddrErrors, Ip6InAddrErrors);
- rrddim_set_by_pointer(st, rd_InUnknownProtos, Ip6InUnknownProtos);
- rrddim_set_by_pointer(st, rd_InTooBigErrors, Ip6InTooBigErrors);
- rrddim_set_by_pointer(st, rd_InTruncatedPkts, Ip6InTruncatedPkts);
- rrddim_set_by_pointer(st, rd_InNoRoutes, Ip6InNoRoutes);
- rrddim_set_by_pointer(st, rd_OutNoRoutes, Ip6OutNoRoutes);
- rrdset_done(st);
- }
-
- if(do_ip6_udp_packets == CONFIG_BOOLEAN_YES || (do_ip6_udp_packets == CONFIG_BOOLEAN_AUTO &&
- (Udp6InDatagrams ||
- Udp6OutDatagrams ||
- netdata_zero_metrics_enabled == CONFIG_BOOLEAN_YES))) {
- do_udp_packets = CONFIG_BOOLEAN_YES;
- static RRDSET *st = NULL;
- static RRDDIM *rd_received = NULL,
- *rd_sent = NULL;
-
- if(unlikely(!st)) {
- st = rrdset_create_localhost(
- RRD_TYPE_NET_SNMP6
- , "udppackets"
- , NULL
- , "udp6"
- , NULL
- , "IPv6 UDP Packets"
- , "packets/s"
- , PLUGIN_PROC_NAME
- , PLUGIN_PROC_MODULE_NETSTAT_NAME
- , NETDATA_CHART_PRIO_IPV6_UDP_PACKETS
- , update_every
- , RRDSET_TYPE_LINE
- );
-
- rd_received = rrddim_add(st, "InDatagrams", "received", 1, 1, RRD_ALGORITHM_INCREMENTAL);
- rd_sent = rrddim_add(st, "OutDatagrams", "sent", -1, 1, RRD_ALGORITHM_INCREMENTAL);
- }
-
- rrddim_set_by_pointer(st, rd_received, Udp6InDatagrams);
- rrddim_set_by_pointer(st, rd_sent, Udp6OutDatagrams);
- rrdset_done(st);
- }
-
- if(do_ip6_udp_errors == CONFIG_BOOLEAN_YES || (do_ip6_udp_errors == CONFIG_BOOLEAN_AUTO &&
- (Udp6InErrors ||
- Udp6NoPorts ||
- Udp6RcvbufErrors ||
- Udp6SndbufErrors ||
- Udp6InCsumErrors ||
- Udp6IgnoredMulti ||
- netdata_zero_metrics_enabled == CONFIG_BOOLEAN_YES))) {
- do_ip6_udp_errors = CONFIG_BOOLEAN_YES;
- static RRDSET *st = NULL;
- static RRDDIM *rd_RcvbufErrors = NULL,
- *rd_SndbufErrors = NULL,
- *rd_InErrors = NULL,
- *rd_NoPorts = NULL,
- *rd_InCsumErrors = NULL,
- *rd_IgnoredMulti = NULL;
-
- if(unlikely(!st)) {
- st = rrdset_create_localhost(
- RRD_TYPE_NET_SNMP6
- , "udperrors"
- , NULL
- , "udp6"
- , NULL
- , "IPv6 UDP Errors"
- , "events/s"
- , PLUGIN_PROC_NAME
- , PLUGIN_PROC_MODULE_NETSTAT_NAME
- , NETDATA_CHART_PRIO_IPV6_UDP_ERRORS
- , update_every
- , RRDSET_TYPE_LINE
- );
- rrdset_flag_set(st, RRDSET_FLAG_DETAIL);
-
- rd_RcvbufErrors = rrddim_add(st, "RcvbufErrors", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
- rd_SndbufErrors = rrddim_add(st, "SndbufErrors", NULL, -1, 1, RRD_ALGORITHM_INCREMENTAL);
- rd_InErrors = rrddim_add(st, "InErrors", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
- rd_NoPorts = rrddim_add(st, "NoPorts", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
- rd_InCsumErrors = rrddim_add(st, "InCsumErrors", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
- rd_IgnoredMulti = rrddim_add(st, "IgnoredMulti", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
- }
-
- rrddim_set_by_pointer(st, rd_RcvbufErrors, Udp6RcvbufErrors);
- rrddim_set_by_pointer(st, rd_SndbufErrors, Udp6SndbufErrors);
- rrddim_set_by_pointer(st, rd_InErrors, Udp6InErrors);
- rrddim_set_by_pointer(st, rd_NoPorts, Udp6NoPorts);
- rrddim_set_by_pointer(st, rd_InCsumErrors, Udp6InCsumErrors);
- rrddim_set_by_pointer(st, rd_IgnoredMulti, Udp6IgnoredMulti);
- rrdset_done(st);
- }
-
- if(do_ip6_udplite_packets == CONFIG_BOOLEAN_YES || (do_ip6_udplite_packets == CONFIG_BOOLEAN_AUTO &&
- (UdpLite6InDatagrams ||
- UdpLite6OutDatagrams ||
- netdata_zero_metrics_enabled == CONFIG_BOOLEAN_YES))) {
- do_udplite_packets = CONFIG_BOOLEAN_YES;
- static RRDSET *st = NULL;
- static RRDDIM *rd_received = NULL,
- *rd_sent = NULL;
-
- if(unlikely(!st)) {
- st = rrdset_create_localhost(
- RRD_TYPE_NET_SNMP6
- , "udplitepackets"
- , NULL
- , "udplite6"
- , NULL
- , "IPv6 UDPlite Packets"
- , "packets/s"
- , PLUGIN_PROC_NAME
- , PLUGIN_PROC_MODULE_NETSTAT_NAME
- , NETDATA_CHART_PRIO_IPV6_UDPLITE_PACKETS
- , update_every
- , RRDSET_TYPE_LINE
- );
-
- rd_received = rrddim_add(st, "InDatagrams", "received", 1, 1, RRD_ALGORITHM_INCREMENTAL);
- rd_sent = rrddim_add(st, "OutDatagrams", "sent", -1, 1, RRD_ALGORITHM_INCREMENTAL);
- }
-
- rrddim_set_by_pointer(st, rd_received, UdpLite6InDatagrams);
- rrddim_set_by_pointer(st, rd_sent, UdpLite6OutDatagrams);
- rrdset_done(st);
- }
-
- if(do_ip6_udplite_errors == CONFIG_BOOLEAN_YES || (do_ip6_udplite_errors == CONFIG_BOOLEAN_AUTO &&
- (UdpLite6InErrors ||
- UdpLite6NoPorts ||
- UdpLite6RcvbufErrors ||
- UdpLite6SndbufErrors ||
- Udp6InCsumErrors ||
- UdpLite6InCsumErrors ||
- netdata_zero_metrics_enabled == CONFIG_BOOLEAN_YES))) {
- do_ip6_udplite_errors = CONFIG_BOOLEAN_YES;
- static RRDSET *st = NULL;
- static RRDDIM *rd_RcvbufErrors = NULL,
- *rd_SndbufErrors = NULL,
- *rd_InErrors = NULL,
- *rd_NoPorts = NULL,
- *rd_InCsumErrors = NULL;
-
- if(unlikely(!st)) {
- st = rrdset_create_localhost(
- RRD_TYPE_NET_SNMP6
- , "udpliteerrors"
- , NULL
- , "udplite6"
- , NULL
- , "IPv6 UDP Lite Errors"
- , "events/s"
- , PLUGIN_PROC_NAME
- , PLUGIN_PROC_MODULE_NETSTAT_NAME
- , NETDATA_CHART_PRIO_IPV6_UDPLITE_ERRORS
- , update_every
- , RRDSET_TYPE_LINE
- );
- rrdset_flag_set(st, RRDSET_FLAG_DETAIL);
-
- rd_RcvbufErrors = rrddim_add(st, "RcvbufErrors", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
- rd_SndbufErrors = rrddim_add(st, "SndbufErrors", NULL, -1, 1, RRD_ALGORITHM_INCREMENTAL);
- rd_InErrors = rrddim_add(st, "InErrors", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
- rd_NoPorts = rrddim_add(st, "NoPorts", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
- rd_InCsumErrors = rrddim_add(st, "InCsumErrors", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
- }
-
- rrddim_set_by_pointer(st, rd_InErrors, UdpLite6InErrors);
- rrddim_set_by_pointer(st, rd_NoPorts, UdpLite6NoPorts);
- rrddim_set_by_pointer(st, rd_RcvbufErrors, UdpLite6RcvbufErrors);
- rrddim_set_by_pointer(st, rd_SndbufErrors, UdpLite6SndbufErrors);
- rrddim_set_by_pointer(st, rd_InCsumErrors, UdpLite6InCsumErrors);
- rrdset_done(st);
- }
-
- if(do_ip6_mcast == CONFIG_BOOLEAN_YES || (do_ip6_mcast == CONFIG_BOOLEAN_AUTO &&
- (Ip6OutMcastOctets ||
- Ip6InMcastOctets ||
- netdata_zero_metrics_enabled == CONFIG_BOOLEAN_YES))) {
- do_ip6_mcast = CONFIG_BOOLEAN_YES;
- static RRDSET *st = NULL;
- static RRDDIM *rd_Ip6InMcastOctets = NULL,
- *rd_Ip6OutMcastOctets = NULL;
-
- if(unlikely(!st)) {
- st = rrdset_create_localhost(
- RRD_TYPE_NET_SNMP6
- , "mcast"
- , NULL
- , "multicast6"
- , NULL
- , "IPv6 Multicast Bandwidth"
- , "kilobits/s"
- , PLUGIN_PROC_NAME
- , PLUGIN_PROC_MODULE_NETSTAT_NAME
- , NETDATA_CHART_PRIO_IPV6_MCAST
- , update_every
- , RRDSET_TYPE_AREA
- );
- rrdset_flag_set(st, RRDSET_FLAG_DETAIL);
-
- rd_Ip6InMcastOctets = rrddim_add(st, "InMcastOctets", "received", 8, BITS_IN_A_KILOBIT, RRD_ALGORITHM_INCREMENTAL);
- rd_Ip6OutMcastOctets = rrddim_add(st, "OutMcastOctets", "sent", -8, BITS_IN_A_KILOBIT, RRD_ALGORITHM_INCREMENTAL);
- }
-
- rrddim_set_by_pointer(st, rd_Ip6InMcastOctets, Ip6InMcastOctets);
- rrddim_set_by_pointer(st, rd_Ip6OutMcastOctets, Ip6OutMcastOctets);
- rrdset_done(st);
- }
-
- if(do_ip6_bcast == CONFIG_BOOLEAN_YES || (do_ip6_bcast == CONFIG_BOOLEAN_AUTO &&
- (Ip6OutBcastOctets ||
- Ip6InBcastOctets ||
- netdata_zero_metrics_enabled == CONFIG_BOOLEAN_YES))) {
- do_ip6_bcast = CONFIG_BOOLEAN_YES;
- static RRDSET *st = NULL;
- static RRDDIM *rd_Ip6InBcastOctets = NULL,
- *rd_Ip6OutBcastOctets = NULL;
-
- if(unlikely(!st)) {
- st = rrdset_create_localhost(
- RRD_TYPE_NET_SNMP6
- , "bcast"
- , NULL
- , "broadcast6"
- , NULL
- , "IPv6 Broadcast Bandwidth"
- , "kilobits/s"
- , PLUGIN_PROC_NAME
- , PLUGIN_PROC_MODULE_NETSTAT_NAME
- , NETDATA_CHART_PRIO_IPV6_BCAST
- , update_every
- , RRDSET_TYPE_AREA
- );
- rrdset_flag_set(st, RRDSET_FLAG_DETAIL);
-
- rd_Ip6InBcastOctets = rrddim_add(st, "InBcastOctets", "received", 8, BITS_IN_A_KILOBIT, RRD_ALGORITHM_INCREMENTAL);
- rd_Ip6OutBcastOctets = rrddim_add(st, "OutBcastOctets", "sent", -8, BITS_IN_A_KILOBIT, RRD_ALGORITHM_INCREMENTAL);
- }
-
- rrddim_set_by_pointer(st, rd_Ip6InBcastOctets, Ip6InBcastOctets);
- rrddim_set_by_pointer(st, rd_Ip6OutBcastOctets, Ip6OutBcastOctets);
- rrdset_done(st);
- }
-
- if(do_ip6_mcast_p == CONFIG_BOOLEAN_YES || (do_ip6_mcast_p == CONFIG_BOOLEAN_AUTO &&
- (Ip6OutMcastPkts ||
- Ip6InMcastPkts ||
- netdata_zero_metrics_enabled == CONFIG_BOOLEAN_YES))) {
- do_ip6_mcast_p = CONFIG_BOOLEAN_YES;
- static RRDSET *st = NULL;
- static RRDDIM *rd_Ip6InMcastPkts = NULL,
- *rd_Ip6OutMcastPkts = NULL;
-
- if(unlikely(!st)) {
- st = rrdset_create_localhost(
- RRD_TYPE_NET_SNMP6
- , "mcastpkts"
- , NULL
- , "multicast6"
- , NULL
- , "IPv6 Multicast Packets"
- , "packets/s"
- , PLUGIN_PROC_NAME
- , PLUGIN_PROC_MODULE_NETSTAT_NAME
- , NETDATA_CHART_PRIO_IPV6_MCAST_PACKETS
- , update_every
- , RRDSET_TYPE_LINE
- );
- rrdset_flag_set(st, RRDSET_FLAG_DETAIL);
-
- rd_Ip6InMcastPkts = rrddim_add(st, "InMcastPkts", "received", 1, 1, RRD_ALGORITHM_INCREMENTAL);
- rd_Ip6OutMcastPkts = rrddim_add(st, "OutMcastPkts", "sent", -1, 1, RRD_ALGORITHM_INCREMENTAL);
- }
-
- rrddim_set_by_pointer(st, rd_Ip6InMcastPkts, Ip6InMcastPkts);
- rrddim_set_by_pointer(st, rd_Ip6OutMcastPkts, Ip6OutMcastPkts);
- rrdset_done(st);
- }
-
- if(do_ip6_icmp == CONFIG_BOOLEAN_YES || (do_ip6_icmp == CONFIG_BOOLEAN_AUTO &&
- (Icmp6InMsgs ||
- Icmp6OutMsgs ||
- netdata_zero_metrics_enabled == CONFIG_BOOLEAN_YES))) {
- do_ip6_icmp = CONFIG_BOOLEAN_YES;
- static RRDSET *st = NULL;
- static RRDDIM *rd_Icmp6InMsgs = NULL,
- *rd_Icmp6OutMsgs = NULL;
-
- if(unlikely(!st)) {
- st = rrdset_create_localhost(
- RRD_TYPE_NET_SNMP6
- , "icmp"
- , NULL
- , "icmp6"
- , NULL
- , "IPv6 ICMP Messages"
- , "messages/s"
- , PLUGIN_PROC_NAME
- , PLUGIN_PROC_MODULE_NETSTAT_NAME
- , NETDATA_CHART_PRIO_IPV6_ICMP
- , update_every
- , RRDSET_TYPE_LINE
- );
-
- rd_Icmp6InMsgs = rrddim_add(st, "InMsgs", "received", 1, 1, RRD_ALGORITHM_INCREMENTAL);
- rd_Icmp6OutMsgs = rrddim_add(st, "OutMsgs", "sent", -1, 1, RRD_ALGORITHM_INCREMENTAL);
- }
-
- rrddim_set_by_pointer(st, rd_Icmp6InMsgs, Icmp6InMsgs);
- rrddim_set_by_pointer(st, rd_Icmp6OutMsgs, Icmp6OutMsgs);
- rrdset_done(st);
- }
-
- if(do_ip6_icmp_redir == CONFIG_BOOLEAN_YES || (do_ip6_icmp_redir == CONFIG_BOOLEAN_AUTO &&
- (Icmp6InRedirects ||
- Icmp6OutRedirects ||
- netdata_zero_metrics_enabled == CONFIG_BOOLEAN_YES))) {
- do_ip6_icmp_redir = CONFIG_BOOLEAN_YES;
- static RRDSET *st = NULL;
- static RRDDIM *rd_Icmp6InRedirects = NULL,
- *rd_Icmp6OutRedirects = NULL;
-
- if(unlikely(!st)) {
- st = rrdset_create_localhost(
- RRD_TYPE_NET_SNMP6
- , "icmpredir"
- , NULL
- , "icmp6"
- , NULL
- , "IPv6 ICMP Redirects"
- , "redirects/s"
- , PLUGIN_PROC_NAME
- , PLUGIN_PROC_MODULE_NETSTAT_NAME
- , NETDATA_CHART_PRIO_IPV6_ICMP_REDIR
- , update_every
- , RRDSET_TYPE_LINE
- );
-
- rd_Icmp6InRedirects = rrddim_add(st, "InRedirects", "received", 1, 1, RRD_ALGORITHM_INCREMENTAL);
- rd_Icmp6OutRedirects = rrddim_add(st, "OutRedirects", "sent", -1, 1, RRD_ALGORITHM_INCREMENTAL);
- }
-
- rrddim_set_by_pointer(st, rd_Icmp6InRedirects, Icmp6InRedirects);
- rrddim_set_by_pointer(st, rd_Icmp6OutRedirects, Icmp6OutRedirects);
- rrdset_done(st);
- }
-
- if(do_ip6_icmp_errors == CONFIG_BOOLEAN_YES || (do_ip6_icmp_errors == CONFIG_BOOLEAN_AUTO &&
- (Icmp6InErrors ||
- Icmp6OutErrors ||
- Icmp6InCsumErrors ||
- Icmp6InDestUnreachs ||
- Icmp6InPktTooBigs ||
- Icmp6InTimeExcds ||
- Icmp6InParmProblems ||
- Icmp6OutDestUnreachs ||
- Icmp6OutPktTooBigs ||
- Icmp6OutTimeExcds ||
- Icmp6OutParmProblems ||
- netdata_zero_metrics_enabled == CONFIG_BOOLEAN_YES))) {
- do_ip6_icmp_errors = CONFIG_BOOLEAN_YES;
- static RRDSET *st = NULL;
- static RRDDIM *rd_InErrors = NULL,
- *rd_OutErrors = NULL,
- *rd_InCsumErrors = NULL,
- *rd_InDestUnreachs = NULL,
- *rd_InPktTooBigs = NULL,
- *rd_InTimeExcds = NULL,
- *rd_InParmProblems = NULL,
- *rd_OutDestUnreachs = NULL,
- *rd_OutPktTooBigs = NULL,
- *rd_OutTimeExcds = NULL,
- *rd_OutParmProblems = NULL;
-
- if(unlikely(!st)) {
- st = rrdset_create_localhost(
- RRD_TYPE_NET_SNMP6
- , "icmperrors"
- , NULL
- , "icmp6"
- , NULL
- , "IPv6 ICMP Errors"
- , "errors/s"
- , PLUGIN_PROC_NAME
- , PLUGIN_PROC_MODULE_NETSTAT_NAME
- , NETDATA_CHART_PRIO_IPV6_ICMP_ERRORS
- , update_every
- , RRDSET_TYPE_LINE
- );
-
- rd_InErrors = rrddim_add(st, "InErrors", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
- rd_OutErrors = rrddim_add(st, "OutErrors", NULL, -1, 1, RRD_ALGORITHM_INCREMENTAL);
- rd_InCsumErrors = rrddim_add(st, "InCsumErrors", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
- rd_InDestUnreachs = rrddim_add(st, "InDestUnreachs", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
- rd_InPktTooBigs = rrddim_add(st, "InPktTooBigs", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
- rd_InTimeExcds = rrddim_add(st, "InTimeExcds", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
- rd_InParmProblems = rrddim_add(st, "InParmProblems", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
- rd_OutDestUnreachs = rrddim_add(st, "OutDestUnreachs", NULL, -1, 1, RRD_ALGORITHM_INCREMENTAL);
- rd_OutPktTooBigs = rrddim_add(st, "OutPktTooBigs", NULL, -1, 1, RRD_ALGORITHM_INCREMENTAL);
- rd_OutTimeExcds = rrddim_add(st, "OutTimeExcds", NULL, -1, 1, RRD_ALGORITHM_INCREMENTAL);
- rd_OutParmProblems = rrddim_add(st, "OutParmProblems", NULL, -1, 1, RRD_ALGORITHM_INCREMENTAL);
- }
-
- rrddim_set_by_pointer(st, rd_InErrors, Icmp6InErrors);
- rrddim_set_by_pointer(st, rd_OutErrors, Icmp6OutErrors);
- rrddim_set_by_pointer(st, rd_InCsumErrors, Icmp6InCsumErrors);
- rrddim_set_by_pointer(st, rd_InDestUnreachs, Icmp6InDestUnreachs);
- rrddim_set_by_pointer(st, rd_InPktTooBigs, Icmp6InPktTooBigs);
- rrddim_set_by_pointer(st, rd_InTimeExcds, Icmp6InTimeExcds);
- rrddim_set_by_pointer(st, rd_InParmProblems, Icmp6InParmProblems);
- rrddim_set_by_pointer(st, rd_OutDestUnreachs, Icmp6OutDestUnreachs);
- rrddim_set_by_pointer(st, rd_OutPktTooBigs, Icmp6OutPktTooBigs);
- rrddim_set_by_pointer(st, rd_OutTimeExcds, Icmp6OutTimeExcds);
- rrddim_set_by_pointer(st, rd_OutParmProblems, Icmp6OutParmProblems);
- rrdset_done(st);
- }
-
- if(do_ip6_icmp_echos == CONFIG_BOOLEAN_YES || (do_ip6_icmp_echos == CONFIG_BOOLEAN_AUTO &&
- (Icmp6InEchos ||
- Icmp6OutEchos ||
- Icmp6InEchoReplies ||
- Icmp6OutEchoReplies ||
- netdata_zero_metrics_enabled == CONFIG_BOOLEAN_YES))) {
- do_ip6_icmp_echos = CONFIG_BOOLEAN_YES;
- static RRDSET *st = NULL;
- static RRDDIM *rd_InEchos = NULL,
- *rd_OutEchos = NULL,
- *rd_InEchoReplies = NULL,
- *rd_OutEchoReplies = NULL;
-
- if(unlikely(!st)) {
- st = rrdset_create_localhost(
- RRD_TYPE_NET_SNMP6
- , "icmpechos"
- , NULL
- , "icmp6"
- , NULL
- , "IPv6 ICMP Echo"
- , "messages/s"
- , PLUGIN_PROC_NAME
- , PLUGIN_PROC_MODULE_NETSTAT_NAME
- , NETDATA_CHART_PRIO_IPV6_ICMP_ECHOS
- , update_every
- , RRDSET_TYPE_LINE
- );
-
- rd_InEchos = rrddim_add(st, "InEchos", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
- rd_OutEchos = rrddim_add(st, "OutEchos", NULL, -1, 1, RRD_ALGORITHM_INCREMENTAL);
- rd_InEchoReplies = rrddim_add(st, "InEchoReplies", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
- rd_OutEchoReplies = rrddim_add(st, "OutEchoReplies", NULL, -1, 1, RRD_ALGORITHM_INCREMENTAL);
- }
-
- rrddim_set_by_pointer(st, rd_InEchos, Icmp6InEchos);
- rrddim_set_by_pointer(st, rd_OutEchos, Icmp6OutEchos);
- rrddim_set_by_pointer(st, rd_InEchoReplies, Icmp6InEchoReplies);
- rrddim_set_by_pointer(st, rd_OutEchoReplies, Icmp6OutEchoReplies);
- rrdset_done(st);
- }
-
- if(do_ip6_icmp_groupmemb == CONFIG_BOOLEAN_YES || (do_ip6_icmp_groupmemb == CONFIG_BOOLEAN_AUTO &&
- (Icmp6InGroupMembQueries ||
- Icmp6OutGroupMembQueries ||
- Icmp6InGroupMembResponses ||
- Icmp6OutGroupMembResponses ||
- Icmp6InGroupMembReductions ||
- Icmp6OutGroupMembReductions ||
- netdata_zero_metrics_enabled == CONFIG_BOOLEAN_YES))) {
- do_ip6_icmp_groupmemb = CONFIG_BOOLEAN_YES;
- static RRDSET *st = NULL;
- static RRDDIM *rd_InQueries = NULL,
- *rd_OutQueries = NULL,
- *rd_InResponses = NULL,
- *rd_OutResponses = NULL,
- *rd_InReductions = NULL,
- *rd_OutReductions = NULL;
-
- if(unlikely(!st)) {
- st = rrdset_create_localhost(
- RRD_TYPE_NET_SNMP6
- , "groupmemb"
- , NULL
- , "icmp6"
- , NULL
- , "IPv6 ICMP Group Membership"
- , "messages/s"
- , PLUGIN_PROC_NAME
- , PLUGIN_PROC_MODULE_NETSTAT_NAME
- , NETDATA_CHART_PRIO_IPV6_ICMP_GROUPMEMB
- , update_every
- , RRDSET_TYPE_LINE);
-
- rd_InQueries = rrddim_add(st, "InQueries", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
- rd_OutQueries = rrddim_add(st, "OutQueries", NULL, -1, 1, RRD_ALGORITHM_INCREMENTAL);
- rd_InResponses = rrddim_add(st, "InResponses", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
- rd_OutResponses = rrddim_add(st, "OutResponses", NULL, -1, 1, RRD_ALGORITHM_INCREMENTAL);
- rd_InReductions = rrddim_add(st, "InReductions", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
- rd_OutReductions = rrddim_add(st, "OutReductions", NULL, -1, 1, RRD_ALGORITHM_INCREMENTAL);
- }
-
- rrddim_set_by_pointer(st, rd_InQueries, Icmp6InGroupMembQueries);
- rrddim_set_by_pointer(st, rd_OutQueries, Icmp6OutGroupMembQueries);
- rrddim_set_by_pointer(st, rd_InResponses, Icmp6InGroupMembResponses);
- rrddim_set_by_pointer(st, rd_OutResponses, Icmp6OutGroupMembResponses);
- rrddim_set_by_pointer(st, rd_InReductions, Icmp6InGroupMembReductions);
- rrddim_set_by_pointer(st, rd_OutReductions, Icmp6OutGroupMembReductions);
- rrdset_done(st);
- }
-
- if(do_ip6_icmp_router == CONFIG_BOOLEAN_YES || (do_ip6_icmp_router == CONFIG_BOOLEAN_AUTO &&
- (Icmp6InRouterSolicits ||
- Icmp6OutRouterSolicits ||
- Icmp6InRouterAdvertisements ||
- Icmp6OutRouterAdvertisements ||
- netdata_zero_metrics_enabled == CONFIG_BOOLEAN_YES))) {
- do_ip6_icmp_router = CONFIG_BOOLEAN_YES;
- static RRDSET *st = NULL;
- static RRDDIM *rd_InSolicits = NULL,
- *rd_OutSolicits = NULL,
- *rd_InAdvertisements = NULL,
- *rd_OutAdvertisements = NULL;
-
- if(unlikely(!st)) {
- st = rrdset_create_localhost(
- RRD_TYPE_NET_SNMP6
- , "icmprouter"
- , NULL
- , "icmp6"
- , NULL
- , "IPv6 Router Messages"
- , "messages/s"
- , PLUGIN_PROC_NAME
- , PLUGIN_PROC_MODULE_NETSTAT_NAME
- , NETDATA_CHART_PRIO_IPV6_ICMP_ROUTER
- , update_every
- , RRDSET_TYPE_LINE
- );
-
- rd_InSolicits = rrddim_add(st, "InSolicits", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
- rd_OutSolicits = rrddim_add(st, "OutSolicits", NULL, -1, 1, RRD_ALGORITHM_INCREMENTAL);
- rd_InAdvertisements = rrddim_add(st, "InAdvertisements", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
- rd_OutAdvertisements = rrddim_add(st, "OutAdvertisements", NULL, -1, 1, RRD_ALGORITHM_INCREMENTAL);
- }
-
- rrddim_set_by_pointer(st, rd_InSolicits, Icmp6InRouterSolicits);
- rrddim_set_by_pointer(st, rd_OutSolicits, Icmp6OutRouterSolicits);
- rrddim_set_by_pointer(st, rd_InAdvertisements, Icmp6InRouterAdvertisements);
- rrddim_set_by_pointer(st, rd_OutAdvertisements, Icmp6OutRouterAdvertisements);
- rrdset_done(st);
- }
-
- if(do_ip6_icmp_neighbor == CONFIG_BOOLEAN_YES || (do_ip6_icmp_neighbor == CONFIG_BOOLEAN_AUTO &&
- (Icmp6InNeighborSolicits ||
- Icmp6OutNeighborSolicits ||
- Icmp6InNeighborAdvertisements ||
- Icmp6OutNeighborAdvertisements ||
- netdata_zero_metrics_enabled == CONFIG_BOOLEAN_YES))) {
- do_ip6_icmp_neighbor = CONFIG_BOOLEAN_YES;
- static RRDSET *st = NULL;
- static RRDDIM *rd_InSolicits = NULL,
- *rd_OutSolicits = NULL,
- *rd_InAdvertisements = NULL,
- *rd_OutAdvertisements = NULL;
-
- if(unlikely(!st)) {
- st = rrdset_create_localhost(
- RRD_TYPE_NET_SNMP6
- , "icmpneighbor"
- , NULL
- , "icmp6"
- , NULL
- , "IPv6 Neighbor Messages"
- , "messages/s"
- , PLUGIN_PROC_NAME
- , PLUGIN_PROC_MODULE_NETSTAT_NAME
- , NETDATA_CHART_PRIO_IPV6_ICMP_NEIGHBOR
- , update_every
- , RRDSET_TYPE_LINE
- );
-
- rd_InSolicits = rrddim_add(st, "InSolicits", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
- rd_OutSolicits = rrddim_add(st, "OutSolicits", NULL, -1, 1, RRD_ALGORITHM_INCREMENTAL);
- rd_InAdvertisements = rrddim_add(st, "InAdvertisements", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
- rd_OutAdvertisements = rrddim_add(st, "OutAdvertisements", NULL, -1, 1, RRD_ALGORITHM_INCREMENTAL);
- }
-
- rrddim_set_by_pointer(st, rd_InSolicits, Icmp6InNeighborSolicits);
- rrddim_set_by_pointer(st, rd_OutSolicits, Icmp6OutNeighborSolicits);
- rrddim_set_by_pointer(st, rd_InAdvertisements, Icmp6InNeighborAdvertisements);
- rrddim_set_by_pointer(st, rd_OutAdvertisements, Icmp6OutNeighborAdvertisements);
- rrdset_done(st);
- }
-
- if(do_ip6_icmp_mldv2 == CONFIG_BOOLEAN_YES || (do_ip6_icmp_mldv2 == CONFIG_BOOLEAN_AUTO &&
- (Icmp6InMLDv2Reports ||
- Icmp6OutMLDv2Reports ||
- netdata_zero_metrics_enabled == CONFIG_BOOLEAN_YES))) {
- do_ip6_icmp_mldv2 = CONFIG_BOOLEAN_YES;
- static RRDSET *st = NULL;
- static RRDDIM *rd_InMLDv2Reports = NULL,
- *rd_OutMLDv2Reports = NULL;
-
- if(unlikely(!st)) {
- st = rrdset_create_localhost(
- RRD_TYPE_NET_SNMP6
- , "icmpmldv2"
- , NULL
- , "icmp6"
- , NULL
- , "IPv6 ICMP MLDv2 Reports"
- , "reports/s"
- , PLUGIN_PROC_NAME
- , PLUGIN_PROC_MODULE_NETSTAT_NAME
- , NETDATA_CHART_PRIO_IPV6_ICMP_LDV2
- , update_every
- , RRDSET_TYPE_LINE
- );
-
- rd_InMLDv2Reports = rrddim_add(st, "InMLDv2Reports", "received", 1, 1, RRD_ALGORITHM_INCREMENTAL);
- rd_OutMLDv2Reports = rrddim_add(st, "OutMLDv2Reports", "sent", -1, 1, RRD_ALGORITHM_INCREMENTAL);
- }
-
- rrddim_set_by_pointer(st, rd_InMLDv2Reports, Icmp6InMLDv2Reports);
- rrddim_set_by_pointer(st, rd_OutMLDv2Reports, Icmp6OutMLDv2Reports);
- rrdset_done(st);
- }
-
- if(do_ip6_icmp_types == CONFIG_BOOLEAN_YES || (do_ip6_icmp_types == CONFIG_BOOLEAN_AUTO &&
- (Icmp6InType1 ||
- Icmp6InType128 ||
- Icmp6InType129 ||
- Icmp6InType136 ||
- Icmp6OutType1 ||
- Icmp6OutType128 ||
- Icmp6OutType129 ||
- Icmp6OutType133 ||
- Icmp6OutType135 ||
- Icmp6OutType143 ||
- netdata_zero_metrics_enabled == CONFIG_BOOLEAN_YES))) {
- do_ip6_icmp_types = CONFIG_BOOLEAN_YES;
- static RRDSET *st = NULL;
- static RRDDIM *rd_InType1 = NULL,
- *rd_InType128 = NULL,
- *rd_InType129 = NULL,
- *rd_InType136 = NULL,
- *rd_OutType1 = NULL,
- *rd_OutType128 = NULL,
- *rd_OutType129 = NULL,
- *rd_OutType133 = NULL,
- *rd_OutType135 = NULL,
- *rd_OutType143 = NULL;
-
- if(unlikely(!st)) {
- st = rrdset_create_localhost(
- RRD_TYPE_NET_SNMP6
- , "icmptypes"
- , NULL
- , "icmp6"
- , NULL
- , "IPv6 ICMP Types"
- , "messages/s"
- , PLUGIN_PROC_NAME
- , PLUGIN_PROC_MODULE_NETSTAT_NAME
- , NETDATA_CHART_PRIO_IPV6_ICMP_TYPES
- , update_every
- , RRDSET_TYPE_LINE
- );
-
- rd_InType1 = rrddim_add(st, "InType1", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
- rd_InType128 = rrddim_add(st, "InType128", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
- rd_InType129 = rrddim_add(st, "InType129", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
- rd_InType136 = rrddim_add(st, "InType136", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
- rd_OutType1 = rrddim_add(st, "OutType1", NULL, -1, 1, RRD_ALGORITHM_INCREMENTAL);
- rd_OutType128 = rrddim_add(st, "OutType128", NULL, -1, 1, RRD_ALGORITHM_INCREMENTAL);
- rd_OutType129 = rrddim_add(st, "OutType129", NULL, -1, 1, RRD_ALGORITHM_INCREMENTAL);
- rd_OutType133 = rrddim_add(st, "OutType133", NULL, -1, 1, RRD_ALGORITHM_INCREMENTAL);
- rd_OutType135 = rrddim_add(st, "OutType135", NULL, -1, 1, RRD_ALGORITHM_INCREMENTAL);
- rd_OutType143 = rrddim_add(st, "OutType143", NULL, -1, 1, RRD_ALGORITHM_INCREMENTAL);
- }
-
- rrddim_set_by_pointer(st, rd_InType1, Icmp6InType1);
- rrddim_set_by_pointer(st, rd_InType128, Icmp6InType128);
- rrddim_set_by_pointer(st, rd_InType129, Icmp6InType129);
- rrddim_set_by_pointer(st, rd_InType136, Icmp6InType136);
- rrddim_set_by_pointer(st, rd_OutType1, Icmp6OutType1);
- rrddim_set_by_pointer(st, rd_OutType128, Icmp6OutType128);
- rrddim_set_by_pointer(st, rd_OutType129, Icmp6OutType129);
- rrddim_set_by_pointer(st, rd_OutType133, Icmp6OutType133);
- rrddim_set_by_pointer(st, rd_OutType135, Icmp6OutType135);
- rrddim_set_by_pointer(st, rd_OutType143, Icmp6OutType143);
- rrdset_done(st);
- }
-
- if(do_ip6_ect == CONFIG_BOOLEAN_YES || (do_ip6_ect == CONFIG_BOOLEAN_AUTO &&
- (Ip6InNoECTPkts ||
- Ip6InECT1Pkts ||
- Ip6InECT0Pkts ||
- Ip6InCEPkts ||
- netdata_zero_metrics_enabled == CONFIG_BOOLEAN_YES))) {
- do_ip6_ect = CONFIG_BOOLEAN_YES;
- static RRDSET *st = NULL;
- static RRDDIM *rd_InNoECTPkts = NULL,
- *rd_InECT1Pkts = NULL,
- *rd_InECT0Pkts = NULL,
- *rd_InCEPkts = NULL;
-
- if(unlikely(!st)) {
- st = rrdset_create_localhost(
- RRD_TYPE_NET_SNMP6
- , "ect"
- , NULL
- , "packets"
- , NULL
- , "IPv6 ECT Packets"
- , "packets/s"
- , PLUGIN_PROC_NAME
- , PLUGIN_PROC_MODULE_NETSTAT_NAME
- , NETDATA_CHART_PRIO_IPV6_ECT
- , update_every
- , RRDSET_TYPE_LINE
- );
-
- rd_InNoECTPkts = rrddim_add(st, "InNoECTPkts", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
- rd_InECT1Pkts = rrddim_add(st, "InECT1Pkts", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
- rd_InECT0Pkts = rrddim_add(st, "InECT0Pkts", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
- rd_InCEPkts = rrddim_add(st, "InCEPkts", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
- }
-
- rrddim_set_by_pointer(st, rd_InNoECTPkts, Ip6InNoECTPkts);
- rrddim_set_by_pointer(st, rd_InECT1Pkts, Ip6InECT1Pkts);
- rrddim_set_by_pointer(st, rd_InECT0Pkts, Ip6InECT0Pkts);
- rrddim_set_by_pointer(st, rd_InCEPkts, Ip6InCEPkts);
- rrdset_done(st);
- }
-
+ do_proc_net_snmp6(update_every);
+
return 0;
}
diff --git a/collectors/proc.plugin/proc_net_rpc_nfs.c b/collectors/proc.plugin/proc_net_rpc_nfs.c
index b1ff4e05..0ab9d28b 100644
--- a/collectors/proc.plugin/proc_net_rpc_nfs.c
+++ b/collectors/proc.plugin/proc_net_rpc_nfs.c
@@ -183,7 +183,7 @@ int do_proc_net_rpc_nfs(int update_every, usec_t dt) {
if(do_net == 1 && strcmp(type, "net") == 0) {
if(words < 5) {
- error("%s line of /proc/net/rpc/nfs has %zu words, expected %d", type, words, 5);
+ collector_error("%s line of /proc/net/rpc/nfs has %zu words, expected %d", type, words, 5);
continue;
}
@@ -198,7 +198,7 @@ int do_proc_net_rpc_nfs(int update_every, usec_t dt) {
}
else if(do_rpc == 1 && strcmp(type, "rpc") == 0) {
if(words < 4) {
- error("%s line of /proc/net/rpc/nfs has %zu words, expected %d", type, words, 6);
+ collector_error("%s line of /proc/net/rpc/nfs has %zu words, expected %d", type, words, 6);
continue;
}
@@ -224,7 +224,7 @@ int do_proc_net_rpc_nfs(int update_every, usec_t dt) {
if(sum == 0ULL) {
if(!proc2_warning) {
- error("Disabling /proc/net/rpc/nfs v2 procedure calls chart. It seems unused on this machine. It will be enabled automatically when found with data in it.");
+ collector_error("Disabling /proc/net/rpc/nfs v2 procedure calls chart. It seems unused on this machine. It will be enabled automatically when found with data in it.");
proc2_warning = 1;
}
do_proc2 = 0;
@@ -245,7 +245,7 @@ int do_proc_net_rpc_nfs(int update_every, usec_t dt) {
if(sum == 0ULL) {
if(!proc3_warning) {
- info("Disabling /proc/net/rpc/nfs v3 procedure calls chart. It seems unused on this machine. It will be enabled automatically when found with data in it.");
+ collector_info("Disabling /proc/net/rpc/nfs v3 procedure calls chart. It seems unused on this machine. It will be enabled automatically when found with data in it.");
proc3_warning = 1;
}
do_proc3 = 0;
@@ -266,7 +266,7 @@ int do_proc_net_rpc_nfs(int update_every, usec_t dt) {
if(sum == 0ULL) {
if(!proc4_warning) {
- info("Disabling /proc/net/rpc/nfs v4 procedure calls chart. It seems unused on this machine. It will be enabled automatically when found with data in it.");
+ collector_info("Disabling /proc/net/rpc/nfs v4 procedure calls chart. It seems unused on this machine. It will be enabled automatically when found with data in it.");
proc4_warning = 1;
}
do_proc4 = 0;
diff --git a/collectors/proc.plugin/proc_net_rpc_nfsd.c b/collectors/proc.plugin/proc_net_rpc_nfsd.c
index bd1da888..faa6b5c4 100644
--- a/collectors/proc.plugin/proc_net_rpc_nfsd.c
+++ b/collectors/proc.plugin/proc_net_rpc_nfsd.c
@@ -282,7 +282,7 @@ int do_proc_net_rpc_nfsd(int update_every, usec_t dt) {
if(do_rc == 1 && strcmp(type, "rc") == 0) {
if(unlikely(words < 4)) {
- error("%s line of /proc/net/rpc/nfsd has %zu words, expected %d", type, words, 4);
+ collector_error("%s line of /proc/net/rpc/nfsd has %zu words, expected %d", type, words, 4);
continue;
}
@@ -296,7 +296,7 @@ int do_proc_net_rpc_nfsd(int update_every, usec_t dt) {
}
else if(do_fh == 1 && strcmp(type, "fh") == 0) {
if(unlikely(words < 6)) {
- error("%s line of /proc/net/rpc/nfsd has %zu words, expected %d", type, words, 6);
+ collector_error("%s line of /proc/net/rpc/nfsd has %zu words, expected %d", type, words, 6);
continue;
}
@@ -309,7 +309,7 @@ int do_proc_net_rpc_nfsd(int update_every, usec_t dt) {
}
else if(do_io == 1 && strcmp(type, "io") == 0) {
if(unlikely(words < 3)) {
- error("%s line of /proc/net/rpc/nfsd has %zu words, expected %d", type, words, 3);
+ collector_error("%s line of /proc/net/rpc/nfsd has %zu words, expected %d", type, words, 3);
continue;
}
@@ -322,7 +322,7 @@ int do_proc_net_rpc_nfsd(int update_every, usec_t dt) {
}
else if(do_th == 1 && strcmp(type, "th") == 0) {
if(unlikely(words < 13)) {
- error("%s line of /proc/net/rpc/nfsd has %zu words, expected %d", type, words, 13);
+ collector_error("%s line of /proc/net/rpc/nfsd has %zu words, expected %d", type, words, 13);
continue;
}
@@ -335,7 +335,7 @@ int do_proc_net_rpc_nfsd(int update_every, usec_t dt) {
}
else if(do_net == 1 && strcmp(type, "net") == 0) {
if(unlikely(words < 5)) {
- error("%s line of /proc/net/rpc/nfsd has %zu words, expected %d", type, words, 5);
+ collector_error("%s line of /proc/net/rpc/nfsd has %zu words, expected %d", type, words, 5);
continue;
}
@@ -350,7 +350,7 @@ int do_proc_net_rpc_nfsd(int update_every, usec_t dt) {
}
else if(do_rpc == 1 && strcmp(type, "rpc") == 0) {
if(unlikely(words < 6)) {
- error("%s line of /proc/net/rpc/nfsd has %zu words, expected %d", type, words, 6);
+ collector_error("%s line of /proc/net/rpc/nfsd has %zu words, expected %d", type, words, 6);
continue;
}
@@ -377,7 +377,7 @@ int do_proc_net_rpc_nfsd(int update_every, usec_t dt) {
if(sum == 0ULL) {
if(!proc2_warning) {
- error("Disabling /proc/net/rpc/nfsd v2 procedure calls chart. It seems unused on this machine. It will be enabled automatically when found with data in it.");
+ collector_error("Disabling /proc/net/rpc/nfsd v2 procedure calls chart. It seems unused on this machine. It will be enabled automatically when found with data in it.");
proc2_warning = 1;
}
do_proc2 = 0;
@@ -398,7 +398,7 @@ int do_proc_net_rpc_nfsd(int update_every, usec_t dt) {
if(sum == 0ULL) {
if(!proc3_warning) {
- info("Disabling /proc/net/rpc/nfsd v3 procedure calls chart. It seems unused on this machine. It will be enabled automatically when found with data in it.");
+ collector_info("Disabling /proc/net/rpc/nfsd v3 procedure calls chart. It seems unused on this machine. It will be enabled automatically when found with data in it.");
proc3_warning = 1;
}
do_proc3 = 0;
@@ -419,7 +419,7 @@ int do_proc_net_rpc_nfsd(int update_every, usec_t dt) {
if(sum == 0ULL) {
if(!proc4_warning) {
- info("Disabling /proc/net/rpc/nfsd v4 procedure calls chart. It seems unused on this machine. It will be enabled automatically when found with data in it.");
+ collector_info("Disabling /proc/net/rpc/nfsd v4 procedure calls chart. It seems unused on this machine. It will be enabled automatically when found with data in it.");
proc4_warning = 1;
}
do_proc4 = 0;
@@ -440,7 +440,7 @@ int do_proc_net_rpc_nfsd(int update_every, usec_t dt) {
if(sum == 0ULL) {
if(!proc4ops_warning) {
- info("Disabling /proc/net/rpc/nfsd v4 operations chart. It seems unused on this machine. It will be enabled automatically when found with data in it.");
+ collector_info("Disabling /proc/net/rpc/nfsd v4 operations chart. It seems unused on this machine. It will be enabled automatically when found with data in it.");
proc4ops_warning = 1;
}
do_proc4ops = 0;
diff --git a/collectors/proc.plugin/proc_net_sctp_snmp.c b/collectors/proc.plugin/proc_net_sctp_snmp.c
index 292449a7..e67143e6 100644
--- a/collectors/proc.plugin/proc_net_sctp_snmp.c
+++ b/collectors/proc.plugin/proc_net_sctp_snmp.c
@@ -113,7 +113,7 @@ int do_proc_net_sctp_snmp(int update_every, usec_t dt) {
for(l = 0; l < lines ;l++) {
size_t words = procfile_linewords(ff, l);
if(unlikely(words < 2)) {
- if(unlikely(words)) error("Cannot read /proc/net/sctp/snmp line %zu. Expected 2 params, read %zu.", l, words);
+ if(unlikely(words)) collector_error("Cannot read /proc/net/sctp/snmp line %zu. Expected 2 params, read %zu.", l, words);
continue;
}
diff --git a/collectors/proc.plugin/proc_net_softnet_stat.c b/collectors/proc.plugin/proc_net_softnet_stat.c
index 65239246..dfd372b2 100644
--- a/collectors/proc.plugin/proc_net_softnet_stat.c
+++ b/collectors/proc.plugin/proc_net_softnet_stat.c
@@ -40,7 +40,7 @@ int do_proc_net_softnet_stat(int update_every, usec_t dt) {
size_t words = procfile_linewords(ff, 0), w;
if(unlikely(!lines || !words)) {
- error("Cannot read /proc/net/softnet_stat, %zu lines and %zu columns reported.", lines, words);
+ collector_error("Cannot read /proc/net/softnet_stat, %zu lines and %zu columns reported.", lines, words);
return 1;
}
diff --git a/collectors/proc.plugin/proc_net_stat_conntrack.c b/collectors/proc.plugin/proc_net_stat_conntrack.c
index f9dbdf47..e8fbdbb6 100644
--- a/collectors/proc.plugin/proc_net_stat_conntrack.c
+++ b/collectors/proc.plugin/proc_net_stat_conntrack.c
@@ -69,7 +69,7 @@ int do_proc_net_stat_conntrack(int update_every, usec_t dt) {
for(l = 1; l < lines ;l++) {
size_t words = procfile_linewords(ff, l);
if(unlikely(words < 17)) {
- if(unlikely(words)) error("Cannot read /proc/net/stat/nf_conntrack line. Expected 17 params, read %zu.", words);
+ if(unlikely(words)) collector_error("Cannot read /proc/net/stat/nf_conntrack line. Expected 17 params, read %zu.", words);
continue;
}
diff --git a/collectors/proc.plugin/proc_net_stat_synproxy.c b/collectors/proc.plugin/proc_net_stat_synproxy.c
index 0a74b357..e23a0ab7 100644
--- a/collectors/proc.plugin/proc_net_stat_synproxy.c
+++ b/collectors/proc.plugin/proc_net_stat_synproxy.c
@@ -34,7 +34,7 @@ int do_proc_net_stat_synproxy(int update_every, usec_t dt) {
// make sure we have 3 lines
size_t lines = procfile_lines(ff), l;
if(unlikely(lines < 2)) {
- error("/proc/net/stat/synproxy has %zu lines, expected no less than 2. Disabling it.", lines);
+ collector_error("/proc/net/stat/synproxy has %zu lines, expected no less than 2. Disabling it.", lines);
return 1;
}
diff --git a/collectors/proc.plugin/proc_pagetypeinfo.c b/collectors/proc.plugin/proc_pagetypeinfo.c
index dc006aa5..e12c5bff 100644
--- a/collectors/proc.plugin/proc_pagetypeinfo.c
+++ b/collectors/proc.plugin/proc_pagetypeinfo.c
@@ -112,7 +112,7 @@ int do_proc_pagetypeinfo(int update_every, usec_t dt) {
ff_lines = procfile_lines(ff);
if(unlikely(!ff_lines)) {
- error("PLUGIN: PROC_PAGETYPEINFO: Cannot read %s, zero lines reported.", ff_path);
+ collector_error("PLUGIN: PROC_PAGETYPEINFO: Cannot read %s, zero lines reported.", ff_path);
return 1;
}
@@ -135,21 +135,21 @@ int do_proc_pagetypeinfo(int update_every, usec_t dt) {
pagelines_cnt++;
}
if (pagelines_cnt == 0) {
- error("PLUGIN: PROC_PAGETYPEINFO: Unable to parse any valid line in %s", ff_path);
+ collector_error("PLUGIN: PROC_PAGETYPEINFO: Unable to parse any valid line in %s", ff_path);
return 1;
}
// 4th line is the "Free pages count per migrate type at order". Just subtract these 8 words.
pageorders_cnt = procfile_linewords(ff, 3);
if (pageorders_cnt < 9) {
- error("PLUGIN: PROC_PAGETYPEINFO: Unable to parse Line 4 of %s", ff_path);
+ collector_error("PLUGIN: PROC_PAGETYPEINFO: Unable to parse Line 4 of %s", ff_path);
return 1;
}
pageorders_cnt -= 9;
if (pageorders_cnt > MAX_PAGETYPE_ORDER) {
- error("PLUGIN: PROC_PAGETYPEINFO: pageorder found (%lu) is higher than max %d",
+ collector_error("PLUGIN: PROC_PAGETYPEINFO: pageorder found (%lu) is higher than max %d",
(long unsigned int) pageorders_cnt, MAX_PAGETYPE_ORDER);
return 1;
}
@@ -158,7 +158,7 @@ int do_proc_pagetypeinfo(int update_every, usec_t dt) {
if (!pagelines) {
pagelines = callocz(pagelines_cnt, sizeof(struct pageline));
if (!pagelines) {
- error("PLUGIN: PROC_PAGETYPEINFO: Cannot allocate %lu pagelines of %lu B",
+ collector_error("PLUGIN: PROC_PAGETYPEINFO: Cannot allocate %lu pagelines of %lu B",
(long unsigned int) pagelines_cnt, (long unsigned int) sizeof(struct pageline));
return 1;
}
@@ -291,8 +291,8 @@ int do_proc_pagetypeinfo(int update_every, usec_t dt) {
size_t words = procfile_linewords(ff, l);
if (words != 7+pageorders_cnt) {
- error("PLUGIN: PROC_PAGETYPEINFO: Unable to read line %lu, %lu words found instead of %lu",
- l+1, (long unsigned int) words, (long unsigned int) 7+pageorders_cnt);
+ collector_error("PLUGIN: PROC_PAGETYPEINFO: Unable to read line %lu, %lu words found instead of %lu",
+ l+1, (long unsigned int) words, (long unsigned int) 7+pageorders_cnt);
break;
}
diff --git a/collectors/proc.plugin/proc_pressure.c b/collectors/proc.plugin/proc_pressure.c
index 6649aa63..80b08d9a 100644
--- a/collectors/proc.plugin/proc_pressure.c
+++ b/collectors/proc.plugin/proc_pressure.c
@@ -171,7 +171,7 @@ int do_proc_pressure(int update_every, usec_t dt) {
ff = procfile_open(filename, " =", PROCFILE_FLAG_DEFAULT);
if (unlikely(!ff)) {
- error("Cannot read pressure information from %s.", filename);
+ collector_error("Cannot read pressure information from %s.", filename);
fail_count++;
continue;
}
@@ -186,7 +186,7 @@ int do_proc_pressure(int update_every, usec_t dt) {
size_t lines = procfile_lines(ff);
if (unlikely(lines < 1)) {
- error("%s has no lines.", procfile_filename(ff));
+ collector_error("%s has no lines.", procfile_filename(ff));
fail_count++;
continue;
}
diff --git a/collectors/proc.plugin/proc_self_mountinfo.c b/collectors/proc.plugin/proc_self_mountinfo.c
index 9310f2ff..0483749c 100644
--- a/collectors/proc.plugin/proc_self_mountinfo.c
+++ b/collectors/proc.plugin/proc_self_mountinfo.c
@@ -227,8 +227,9 @@ struct mountinfo *mountinfo_read(int do_statvfs) {
struct mountinfo *root = NULL, *last = NULL, *mi = NULL;
// create a dictionary to track uniqueness
- DICTIONARY *dict = dictionary_create(
- DICT_OPTION_SINGLE_THREADED | DICT_OPTION_DONT_OVERWRITE_VALUE | DICT_OPTION_NAME_LINK_DONT_CLONE);
+ DICTIONARY *dict = dictionary_create_advanced(
+ DICT_OPTION_SINGLE_THREADED | DICT_OPTION_DONT_OVERWRITE_VALUE | DICT_OPTION_NAME_LINK_DONT_CLONE,
+ &dictionary_stats_category_collectors, 0);
unsigned long l, lines = procfile_lines(ff);
for(l = 0; l < lines ;l++) {
@@ -252,7 +253,7 @@ struct mountinfo *mountinfo_read(int do_statvfs) {
for(minor = major; *minor && *minor != ':' ;minor++) ;
if(unlikely(!*minor)) {
- error("Cannot parse major:minor on '%s' at line %lu of '%s'", major, l + 1, filename);
+ collector_error("Cannot parse major:minor on '%s' at line %lu of '%s'", major, l + 1, filename);
freez(mi);
continue;
}
@@ -442,7 +443,7 @@ struct mountinfo *mountinfo_read(int do_statvfs) {
#ifdef NETDATA_INTERNAL_CHECKS
if(unlikely(!mi)) {
- error("Mount point '%s' not found in /proc/self/mountinfo", mnt->mnt_dir);
+ collector_error("Mount point '%s' not found in /proc/self/mountinfo", mnt->mnt_dir);
}
#endif
}
diff --git a/collectors/proc.plugin/proc_softirqs.c b/collectors/proc.plugin/proc_softirqs.c
index 4c4df766..0d5d8ef9 100644
--- a/collectors/proc.plugin/proc_softirqs.c
+++ b/collectors/proc.plugin/proc_softirqs.c
@@ -75,7 +75,7 @@ int do_proc_softirqs(int update_every, usec_t dt) {
size_t words = procfile_linewords(ff, 0);
if(unlikely(!lines)) {
- error("Cannot read /proc/softirqs, zero lines reported.");
+ collector_error("Cannot read /proc/softirqs, zero lines reported.");
return 1;
}
@@ -90,7 +90,7 @@ int do_proc_softirqs(int update_every, usec_t dt) {
}
if(unlikely(!cpus)) {
- error("PLUGIN: PROC_SOFTIRQS: Cannot find the number of CPUs in /proc/softirqs");
+ collector_error("PLUGIN: PROC_SOFTIRQS: Cannot find the number of CPUs in /proc/softirqs");
return 1;
}
diff --git a/collectors/proc.plugin/proc_spl_kstat_zfs.c b/collectors/proc.plugin/proc_spl_kstat_zfs.c
index 8938d643..0db9970c 100644
--- a/collectors/proc.plugin/proc_spl_kstat_zfs.c
+++ b/collectors/proc.plugin/proc_spl_kstat_zfs.c
@@ -140,7 +140,7 @@ int do_proc_spl_kstat_zfs_arcstats(int update_every, usec_t dt) {
if(likely(!do_zfs_stats)) {
DIR *dir = opendir(dirname);
if(unlikely(!dir)) {
- error("Cannot read directory '%s'", dirname);
+ collector_error("Cannot read directory '%s'", dirname);
return 1;
}
@@ -177,7 +177,7 @@ int do_proc_spl_kstat_zfs_arcstats(int update_every, usec_t dt) {
for(l = 0; l < lines ;l++) {
size_t words = procfile_linewords(ff, l);
if(unlikely(words < 3)) {
- if(unlikely(words)) error("Cannot read " ZFS_PROC_ARCSTATS " line %zu. Expected 3 params, read %zu.", l, words);
+ if(unlikely(words)) collector_error("Cannot read " ZFS_PROC_ARCSTATS " line %zu. Expected 3 params, read %zu.", l, words);
continue;
}
@@ -285,6 +285,8 @@ int update_zfs_pool_state_chart(const DICTIONARY_ITEM *item, void *pool_p, void
pool->rd_offline = rrddim_add(pool->st, "offline", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
pool->rd_removed = rrddim_add(pool->st, "removed", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
pool->rd_unavail = rrddim_add(pool->st, "unavail", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
+
+ rrdlabels_add(pool->st->rrdlabels, "pool", name, RRDLABEL_SRC_AUTO);
}
rrddim_set_by_pointer(pool->st, pool->rd_online, pool->online);
@@ -320,7 +322,7 @@ int do_proc_spl_kstat_zfs_pool_state(int update_every, usec_t dt)
snprintfz(filename, FILENAME_MAX, "%s%s", netdata_configured_host_prefix, "/proc/spl/kstat/zfs");
dirname = config_get("plugin:proc:" ZFS_PROC_POOLS, "directory to monitor", filename);
- zfs_pools = dictionary_create(DICT_OPTION_SINGLE_THREADED);
+ zfs_pools = dictionary_create_advanced(DICT_OPTION_SINGLE_THREADED, &dictionary_stats_category_collectors, 0);
do_zfs_pool_state = 1;
}
@@ -328,7 +330,7 @@ int do_proc_spl_kstat_zfs_pool_state(int update_every, usec_t dt)
if (likely(do_zfs_pool_state)) {
DIR *dir = opendir(dirname);
if (unlikely(!dir)) {
- error("Cannot read directory '%s'", dirname);
+ collector_error("Cannot read directory '%s'", dirname);
return 1;
}
@@ -392,7 +394,7 @@ int do_proc_spl_kstat_zfs_pool_state(int update_every, usec_t dt)
char *c = strchr(state, '\n');
if (c)
*c = '\0';
- error("ZFS POOLS: Undefined state %s for zpool %s, disabling the chart", state, de->d_name);
+ collector_error("ZFS POOLS: Undefined state %s for zpool %s, disabling the chart", state, de->d_name);
}
}
}
@@ -402,7 +404,7 @@ int do_proc_spl_kstat_zfs_pool_state(int update_every, usec_t dt)
}
if (do_zfs_pool_state && pool_found && !state_file_found) {
- info("ZFS POOLS: State files not found. Disabling the module.");
+ collector_info("ZFS POOLS: State files not found. Disabling the module.");
do_zfs_pool_state = 0;
}
diff --git a/collectors/proc.plugin/proc_stat.c b/collectors/proc.plugin/proc_stat.c
index 33fe9323..2ca7c42e 100644
--- a/collectors/proc.plugin/proc_stat.c
+++ b/collectors/proc.plugin/proc_stat.c
@@ -69,7 +69,7 @@ static int read_per_core_files(struct cpu_chart *all_cpu_charts, size_t len, siz
if(unlikely(f->fd == -1)) {
f->fd = open(f->filename, O_RDONLY);
if (unlikely(f->fd == -1)) {
- error("Cannot open file '%s'", f->filename);
+ collector_error("Cannot open file '%s'", f->filename);
continue;
}
}
@@ -78,7 +78,7 @@ static int read_per_core_files(struct cpu_chart *all_cpu_charts, size_t len, siz
if(unlikely(ret < 0)) {
// cannot read that file
- error("Cannot read file '%s'", f->filename);
+ collector_error("Cannot read file '%s'", f->filename);
close(f->fd);
f->fd = -1;
continue;
@@ -94,7 +94,7 @@ static int read_per_core_files(struct cpu_chart *all_cpu_charts, size_t len, siz
f->fd = -1;
}
else if(lseek(f->fd, 0, SEEK_SET) == -1) {
- error("Cannot seek in file '%s'", f->filename);
+ collector_error("Cannot seek in file '%s'", f->filename);
close(f->fd);
f->fd = -1;
}
@@ -133,14 +133,14 @@ static int read_per_core_time_in_state_files(struct cpu_chart *all_cpu_charts, s
tsf->ff = procfile_open(tsf->filename, " \t:", PROCFILE_FLAG_DEFAULT);
if(unlikely(!tsf->ff))
{
- error("Cannot open file '%s'", tsf->filename);
+ collector_error("Cannot open file '%s'", tsf->filename);
continue;
}
}
tsf->ff = procfile_readall(tsf->ff);
if(unlikely(!tsf->ff)) {
- error("Cannot read file '%s'", tsf->filename);
+ collector_error("Cannot read file '%s'", tsf->filename);
procfile_close(tsf->ff);
tsf->ff = NULL;
continue;
@@ -179,7 +179,7 @@ static int read_per_core_time_in_state_files(struct cpu_chart *all_cpu_charts, s
words = procfile_linewords(tsf->ff, l);
if(unlikely(words < 2)) {
- error("Cannot read time_in_state line. Expected 2 params, read %zu.", words);
+ collector_error("Cannot read time_in_state line. Expected 2 params, read %zu.", words);
continue;
}
frequency = str2ull(procfile_lineword(tsf->ff, l, 0));
@@ -273,11 +273,11 @@ static void* wake_cpu_thread(void* core) {
thread = pthread_self();
if(unlikely(pthread_setaffinity_np(thread, sizeof(cpu_set_t), &cpu_set))) {
if(unlikely(errors < 8)) {
- error("Cannot set CPU affinity for core %d", *(int*)core);
+ collector_error("Cannot set CPU affinity for core %d", *(int*)core);
errors++;
}
else if(unlikely(errors < 9)) {
- error("CPU affinity errors are disabled");
+ collector_error("CPU affinity errors are disabled");
errors++;
}
}
@@ -312,14 +312,14 @@ static int read_schedstat(char *schedstat_filename, struct per_core_cpuidle_char
if(likely(row_key[0] == 'c' && row_key[1] == 'p' && row_key[2] == 'u')) {
words = procfile_linewords(ff, l);
if(unlikely(words < 10)) {
- error("Cannot read /proc/schedstat cpu line. Expected 9 params, read %zu.", words);
+ collector_error("Cannot read /proc/schedstat cpu line. Expected 9 params, read %zu.", words);
return 1;
}
cores_found++;
size_t core = str2ul(&row_key[3]);
if(unlikely(core >= cores_found)) {
- error("Core %zu found but no more than %zu cores were expected.", core, cores_found);
+ collector_error("Core %zu found but no more than %zu cores were expected.", core, cores_found);
return 1;
}
@@ -343,7 +343,7 @@ static int read_one_state(char *buf, const char *filename, int *fd) {
if(unlikely(ret <= 0)) {
// cannot read that file
- error("Cannot read file '%s'", filename);
+ collector_error("Cannot read file '%s'", filename);
close(*fd);
*fd = -1;
return 0;
@@ -359,7 +359,7 @@ static int read_one_state(char *buf, const char *filename, int *fd) {
*fd = -1;
}
else if(lseek(*fd, 0, SEEK_SET) == -1) {
- error("Cannot seek in file '%s'", filename);
+ collector_error("Cannot seek in file '%s'", filename);
close(*fd);
*fd = -1;
}
@@ -413,14 +413,14 @@ static int read_cpuidle_states(char *cpuidle_name_filename , char *cpuidle_time_
int fd = open(filename, O_RDONLY, 0666);
if(unlikely(fd == -1)) {
- error("Cannot open file '%s'", filename);
+ collector_error("Cannot open file '%s'", filename);
cc->rescan_cpu_states = 1;
return 1;
}
ssize_t r = read(fd, name_buf, 50);
if(unlikely(r < 1)) {
- error("Cannot read file '%s'", filename);
+ collector_error("Cannot read file '%s'", filename);
close(fd);
cc->rescan_cpu_states = 1;
return 1;
@@ -445,7 +445,7 @@ static int read_cpuidle_states(char *cpuidle_name_filename , char *cpuidle_time_
if(unlikely(cs->time_fd == -1)) {
cs->time_fd = open(cs->time_filename, O_RDONLY);
if (unlikely(cs->time_fd == -1)) {
- error("Cannot open file '%s'", cs->time_filename);
+ collector_error("Cannot open file '%s'", cs->time_filename);
cc->rescan_cpu_states = 1;
return 1;
}
@@ -483,7 +483,7 @@ int do_proc_stat(int update_every, usec_t dt) {
*time_in_state_filename = NULL, *schedstat_filename = NULL, *cpuidle_name_filename = NULL, *cpuidle_time_filename = NULL;
static const RRDVAR_ACQUIRED *cpus_var = NULL;
static int accurate_freq_avail = 0, accurate_freq_is_used = 0;
- size_t cores_found = (size_t)processors;
+ size_t cores_found = (size_t)get_system_cpus();
if(unlikely(do_cpu == -1)) {
do_cpu = config_get_boolean("plugin:proc:/proc/stat", "cpu utilization", CONFIG_BOOLEAN_YES);
@@ -494,7 +494,7 @@ int do_proc_stat(int update_every, usec_t dt) {
do_processes = config_get_boolean("plugin:proc:/proc/stat", "processes running", CONFIG_BOOLEAN_YES);
// give sane defaults based on the number of processors
- if(unlikely(processors > 50)) {
+ if(unlikely(get_system_cpus() > 50)) {
// the system has too many processors
keep_per_core_fds_open = CONFIG_BOOLEAN_NO;
do_core_throttle_count = CONFIG_BOOLEAN_NO;
@@ -510,7 +510,7 @@ int do_proc_stat(int update_every, usec_t dt) {
do_cpu_freq = CONFIG_BOOLEAN_YES;
do_cpuidle = CONFIG_BOOLEAN_YES;
}
- if(unlikely(processors > 24)) {
+ if(unlikely(get_system_cpus() > 24)) {
// the system has too many processors
keep_cpuidle_fds_open = CONFIG_BOOLEAN_NO;
}
@@ -585,7 +585,7 @@ int do_proc_stat(int update_every, usec_t dt) {
if(likely(row_key[0] == 'c' && row_key[1] == 'p' && row_key[2] == 'u')) {
words = procfile_linewords(ff, l);
if(unlikely(words < 9)) {
- error("Cannot read /proc/stat cpu line. Expected 9 params, read %zu.", words);
+ collector_error("Cannot read /proc/stat cpu line. Expected 9 params, read %zu.", words);
continue;
}
@@ -712,6 +712,12 @@ int do_proc_stat(int update_every, usec_t dt) {
cpu_chart->rd_idle = rrddim_add(cpu_chart->st, "idle", NULL, multiplier, divisor, RRD_ALGORITHM_PCENT_OVER_DIFF_TOTAL);
rrddim_hide(cpu_chart->st, "idle");
+ if (core > 0) {
+ char cpu_core[50 + 1];
+ snprintfz(cpu_core, 50, "cpu%lu", core - 1);
+ rrdlabels_add(cpu_chart->st->rrdlabels, "cpu", cpu_core, RRDLABEL_SRC_AUTO);
+ }
+
if(unlikely(core == 0 && cpus_var == NULL))
cpus_var = rrdvar_custom_host_variable_add_and_acquire(localhost, "active_processors");
}
@@ -930,7 +936,7 @@ int do_proc_stat(int update_every, usec_t dt) {
if(r > 0 && !accurate_freq_is_used) {
accurate_freq_is_used = 1;
snprintfz(filename, FILENAME_MAX, time_in_state_filename, "cpu*");
- info("cpufreq is using %s", filename);
+ collector_info("cpufreq is using %s", filename);
}
}
if (r < 1) {
@@ -938,7 +944,7 @@ int do_proc_stat(int update_every, usec_t dt) {
if(accurate_freq_is_used) {
accurate_freq_is_used = 0;
snprintfz(filename, FILENAME_MAX, scaling_cur_freq_filename, "cpu*");
- info("cpufreq fell back to %s", filename);
+ collector_info("cpufreq fell back to %s", filename);
}
}
@@ -993,13 +999,13 @@ int do_proc_stat(int update_every, usec_t dt) {
}
}
else
- error("Cannot read current process affinity");
+ collector_error("Cannot read current process affinity");
// These threads are very ephemeral and don't need to have a specific name
if(unlikely(pthread_create(&thread, NULL, wake_cpu_thread, (void *)&core)))
- error("Cannot create wake_cpu_thread");
+ collector_error("Cannot create wake_cpu_thread");
else if(unlikely(pthread_join(thread, NULL)))
- error("Cannot join wake_cpu_thread");
+ collector_error("Cannot join wake_cpu_thread");
cpu_states_updated = 1;
}
}
diff --git a/collectors/proc.plugin/proc_vmstat.c b/collectors/proc.plugin/proc_vmstat.c
index b8defc45..638d1690 100644
--- a/collectors/proc.plugin/proc_vmstat.c
+++ b/collectors/proc.plugin/proc_vmstat.c
@@ -100,7 +100,7 @@ int do_proc_vmstat(int update_every, usec_t dt) {
for(l = 0; l < lines ;l++) {
size_t words = procfile_linewords(ff, l);
if(unlikely(words < 2)) {
- if(unlikely(words)) error("Cannot read /proc/vmstat line %zu. Expected 2 params, read %zu.", l, words);
+ if(unlikely(words)) collector_error("Cannot read /proc/vmstat line %zu. Expected 2 params, read %zu.", l, words);
continue;
}
diff --git a/collectors/proc.plugin/sys_block_zram.c b/collectors/proc.plugin/sys_block_zram.c
index 6bae5424..1be725b1 100644
--- a/collectors/proc.plugin/sys_block_zram.c
+++ b/collectors/proc.plugin/sys_block_zram.c
@@ -144,17 +144,17 @@ static int init_devices(DICTIONARY *devices, unsigned int zram_id, int update_ev
snprintfz(filename, FILENAME_MAX, "/dev/%s", de->d_name);
if (unlikely(stat(filename, &st) != 0))
{
- error("ZRAM : Unable to stat %s: %s", filename, strerror(errno));
+ collector_error("ZRAM : Unable to stat %s: %s", filename, strerror(errno));
continue;
}
if (major(st.st_rdev) == zram_id)
{
- info("ZRAM : Found device %s", filename);
+ collector_info("ZRAM : Found device %s", filename);
snprintfz(filename, FILENAME_MAX, "/sys/block/%s/mm_stat", de->d_name);
ff = procfile_open(filename, " \t:", PROCFILE_FLAG_DEFAULT);
if (ff == NULL)
{
- error("ZRAM : Failed to open %s: %s", filename, strerror(errno));
+ collector_error("ZRAM : Failed to open %s: %s", filename, strerror(errno));
continue;
}
device.file = ff;
@@ -170,7 +170,7 @@ static int init_devices(DICTIONARY *devices, unsigned int zram_id, int update_ev
static void free_device(DICTIONARY *dict, const char *name)
{
ZRAM_DEVICE *d = (ZRAM_DEVICE*)dictionary_get(dict, name);
- info("ZRAM : Disabling monitoring of device %s", name);
+ collector_info("ZRAM : Disabling monitoring of device %s", name);
rrdset_obsolete_and_pointer_null(d->st_usage);
rrdset_obsolete_and_pointer_null(d->st_savings);
rrdset_obsolete_and_pointer_null(d->st_alloc_efficiency);
@@ -252,7 +252,7 @@ int do_sys_block_zram(int update_every, usec_t dt) {
ff = procfile_open("/proc/devices", " \t:", PROCFILE_FLAG_DEFAULT);
if (ff == NULL)
{
- error("Cannot read /proc/devices");
+ collector_error("Cannot read /proc/devices");
return 1;
}
ff = procfile_readall(ff);
@@ -267,7 +267,7 @@ int do_sys_block_zram(int update_every, usec_t dt) {
}
procfile_close(ff);
- devices = dictionary_create(DICT_OPTION_SINGLE_THREADED);
+ devices = dictionary_create_advanced(DICT_OPTION_SINGLE_THREADED, &dictionary_stats_category_collectors, 0);
device_count = init_devices(devices, (unsigned int)zram_id, update_every);
}
diff --git a/collectors/proc.plugin/sys_class_infiniband.c b/collectors/proc.plugin/sys_class_infiniband.c
index fca0cb8a..5f5e5323 100644
--- a/collectors/proc.plugin/sys_class_infiniband.c
+++ b/collectors/proc.plugin/sys_class_infiniband.c
@@ -200,7 +200,7 @@ static struct ibport {
#define GEN_DO_HWCOUNTER_READ(NAME, GRP, DESC, DIR, PORT, HW, ...) \
if (HW->file_##NAME) { \
if (read_single_number_file(HW->file_##NAME, (unsigned long long *)&HW->NAME)) { \
- error("cannot read iface '%s' hwcounter '" #HW "'", PORT->name); \
+ collector_error("cannot read iface '%s' hwcounter '" #HW "'", PORT->name); \
HW->file_##NAME = NULL; \
} \
}
@@ -469,7 +469,7 @@ int do_sys_class_infiniband(int update_every, usec_t dt)
snprintfz(buffer, FILENAME_MAX, "%s/%s/%s", ports_dirname, port_dent->d_name, "rate");
char buffer_rate[65];
if (read_file(buffer, buffer_rate, 64)) {
- error("Unable to read '%s'", buffer);
+ collector_error("Unable to read '%s'", buffer);
p->width = 1;
} else {
char *buffer_width = strstr(buffer_rate, "(");
@@ -480,12 +480,11 @@ int do_sys_class_infiniband(int update_every, usec_t dt)
}
if (!p->discovered)
- info(
- "Infiniband card %s port %s at speed %" PRIu64 " width %" PRIu64 "",
- dev_dent->d_name,
- port_dent->d_name,
- p->speed,
- p->width);
+ collector_info("Infiniband card %s port %s at speed %" PRIu64 " width %" PRIu64 "",
+ dev_dent->d_name,
+ port_dent->d_name,
+ p->speed,
+ p->width);
p->discovered = 1;
}
@@ -511,7 +510,7 @@ int do_sys_class_infiniband(int update_every, usec_t dt)
#define GEN_DO_COUNTER_READ(NAME, GRP, DESC, DIR, PORT, ...) \
if (PORT->file_##NAME) { \
if (read_single_number_file(PORT->file_##NAME, (unsigned long long *)&PORT->NAME)) { \
- error("cannot read iface '%s' counter '" #NAME "'", PORT->name); \
+ collector_error("cannot read iface '%s' counter '" #NAME "'", PORT->name); \
PORT->file_##NAME = NULL; \
} \
}
@@ -650,7 +649,7 @@ int do_sys_class_infiniband(int update_every, usec_t dt)
// Unknown vendor, should not happen
else {
- error(
+ collector_error(
"Unmanaged vendor for '%s', do_hwerrors should have been set to no. Please report this bug",
port->name);
port->do_hwerrors = CONFIG_BOOLEAN_NO;
@@ -686,7 +685,7 @@ int do_sys_class_infiniband(int update_every, usec_t dt)
// Unknown vendor, should not happen
else {
- error(
+ collector_error(
"Unmanaged vendor for '%s', do_hwpackets should have been set to no. Please report this bug",
port->name);
port->do_hwpackets = CONFIG_BOOLEAN_NO;
diff --git a/collectors/proc.plugin/sys_class_power_supply.c b/collectors/proc.plugin/sys_class_power_supply.c
index dde42150..ec36a295 100644
--- a/collectors/proc.plugin/sys_class_power_supply.c
+++ b/collectors/proc.plugin/sys_class_power_supply.c
@@ -137,7 +137,7 @@ int do_sys_class_power_supply(int update_every, usec_t dt) {
DIR *dir = opendir(dirname);
if(unlikely(!dir)) {
- error("Cannot read directory '%s'", dirname);
+ collector_error("Cannot read directory '%s'", dirname);
return 1;
}
@@ -247,7 +247,7 @@ int do_sys_class_power_supply(int update_every, usec_t dt) {
if(unlikely(ps->capacity->fd == -1)) {
ps->capacity->fd = open(ps->capacity->filename, O_RDONLY, 0666);
if(unlikely(ps->capacity->fd == -1)) {
- error("Cannot open file '%s'", ps->capacity->filename);
+ collector_error("Cannot open file '%s'", ps->capacity->filename);
power_supply_free(ps);
ps = NULL;
}
@@ -257,7 +257,7 @@ int do_sys_class_power_supply(int update_every, usec_t dt) {
{
ssize_t r = read(ps->capacity->fd, buffer, 30);
if(unlikely(r < 1)) {
- error("Cannot read file '%s'", ps->capacity->filename);
+ collector_error("Cannot read file '%s'", ps->capacity->filename);
power_supply_free(ps);
ps = NULL;
}
@@ -270,7 +270,7 @@ int do_sys_class_power_supply(int update_every, usec_t dt) {
ps->capacity->fd = -1;
}
else if(unlikely(lseek(ps->capacity->fd, 0, SEEK_SET) == -1)) {
- error("Cannot seek in file '%s'", ps->capacity->filename);
+ collector_error("Cannot seek in file '%s'", ps->capacity->filename);
close(ps->capacity->fd);
ps->capacity->fd = -1;
}
@@ -292,7 +292,7 @@ int do_sys_class_power_supply(int update_every, usec_t dt) {
if(unlikely(pd->fd == -1)) {
pd->fd = open(pd->filename, O_RDONLY, 0666);
if(unlikely(pd->fd == -1)) {
- error("Cannot open file '%s'", pd->filename);
+ collector_error("Cannot open file '%s'", pd->filename);
read_error = 1;
power_supply_free(ps);
break;
@@ -301,7 +301,7 @@ int do_sys_class_power_supply(int update_every, usec_t dt) {
ssize_t r = read(pd->fd, buffer, 30);
if(unlikely(r < 1)) {
- error("Cannot read file '%s'", pd->filename);
+ collector_error("Cannot read file '%s'", pd->filename);
read_error = 1;
power_supply_free(ps);
break;
@@ -314,7 +314,7 @@ int do_sys_class_power_supply(int update_every, usec_t dt) {
pd->fd = -1;
}
else if(unlikely(lseek(pd->fd, 0, SEEK_SET) == -1)) {
- error("Cannot seek in file '%s'", pd->filename);
+ collector_error("Cannot seek in file '%s'", pd->filename);
close(pd->fd);
pd->fd = -1;
}
diff --git a/collectors/proc.plugin/sys_devices_system_edac_mc.c b/collectors/proc.plugin/sys_devices_system_edac_mc.c
index 13d20978..fe825096 100644
--- a/collectors/proc.plugin/sys_devices_system_edac_mc.c
+++ b/collectors/proc.plugin/sys_devices_system_edac_mc.c
@@ -30,7 +30,7 @@ static void find_all_mc() {
DIR *dir = opendir(dirname);
if(unlikely(!dir)) {
- error("Cannot read ECC memory errors directory '%s'", dirname);
+ collector_error("Cannot read ECC memory errors directory '%s'", dirname);
return;
}
diff --git a/collectors/proc.plugin/sys_devices_system_node.c b/collectors/proc.plugin/sys_devices_system_node.c
index 90aafd56..068d739d 100644
--- a/collectors/proc.plugin/sys_devices_system_node.c
+++ b/collectors/proc.plugin/sys_devices_system_node.c
@@ -19,7 +19,7 @@ static int find_all_nodes() {
DIR *dir = opendir(dirname);
if(!dir) {
- error("Cannot read NUMA node directory '%s'", dirname);
+ collector_error("Cannot read NUMA node directory '%s'", dirname);
return 0;
}
@@ -134,7 +134,7 @@ int do_proc_sys_devices_system_node(int update_every, usec_t dt) {
if(unlikely(words < 2)) {
if(unlikely(words))
- error("Cannot read %s numastat line %zu. Expected 2 params, read %zu.", m->name, l, words);
+ collector_error("Cannot read %s numastat line %zu. Expected 2 params, read %zu.", m->name, l, words);
continue;
}
diff --git a/collectors/proc.plugin/sys_fs_btrfs.c b/collectors/proc.plugin/sys_fs_btrfs.c
index 3b9841fe..6abfd785 100644
--- a/collectors/proc.plugin/sys_fs_btrfs.c
+++ b/collectors/proc.plugin/sys_fs_btrfs.c
@@ -90,7 +90,7 @@ static inline void btrfs_free_disk(BTRFS_DISK *d) {
}
static inline void btrfs_free_node(BTRFS_NODE *node) {
- // info("BTRFS: destroying '%s'", node->id);
+ // collector_info("BTRFS: destroying '%s'", node->id);
if(node->st_allocation_disks)
rrdset_is_obsolete(node->st_allocation_disks);
@@ -136,7 +136,7 @@ static inline int find_btrfs_disks(BTRFS_NODE *node, const char *path) {
DIR *dir = opendir(path);
if (!dir) {
if(!node->logged_error) {
- error("BTRFS: Cannot open directory '%s'.", path);
+ collector_error("BTRFS: Cannot open directory '%s'.", path);
node->logged_error = 1;
}
return 1;
@@ -149,7 +149,7 @@ static inline int find_btrfs_disks(BTRFS_NODE *node, const char *path) {
|| !strcmp(de->d_name, ".")
|| !strcmp(de->d_name, "..")
) {
- // info("BTRFS: ignoring '%s'", de->d_name);
+ // collector_info("BTRFS: ignoring '%s'", de->d_name);
continue;
}
@@ -200,13 +200,13 @@ static inline int find_btrfs_disks(BTRFS_NODE *node, const char *path) {
// update the values
if(read_single_number_file(d->size_filename, &d->size) != 0) {
- error("BTRFS: failed to read '%s'", d->size_filename);
+ collector_error("BTRFS: failed to read '%s'", d->size_filename);
d->exists = 0;
continue;
}
if(read_single_number_file(d->hw_sector_size_filename, &d->hw_sector_size) != 0) {
- error("BTRFS: failed to read '%s'", d->hw_sector_size_filename);
+ collector_error("BTRFS: failed to read '%s'", d->hw_sector_size_filename);
d->exists = 0;
continue;
}
@@ -257,7 +257,7 @@ static inline int find_all_btrfs_pools(const char *path) {
DIR *dir = opendir(path);
if (!dir) {
if(!logged_error) {
- error("BTRFS: Cannot open directory '%s'.", path);
+ collector_error("BTRFS: Cannot open directory '%s'.", path);
logged_error = 1;
}
return 1;
@@ -271,7 +271,7 @@ static inline int find_all_btrfs_pools(const char *path) {
|| !strcmp(de->d_name, "..")
|| !strcmp(de->d_name, "features")
) {
- // info("BTRFS: ignoring '%s'", de->d_name);
+ // collector_info("BTRFS: ignoring '%s'", de->d_name);
continue;
}
@@ -285,7 +285,7 @@ static inline int find_all_btrfs_pools(const char *path) {
// did we find it?
if(node) {
- // info("BTRFS: already exists '%s'", de->d_name);
+ // collector_info("BTRFS: already exists '%s'", de->d_name);
node->exists = 1;
// update the disk sizes
@@ -295,7 +295,7 @@ static inline int find_all_btrfs_pools(const char *path) {
continue;
}
- // info("BTRFS: adding '%s'", de->d_name);
+ // collector_info("BTRFS: adding '%s'", de->d_name);
// not found, create it
node = callocz(sizeof(BTRFS_NODE), 1);
@@ -309,7 +309,7 @@ static inline int find_all_btrfs_pools(const char *path) {
snprintfz(filename, FILENAME_MAX, "%s/%s/label", path, de->d_name);
if(read_file(filename, label, FILENAME_MAX) != 0) {
- error("BTRFS: failed to read '%s'", filename);
+ collector_error("BTRFS: failed to read '%s'", filename);
btrfs_free_node(node);
continue;
}
@@ -326,21 +326,21 @@ static inline int find_all_btrfs_pools(const char *path) {
//snprintfz(filename, FILENAME_MAX, "%s/%s/sectorsize", path, de->d_name);
//if(read_single_number_file(filename, &node->sectorsize) != 0) {
- // error("BTRFS: failed to read '%s'", filename);
+ // collector_error("BTRFS: failed to read '%s'", filename);
// btrfs_free_node(node);
// continue;
//}
//snprintfz(filename, FILENAME_MAX, "%s/%s/nodesize", path, de->d_name);
//if(read_single_number_file(filename, &node->nodesize) != 0) {
- // error("BTRFS: failed to read '%s'", filename);
+ // collector_error("BTRFS: failed to read '%s'", filename);
// btrfs_free_node(node);
// continue;
//}
//snprintfz(filename, FILENAME_MAX, "%s/%s/quota_override", path, de->d_name);
//if(read_single_number_file(filename, &node->quota_override) != 0) {
- // error("BTRFS: failed to read '%s'", filename);
+ // collector_error("BTRFS: failed to read '%s'", filename);
// btrfs_free_node(node);
// continue;
//}
@@ -351,7 +351,7 @@ static inline int find_all_btrfs_pools(const char *path) {
#define init_btrfs_allocation_field(FIELD) {\
snprintfz(filename, FILENAME_MAX, "%s/%s/allocation/" #FIELD, path, de->d_name); \
if(read_single_number_file(filename, &node->allocation_ ## FIELD) != 0) {\
- error("BTRFS: failed to read '%s'", filename);\
+ collector_error("BTRFS: failed to read '%s'", filename);\
btrfs_free_node(node);\
continue;\
}\
@@ -362,7 +362,7 @@ static inline int find_all_btrfs_pools(const char *path) {
#define init_btrfs_allocation_section_field(SECTION, FIELD) {\
snprintfz(filename, FILENAME_MAX, "%s/%s/allocation/" #SECTION "/" #FIELD, path, de->d_name); \
if(read_single_number_file(filename, &node->allocation_ ## SECTION ## _ ## FIELD) != 0) {\
- error("BTRFS: failed to read '%s'", filename);\
+ collector_error("BTRFS: failed to read '%s'", filename);\
btrfs_free_node(node);\
continue;\
}\
@@ -411,7 +411,7 @@ static inline int find_all_btrfs_pools(const char *path) {
// --------------------------------------------------------------------
// link it
- // info("BTRFS: linking '%s'", node->id);
+ // collector_info("BTRFS: linking '%s'", node->id);
node->next = nodes;
nodes = node;
}
@@ -505,7 +505,7 @@ int do_sys_fs_btrfs(int update_every, usec_t dt) {
|| collect_btrfs_allocation_section_field(metadata, disk_used) != 0
|| collect_btrfs_allocation_section_field(system, disk_total) != 0
|| collect_btrfs_allocation_section_field(system, disk_used) != 0) {
- error("BTRFS: failed to collect physical disks allocation for '%s'", node->id);
+ collector_error("BTRFS: failed to collect physical disks allocation for '%s'", node->id);
// make it refresh btrfs at the next iteration
refresh_delta = refresh_every;
continue;
@@ -515,7 +515,7 @@ int do_sys_fs_btrfs(int update_every, usec_t dt) {
if(do_allocation_data != CONFIG_BOOLEAN_NO) {
if (collect_btrfs_allocation_section_field(data, total_bytes) != 0
|| collect_btrfs_allocation_section_field(data, bytes_used) != 0) {
- error("BTRFS: failed to collect allocation/data for '%s'", node->id);
+ collector_error("BTRFS: failed to collect allocation/data for '%s'", node->id);
// make it refresh btrfs at the next iteration
refresh_delta = refresh_every;
continue;
@@ -527,7 +527,7 @@ int do_sys_fs_btrfs(int update_every, usec_t dt) {
|| collect_btrfs_allocation_section_field(metadata, bytes_used) != 0
|| collect_btrfs_allocation_field(global_rsv_size) != 0
) {
- error("BTRFS: failed to collect allocation/metadata for '%s'", node->id);
+ collector_error("BTRFS: failed to collect allocation/metadata for '%s'", node->id);
// make it refresh btrfs at the next iteration
refresh_delta = refresh_every;
continue;
@@ -537,7 +537,7 @@ int do_sys_fs_btrfs(int update_every, usec_t dt) {
if(do_allocation_system != CONFIG_BOOLEAN_NO) {
if (collect_btrfs_allocation_section_field(system, total_bytes) != 0
|| collect_btrfs_allocation_section_field(system, bytes_used) != 0) {
- error("BTRFS: failed to collect allocation/system for '%s'", node->id);
+ collector_error("BTRFS: failed to collect allocation/system for '%s'", node->id);
// make it refresh btrfs at the next iteration
refresh_delta = refresh_every;
continue;
diff --git a/collectors/python.d.plugin/Makefile.am b/collectors/python.d.plugin/Makefile.am
index 1bbbf8ca..6ea7b21b 100644
--- a/collectors/python.d.plugin/Makefile.am
+++ b/collectors/python.d.plugin/Makefile.am
@@ -48,7 +48,6 @@ include bind_rndc/Makefile.inc
include boinc/Makefile.inc
include ceph/Makefile.inc
include changefinder/Makefile.inc
-include dockerd/Makefile.inc
include dovecot/Makefile.inc
include example/Makefile.inc
include exim/Makefile.inc
@@ -61,10 +60,8 @@ include hpssa/Makefile.inc
include icecast/Makefile.inc
include ipfs/Makefile.inc
include litespeed/Makefile.inc
-include logind/Makefile.inc
include megacli/Makefile.inc
include memcached/Makefile.inc
-include mongodb/Makefile.inc
include monit/Makefile.inc
include nvidia_smi/Makefile.inc
include nsd/Makefile.inc
@@ -83,7 +80,6 @@ include samba/Makefile.inc
include sensors/Makefile.inc
include smartd_log/Makefile.inc
include spigotmc/Makefile.inc
-include springboot/Makefile.inc
include squid/Makefile.inc
include tomcat/Makefile.inc
include tor/Makefile.inc
diff --git a/collectors/python.d.plugin/README.md b/collectors/python.d.plugin/README.md
index 2f5ebfcb..b6d658fa 100644
--- a/collectors/python.d.plugin/README.md
+++ b/collectors/python.d.plugin/README.md
@@ -1,6 +1,10 @@
<!--
title: "python.d.plugin"
-custom_edit_url: https://github.com/netdata/netdata/edit/master/collectors/python.d.plugin/README.md
+custom_edit_url: "https://github.com/netdata/netdata/edit/master/collectors/python.d.plugin/README.md"
+sidebar_label: "python.d.plugin"
+learn_status: "Published"
+learn_topic_type: "Tasks"
+learn_rel_path: "Developers/Collectors"
-->
# python.d.plugin
@@ -86,7 +90,7 @@ plugin](https://raw.githubusercontent.com/netdata/netdata/master/collectors/pyth
Netdata (as opposed to having to install Netdata from source again with your new changes) to can copy over the relevant
file to where Netdata expects it and then either `sudo systemctl restart netdata` to have it be picked up and used by
Netdata or you can just run the updated collector in debug mode by following a process like below (this assumes you have
-[installed Netdata from a GitHub fork](https://learn.netdata.cloud/docs/agent/packaging/installer/methods/manual) you
+[installed Netdata from a GitHub fork](https://github.com/netdata/netdata/blob/master/packaging/installer/methods/manual.md) you
have made to do your development on).
```bash
@@ -125,7 +129,7 @@ CHART = {
]}
```
-All names are better explained in the [External Plugins](/collectors/plugins.d/README.md) section.
+All names are better explained in the [External Plugins](https://github.com/netdata/netdata/blob/master/collectors/plugins.d/README.md) section.
Parameters like `priority` and `update_every` are handled by `python.d.plugin`.
### `Service` class
@@ -227,7 +231,7 @@ For additional security it uses python `subprocess.Popen` (without `shell=True`
_Examples: `apache`, `nginx`, `tomcat`_
-_Multiple Endpoints (urls) Examples: [`rabbitmq`](/collectors/python.d.plugin/rabbitmq/README.md) (simpler).
+_Multiple Endpoints (urls) Examples: [`rabbitmq`](https://github.com/netdata/netdata/blob/master/collectors/python.d.plugin/rabbitmq/README.md) (simpler).
_Variables from config file_: `url`, `user`, `pass`.
diff --git a/collectors/python.d.plugin/adaptec_raid/README.md b/collectors/python.d.plugin/adaptec_raid/README.md
index da5d13b1..90ef8fa3 100644
--- a/collectors/python.d.plugin/adaptec_raid/README.md
+++ b/collectors/python.d.plugin/adaptec_raid/README.md
@@ -1,7 +1,10 @@
<!--
title: "Adaptec RAID controller monitoring with Netdata"
-custom_edit_url: https://github.com/netdata/netdata/edit/master/collectors/python.d.plugin/adaptec_raid/README.md
+custom_edit_url: "https://github.com/netdata/netdata/edit/master/collectors/python.d.plugin/adaptec_raid/README.md"
sidebar_label: "Adaptec RAID"
+learn_status: "Published"
+learn_topic_type: "References"
+learn_rel_path: "References/Collectors references/Hardware"
-->
# Adaptec RAID controller monitoring with Netdata
@@ -52,7 +55,7 @@ systemctl restart netdata.service
## Enable the collector
The `adaptec_raid` collector is disabled by default. To enable it, use `edit-config` from the
-Netdata [config directory](/docs/configure/nodes.md), which is typically at `/etc/netdata`, to edit the `python.d.conf`
+Netdata [config directory](https://github.com/netdata/netdata/blob/master/docs/configure/nodes.md), which is typically at `/etc/netdata`, to edit the `python.d.conf`
file.
```bash
@@ -61,12 +64,12 @@ sudo ./edit-config python.d.conf
```
Change the value of the `adaptec_raid` setting to `yes`. Save the file and restart the Netdata Agent with `sudo
-systemctl restart netdata`, or the [appropriate method](/docs/configure/start-stop-restart.md) for your system.
+systemctl restart netdata`, or the [appropriate method](https://github.com/netdata/netdata/blob/master/docs/configure/start-stop-restart.md) for your system.
## Configuration
Edit the `python.d/adaptec_raid.conf` configuration file using `edit-config` from the
-Netdata [config directory](/docs/configure/nodes.md), which is typically at `/etc/netdata`.
+Netdata [config directory](https://github.com/netdata/netdata/blob/master/docs/configure/nodes.md), which is typically at `/etc/netdata`.
```bash
cd /etc/netdata # Replace this path with your Netdata config directory, if different
diff --git a/collectors/python.d.plugin/alarms/README.md b/collectors/python.d.plugin/alarms/README.md
index 8dc666f5..4804bd0d 100644
--- a/collectors/python.d.plugin/alarms/README.md
+++ b/collectors/python.d.plugin/alarms/README.md
@@ -1,6 +1,9 @@
<!--
title: "Alarms"
-custom_edit_url: https://github.com/netdata/netdata/edit/master/collectors/python.d.plugin/alarms/README.md
+custom_edit_url: "https://github.com/netdata/netdata/edit/master/collectors/python.d.plugin/alarms/README.md"
+sidebar_label: "alarms"
+learn_status: "Unpublished"
+learn_topic_type: "References"
-->
# Alarms - graphing Netdata alarm states over time
@@ -23,7 +26,7 @@ Below is an example of the chart produced when running `stress-ng --all 2` for a
## Configuration
-Enable the collector and [restart Netdata](/docs/configure/start-stop-restart.md).
+Enable the collector and [restart Netdata](https://github.com/netdata/netdata/blob/master/docs/configure/start-stop-restart.md).
```bash
cd /etc/netdata/
@@ -33,7 +36,7 @@ sudo systemctl restart netdata
```
If needed, edit the `python.d/alarms.conf` configuration file using `edit-config` from the your agent's [config
-directory](/docs/configure/nodes.md), which is usually at `/etc/netdata`.
+directory](https://github.com/netdata/netdata/blob/master/docs/configure/nodes.md), which is usually at `/etc/netdata`.
```bash
cd /etc/netdata # Replace this path with your Netdata config directory, if different
diff --git a/collectors/python.d.plugin/am2320/README.md b/collectors/python.d.plugin/am2320/README.md
index 3503d7c1..070e8eb3 100644
--- a/collectors/python.d.plugin/am2320/README.md
+++ b/collectors/python.d.plugin/am2320/README.md
@@ -1,7 +1,10 @@
<!--
title: "AM2320 sensor monitoring with netdata"
-custom_edit_url: https://github.com/netdata/netdata/edit/master/collectors/python.d.plugin/am2320/README.md
+custom_edit_url: "https://github.com/netdata/netdata/edit/master/collectors/python.d.plugin/am2320/README.md"
sidebar_label: "AM2320"
+learn_status: "Published"
+learn_topic_type: "References"
+learn_rel_path: "References/Collectors references/Remotes/Devices"
-->
# AM2320 sensor monitoring with netdata
@@ -21,7 +24,7 @@ It produces the following charts:
## Configuration
Edit the `python.d/am2320.conf` configuration file using `edit-config` from the Netdata [config
-directory](/docs/configure/nodes.md), which is typically at `/etc/netdata`.
+directory](https://github.com/netdata/netdata/blob/master/docs/configure/nodes.md), which is typically at `/etc/netdata`.
```bash
cd /etc/netdata # Replace this path with your Netdata config directory, if different
diff --git a/collectors/python.d.plugin/anomalies/README.md b/collectors/python.d.plugin/anomalies/README.md
index aaf39ab9..7c59275f 100644
--- a/collectors/python.d.plugin/anomalies/README.md
+++ b/collectors/python.d.plugin/anomalies/README.md
@@ -1,13 +1,17 @@
<!--
title: "Anomaly detection with Netdata"
description: "Use ML-driven anomaly detection to narrow your focus to only affected metrics and services/processes on your node to shorten root cause analysis."
-custom_edit_url: https://github.com/netdata/netdata/edit/master/collectors/python.d.plugin/anomalies/README.md
-sidebar_url: Anomalies
+custom_edit_url: "https://github.com/netdata/netdata/edit/master/collectors/python.d.plugin/anomalies/README.md"
+sidebar_url: "Anomalies"
+sidebar_label: "anomalies"
+learn_status: "Unpublished"
+learn_topic_type: "References"
+learn_rel_path: "References/Collectors references/Misc"
-->
# Anomaly detection with Netdata
-**Note**: Check out the [Netdata Anomaly Advisor](https://learn.netdata.cloud/docs/cloud/insights/anomaly-advisor) for a more native anomaly detection experience within Netdata.
+**Note**: Check out the [Netdata Anomaly Advisor](https://github.com/netdata/netdata/blob/master/docs/cloud/insights/anomaly-advisor.mdx) for a more native anomaly detection experience within Netdata.
This collector uses the Python [PyOD](https://pyod.readthedocs.io/en/latest/index.html) library to perform unsupervised [anomaly detection](https://en.wikipedia.org/wiki/Anomaly_detection) on your Netdata charts and/or dimensions.
@@ -70,7 +74,7 @@ The configuration for the anomalies collector defines how it will behave on your
_**Note**: If you are unsure about any of the below configuration options then it's best to just ignore all this and leave the `anomalies.conf` file alone to begin with. Then you can return to it later if you would like to tune things a bit more once the collector is running for a while and you have a feeling for its performance on your node._
Edit the `python.d/anomalies.conf` configuration file using `edit-config` from the your agent's [config
-directory](/docs/configure/nodes.md), which is usually at `/etc/netdata`.
+directory](https://github.com/netdata/netdata/blob/master/docs/configure/nodes.md), which is usually at `/etc/netdata`.
```bash
cd /etc/netdata # Replace this path with your Netdata config directory, if different
@@ -216,7 +220,7 @@ If you would like to go deeper on what exactly the anomalies collector is doing
## Notes
-- Python 3 is required as the [`netdata-pandas`](https://github.com/netdata/netdata-pandas) package uses Python async libraries ([asks](https://pypi.org/project/asks/) and [trio](https://pypi.org/project/trio/)) to make asynchronous calls to the [Netdata REST API](https://learn.netdata.cloud/docs/agent/web/api) to get the required data for each chart.
+- Python 3 is required as the [`netdata-pandas`](https://github.com/netdata/netdata-pandas) package uses Python async libraries ([asks](https://pypi.org/project/asks/) and [trio](https://pypi.org/project/trio/)) to make asynchronous calls to the [Netdata REST API](https://github.com/netdata/netdata/blob/master/web/api/README.md) to get the required data for each chart.
- Python 3 is also required for the underlying ML libraries of [numba](https://pypi.org/project/numba/), [scikit-learn](https://pypi.org/project/scikit-learn/), and [PyOD](https://pypi.org/project/pyod/).
- It may take a few hours or so (depending on your choice of `train_secs_n`) for the collector to 'settle' into it's typical behaviour in terms of the trained models and probabilities you will see in the normal running of your node.
- As this collector does most of the work in Python itself, with [PyOD](https://pyod.readthedocs.io/en/latest/) leveraging [numba](https://numba.pydata.org/) under the hood, you may want to try it out first on a test or development system to get a sense of its performance characteristics on a node similar to where you would like to use it.
@@ -231,7 +235,7 @@ If you would like to go deeper on what exactly the anomalies collector is doing
- If you activate this collector on a fresh node, it might take a little while to build up enough data to calculate a realistic and useful model.
- Some models like `iforest` can be comparatively expensive (on same n1-standard-2 system above ~2s runtime during predict, ~40s training time, ~50% cpu on both train and predict) so if you would like to use it you might be advised to set a relatively high `update_every` maybe 10, 15 or 30 in `anomalies.conf`.
- Setting a higher `train_every_n` and `update_every` is an easy way to devote less resources on the node to anomaly detection. Specifying less charts and a lower `train_n_secs` will also help reduce resources at the expense of covering less charts and maybe a more noisy model if you set `train_n_secs` to be too small for how your node tends to behave.
-- If you would like to enable this on a Rasberry Pi, then check out [this guide](https://learn.netdata.cloud/guides/monitor/raspberry-pi-anomaly-detection) which will guide you through first installing LLVM.
+- If you would like to enable this on a Raspberry Pi, then check out [this guide](https://github.com/netdata/netdata/blob/master/docs/guides/monitor/raspberry-pi-anomaly-detection.md) which will guide you through first installing LLVM.
## Useful links and further reading
diff --git a/collectors/python.d.plugin/beanstalk/README.md b/collectors/python.d.plugin/beanstalk/README.md
index 3b632597..7e7f30de 100644
--- a/collectors/python.d.plugin/beanstalk/README.md
+++ b/collectors/python.d.plugin/beanstalk/README.md
@@ -1,7 +1,10 @@
<!--
title: "Beanstalk monitoring with Netdata"
-custom_edit_url: https://github.com/netdata/netdata/edit/master/collectors/python.d.plugin/beanstalk/README.md
+custom_edit_url: "https://github.com/netdata/netdata/edit/master/collectors/python.d.plugin/beanstalk/README.md"
sidebar_label: "Beanstalk"
+learn_status: "Published"
+learn_topic_type: "References"
+learn_rel_path: "References/Collectors references/Message brokers"
-->
# Beanstalk monitoring with Netdata
@@ -112,7 +115,7 @@ Provides server and tube-level statistics.
## Configuration
Edit the `python.d/beanstalk.conf` configuration file using `edit-config` from the Netdata [config
-directory](/docs/configure/nodes.md), which is typically at `/etc/netdata`.
+directory](https://github.com/netdata/netdata/blob/master/docs/configure/nodes.md), which is typically at `/etc/netdata`.
```bash
cd /etc/netdata # Replace this path with your Netdata config directory, if different
diff --git a/collectors/python.d.plugin/bind_rndc/README.md b/collectors/python.d.plugin/bind_rndc/README.md
index 2d747f81..e8700188 100644
--- a/collectors/python.d.plugin/bind_rndc/README.md
+++ b/collectors/python.d.plugin/bind_rndc/README.md
@@ -1,7 +1,10 @@
<!--
title: "ISC Bind monitoring with Netdata"
-custom_edit_url: https://github.com/netdata/netdata/edit/master/collectors/python.d.plugin/bind_rndc/README.md
+custom_edit_url: "https://github.com/netdata/netdata/edit/master/collectors/python.d.plugin/bind_rndc/README.md"
sidebar_label: "ISC Bind"
+learn_status: "Published"
+learn_topic_type: "References"
+learn_rel_path: "References/Collectors references/Webapps"
-->
# ISC Bind monitoring with Netdata
@@ -58,7 +61,7 @@ It produces:
## Configuration
Edit the `python.d/bind_rndc.conf` configuration file using `edit-config` from the Netdata [config
-directory](/docs/configure/nodes.md), which is typically at `/etc/netdata`.
+directory](https://github.com/netdata/netdata/blob/master/docs/configure/nodes.md), which is typically at `/etc/netdata`.
```bash
cd /etc/netdata # Replace this path with your Netdata config directory, if different
diff --git a/collectors/python.d.plugin/boinc/README.md b/collectors/python.d.plugin/boinc/README.md
index 4da2d52b..149d37ca 100644
--- a/collectors/python.d.plugin/boinc/README.md
+++ b/collectors/python.d.plugin/boinc/README.md
@@ -1,7 +1,10 @@
<!--
title: "BOINC monitoring with Netdata"
-custom_edit_url: https://github.com/netdata/netdata/edit/master/collectors/python.d.plugin/boinc/README.md
+custom_edit_url: "https://github.com/netdata/netdata/edit/master/collectors/python.d.plugin/boinc/README.md"
sidebar_label: "BOINC"
+learn_status: "Published"
+learn_topic_type: "References"
+learn_rel_path: "References/Collectors references/Distributed computing"
-->
# BOINC monitoring with Netdata
@@ -13,7 +16,7 @@ It provides charts tracking the total number of tasks and active tasks, as well
## Configuration
Edit the `python.d/boinc.conf` configuration file using `edit-config` from the Netdata [config
-directory](/docs/configure/nodes.md), which is typically at `/etc/netdata`.
+directory](https://github.com/netdata/netdata/blob/master/docs/configure/nodes.md), which is typically at `/etc/netdata`.
```bash
cd /etc/netdata # Replace this path with your Netdata config directory, if different
diff --git a/collectors/python.d.plugin/ceph/README.md b/collectors/python.d.plugin/ceph/README.md
index b75ba6d4..e7d0f51e 100644
--- a/collectors/python.d.plugin/ceph/README.md
+++ b/collectors/python.d.plugin/ceph/README.md
@@ -1,7 +1,10 @@
<!--
title: "CEPH monitoring with Netdata"
-custom_edit_url: https://github.com/netdata/netdata/edit/master/collectors/python.d.plugin/ceph/README.md
+custom_edit_url: "https://github.com/netdata/netdata/edit/master/collectors/python.d.plugin/ceph/README.md"
sidebar_label: "CEPH"
+learn_status: "Published"
+learn_topic_type: "References"
+learn_rel_path: "References/Collectors references/Storage"
-->
# CEPH monitoring with Netdata
@@ -28,7 +31,7 @@ Monitors the ceph cluster usage and consumption data of a server, and produces:
## Configuration
Edit the `python.d/ceph.conf` configuration file using `edit-config` from the Netdata [config
-directory](/docs/configure/nodes.md), which is typically at `/etc/netdata`.
+directory](https://github.com/netdata/netdata/blob/master/docs/configure/nodes.md), which is typically at `/etc/netdata`.
```bash
cd /etc/netdata # Replace this path with your Netdata config directory, if different
diff --git a/collectors/python.d.plugin/changefinder/README.md b/collectors/python.d.plugin/changefinder/README.md
index 7ec3a253..326a69dd 100644
--- a/collectors/python.d.plugin/changefinder/README.md
+++ b/collectors/python.d.plugin/changefinder/README.md
@@ -1,7 +1,11 @@
<!--
title: "Online change point detection with Netdata"
description: "Use ML-driven change point detection to narrow your focus and shorten root cause analysis."
-custom_edit_url: https://github.com/netdata/netdata/edit/master/collectors/python.d.plugin/changefinder/README.md
+custom_edit_url: "https://github.com/netdata/netdata/edit/master/collectors/python.d.plugin/changefinder/README.md"
+sidebar_label: "changefinder"
+learn_status: "Published"
+learn_topic_type: "References"
+learn_rel_path: "References/Collectors references/QoS"
-->
# Online changepoint detection with Netdata
@@ -93,7 +97,7 @@ leave the `changefinder.conf` file alone to begin with. Then you can return to i
a bit more once the collector is running for a while and you have a feeling for its performance on your node._
Edit the `python.d/changefinder.conf` configuration file using `edit-config` from the your
-agent's [config directory](/docs/configure/nodes.md), which is usually at `/etc/netdata`.
+agent's [config directory](https://github.com/netdata/netdata/blob/master/docs/configure/nodes.md), which is usually at `/etc/netdata`.
```bash
cd /etc/netdata # Replace this path with your Netdata config directory, if different
diff --git a/collectors/python.d.plugin/dockerd/Makefile.inc b/collectors/python.d.plugin/dockerd/Makefile.inc
deleted file mode 100644
index b100bc6a..00000000
--- a/collectors/python.d.plugin/dockerd/Makefile.inc
+++ /dev/null
@@ -1,13 +0,0 @@
-# SPDX-License-Identifier: GPL-3.0-or-later
-
-# THIS IS NOT A COMPLETE Makefile
-# IT IS INCLUDED BY ITS PARENT'S Makefile.am
-# IT IS REQUIRED TO REFERENCE ALL FILES RELATIVE TO THE PARENT
-
-# install these files
-dist_python_DATA += dockerd/dockerd.chart.py
-dist_pythonconfig_DATA += dockerd/dockerd.conf
-
-# do not install these files, but include them in the distribution
-dist_noinst_DATA += dockerd/README.md dockerd/Makefile.inc
-
diff --git a/collectors/python.d.plugin/dockerd/README.md b/collectors/python.d.plugin/dockerd/README.md
deleted file mode 100644
index 6470a7c0..00000000
--- a/collectors/python.d.plugin/dockerd/README.md
+++ /dev/null
@@ -1,46 +0,0 @@
-<!--
-title: "Docker Engine monitoring with Netdata"
-custom_edit_url: https://github.com/netdata/netdata/edit/master/collectors/python.d.plugin/dockerd/README.md
-sidebar_label: "Docker Engine"
--->
-
-# Docker Engine monitoring with Netdata
-
-Collects docker container health metrics.
-
-**Requirement:**
-
-- `docker` package, required version 3.2.0+
-
-Following charts are drawn:
-
-1. **running containers**
-
- - count
-
-2. **healthy containers**
-
- - count
-
-3. **unhealthy containers**
-
- - count
-
-## Configuration
-
-Edit the `python.d/dockerd.conf` configuration file using `edit-config` from the Netdata [config
-directory](/docs/configure/nodes.md), which is typically at `/etc/netdata`.
-
-```bash
-cd /etc/netdata # Replace this path with your Netdata config directory, if different
-sudo ./edit-config python.d/dockerd.conf
-```
-
-```yaml
- update_every : 1
- priority : 60000
-```
-
----
-
-
diff --git a/collectors/python.d.plugin/dockerd/dockerd.chart.py b/collectors/python.d.plugin/dockerd/dockerd.chart.py
deleted file mode 100644
index bd9640bb..00000000
--- a/collectors/python.d.plugin/dockerd/dockerd.chart.py
+++ /dev/null
@@ -1,86 +0,0 @@
-# -*- coding: utf-8 -*-
-# Description: docker netdata python.d module
-# Author: Kévin Darcel (@tuxity)
-
-try:
- import docker
-
- HAS_DOCKER = True
-except ImportError:
- HAS_DOCKER = False
-
-from distutils.version import StrictVersion
-
-from bases.FrameworkServices.SimpleService import SimpleService
-
-# charts order (can be overridden if you want less charts, or different order)
-ORDER = [
- 'running_containers',
- 'healthy_containers',
- 'unhealthy_containers'
-]
-
-CHARTS = {
- 'running_containers': {
- 'options': [None, 'Number of running containers', 'containers', 'running containers',
- 'docker.running_containers', 'line'],
- 'lines': [
- ['running_containers', 'running']
- ]
- },
- 'healthy_containers': {
- 'options': [None, 'Number of healthy containers', 'containers', 'healthy containers',
- 'docker.healthy_containers', 'line'],
- 'lines': [
- ['healthy_containers', 'healthy']
- ]
- },
- 'unhealthy_containers': {
- 'options': [None, 'Number of unhealthy containers', 'containers', 'unhealthy containers',
- 'docker.unhealthy_containers', 'line'],
- 'lines': [
- ['unhealthy_containers', 'unhealthy']
- ]
- }
-}
-
-MIN_REQUIRED_VERSION = '3.2.0'
-
-
-class Service(SimpleService):
- def __init__(self, configuration=None, name=None):
- SimpleService.__init__(self, configuration=configuration, name=name)
- self.order = ORDER
- self.definitions = CHARTS
- self.client = None
-
- def check(self):
- if not HAS_DOCKER:
- self.error("'docker' package is needed to use dockerd module")
- return False
-
- if StrictVersion(docker.__version__) < StrictVersion(MIN_REQUIRED_VERSION):
- self.error("installed 'docker' package version {0}, minimum required version {1}, please upgrade".format(
- docker.__version__,
- MIN_REQUIRED_VERSION,
- ))
- return False
-
- self.client = docker.DockerClient(base_url=self.configuration.get('url', 'unix://var/run/docker.sock'))
-
- try:
- self.client.ping()
- except docker.errors.APIError as error:
- self.error(error)
- return False
-
- return True
-
- def get_data(self):
- data = dict()
-
- data['running_containers'] = len(self.client.containers.list(sparse=True))
- data['healthy_containers'] = len(self.client.containers.list(filters={'health': 'healthy'}, sparse=True))
- data['unhealthy_containers'] = len(self.client.containers.list(filters={'health': 'unhealthy'}, sparse=True))
-
- return data or None
diff --git a/collectors/python.d.plugin/dockerd/dockerd.conf b/collectors/python.d.plugin/dockerd/dockerd.conf
deleted file mode 100644
index 96c8ee0d..00000000
--- a/collectors/python.d.plugin/dockerd/dockerd.conf
+++ /dev/null
@@ -1,77 +0,0 @@
-# netdata python.d.plugin configuration for dockerd health data API
-#
-# This file is in YaML format. Generally the format is:
-#
-# name: value
-#
-# There are 2 sections:
-# - global variables
-# - one or more JOBS
-#
-# JOBS allow you to collect values from multiple sources.
-# Each source will have its own set of charts.
-#
-# JOB parameters have to be indented (using spaces only, example below).
-
-# ----------------------------------------------------------------------
-# Global Variables
-# These variables set the defaults for all JOBs, however each JOB
-# may define its own, overriding the defaults.
-
-# update_every sets the default data collection frequency.
-# If unset, the python.d.plugin default is used.
-# update_every: 1
-
-# priority controls the order of charts at the netdata dashboard.
-# Lower numbers move the charts towards the top of the page.
-# If unset, the default for python.d.plugin is used.
-# priority: 60000
-
-# penalty indicates whether to apply penalty to update_every in case of failures.
-# Penalty will increase every 5 failed updates in a row. Maximum penalty is 10 minutes.
-# penalty: yes
-
-# autodetection_retry sets the job re-check interval in seconds.
-# The job is not deleted if check fails.
-# Attempts to start the job are made once every autodetection_retry.
-# This feature is disabled by default.
-# autodetection_retry: 0
-
-# ----------------------------------------------------------------------
-# JOBS (data collection sources)
-#
-# The default JOBS share the same *name*. JOBS with the same name
-# are mutually exclusive. Only one of them will be allowed running at
-# any time. This allows autodetection to try several alternatives and
-# pick the one that works.
-#
-# Any number of jobs is supported.
-#
-# All python.d.plugin JOBS (for all its modules) support a set of
-# predefined parameters. These are:
-#
-# job_name:
-# name: myname # the JOB's name as it will appear at the
-# # dashboard (by default is the job_name)
-# # JOBs sharing a name are mutually exclusive
-# update_every: 1 # the JOB's data collection frequency
-# priority: 60000 # the JOB's order on the dashboard
-# penalty: yes # the JOB's penalty
-# autodetection_retry: 0 # the JOB's re-check interval in seconds
-#
-# Additionally to the above, dockerd plugin also supports the following:
-#
-# url: '<scheme>://<host>:<port>/<health_page_api>'
-# # http://localhost:8080/health
-#
-# if the URL is password protected, the following are supported:
-#
-# user: 'username'
-# pass: 'password'
-#
-# ----------------------------------------------------------------------
-# AUTO-DETECTION JOBS
-# only one of them will run (they have the same name)
-#
-local:
- url: 'unix://var/run/docker.sock'
diff --git a/collectors/python.d.plugin/dovecot/README.md b/collectors/python.d.plugin/dovecot/README.md
index e6bbf0d7..358f1ba8 100644
--- a/collectors/python.d.plugin/dovecot/README.md
+++ b/collectors/python.d.plugin/dovecot/README.md
@@ -1,7 +1,10 @@
<!--
title: "Dovecot monitoring with Netdata"
-custom_edit_url: https://github.com/netdata/netdata/edit/master/collectors/python.d.plugin/dovecot/README.md
+custom_edit_url: "https://github.com/netdata/netdata/edit/master/collectors/python.d.plugin/dovecot/README.md"
sidebar_label: "Dovecot"
+learn_status: "Published"
+learn_topic_type: "References"
+learn_rel_path: "References/Collectors references/Webapps"
-->
# Dovecot monitoring with Netdata
@@ -78,7 +81,7 @@ Module gives information with following charts:
## Configuration
Edit the `python.d/dovecot.conf` configuration file using `edit-config` from the Netdata [config
-directory](/docs/configure/nodes.md), which is typically at `/etc/netdata`.
+directory](https://github.com/netdata/netdata/blob/master/docs/configure/nodes.md), which is typically at `/etc/netdata`.
```bash
cd /etc/netdata # Replace this path with your Netdata config directory, if different
diff --git a/collectors/python.d.plugin/example/README.md b/collectors/python.d.plugin/example/README.md
index 0b80aa9e..7e6d2b91 100644
--- a/collectors/python.d.plugin/example/README.md
+++ b/collectors/python.d.plugin/example/README.md
@@ -1,6 +1,10 @@
<!--
-title: "Example"
-custom_edit_url: https://github.com/netdata/netdata/edit/master/collectors/python.d.plugin/example/README.md
+title: "Example module in Python"
+custom_edit_url: "https://github.com/netdata/netdata/edit/master/collectors/python.d.plugin/example/README.md"
+sidebar_label: "Example module in Python"
+learn_status: "Published"
+learn_topic_type: "References"
+learn_rel_path: "References/Collectors references/Mock Collectors"
-->
# Example
@@ -9,6 +13,6 @@ You can add custom data collectors using Python.
Netdata provides an [example python data collection module](https://github.com/netdata/netdata/tree/master/collectors/python.d.plugin/example).
-If you want to write your own collector, read our [writing a new Python module](/collectors/python.d.plugin/README.md#how-to-write-a-new-module) tutorial.
+If you want to write your own collector, read our [writing a new Python module](https://github.com/netdata/netdata/blob/master/collectors/python.d.plugin/README.md#how-to-write-a-new-module) tutorial.
diff --git a/collectors/python.d.plugin/exim/README.md b/collectors/python.d.plugin/exim/README.md
index 92b2d7a5..a9c66c05 100644
--- a/collectors/python.d.plugin/exim/README.md
+++ b/collectors/python.d.plugin/exim/README.md
@@ -1,7 +1,10 @@
<!--
title: "Exim monitoring with Netdata"
-custom_edit_url: https://github.com/netdata/netdata/edit/master/collectors/python.d.plugin/exim/README.md
+custom_edit_url: "https://github.com/netdata/netdata/edit/master/collectors/python.d.plugin/exim/README.md"
sidebar_label: "Exim"
+learn_status: "Published"
+learn_topic_type: "References"
+learn_rel_path: "References/Collectors references/Webapps"
-->
# Exim monitoring with Netdata
diff --git a/collectors/python.d.plugin/fail2ban/README.md b/collectors/python.d.plugin/fail2ban/README.md
index be09e185..6b2c6bba 100644
--- a/collectors/python.d.plugin/fail2ban/README.md
+++ b/collectors/python.d.plugin/fail2ban/README.md
@@ -1,7 +1,10 @@
<!--
title: "Fail2ban monitoring with Netdata"
-custom_edit_url: https://github.com/netdata/netdata/edit/master/collectors/python.d.plugin/fail2ban/README.md
+custom_edit_url: "https://github.com/netdata/netdata/edit/master/collectors/python.d.plugin/fail2ban/README.md"
sidebar_label: "Fail2ban"
+learn_status: "Published"
+learn_topic_type: "References"
+learn_rel_path: "References/Collectors references/Apps"
-->
# Fail2ban monitoring with Netdata
@@ -58,7 +61,7 @@ To persist the changes after rotating the log file, add `create 640 root netdata
## Configuration
Edit the `python.d/fail2ban.conf` configuration file using `edit-config` from the
-Netdata [config directory](/docs/configure/nodes.md), which is typically at `/etc/netdata`.
+Netdata [config directory](https://github.com/netdata/netdata/blob/master/docs/configure/nodes.md), which is typically at `/etc/netdata`.
```bash
cd /etc/netdata # Replace this path with your Netdata config directory, if different
diff --git a/collectors/python.d.plugin/gearman/README.md b/collectors/python.d.plugin/gearman/README.md
index 34ea584a..9ac53cb8 100644
--- a/collectors/python.d.plugin/gearman/README.md
+++ b/collectors/python.d.plugin/gearman/README.md
@@ -1,7 +1,10 @@
<!--
title: "Gearman monitoring with Netdata"
-custom_edit_url: https://github.com/netdata/netdata/edit/master/collectors/python.d.plugin/gearman/README.md
+custom_edit_url: "https://github.com/netdata/netdata/edit/master/collectors/python.d.plugin/gearman/README.md"
sidebar_label: "Gearman"
+learn_status: "Published"
+learn_topic_type: "References"
+learn_rel_path: "References/Collectors references/Distributed computing"
-->
# Gearman monitoring with Netdata
@@ -27,7 +30,7 @@ It produces:
## Configuration
Edit the `python.d/gearman.conf` configuration file using `edit-config` from the Netdata [config
-directory](/docs/configure/nodes.md), which is typically at `/etc/netdata`.
+directory](https://github.com/netdata/netdata/blob/master/docs/configure/nodes.md), which is typically at `/etc/netdata`.
```bash
cd /etc/netdata # Replace this path with your Netdata config directory, if different
diff --git a/collectors/python.d.plugin/go_expvar/README.md b/collectors/python.d.plugin/go_expvar/README.md
index feb150dd..ff786e7c 100644
--- a/collectors/python.d.plugin/go_expvar/README.md
+++ b/collectors/python.d.plugin/go_expvar/README.md
@@ -1,7 +1,10 @@
<!--
title: "Go applications monitoring with Netdata"
-custom_edit_url: https://github.com/netdata/netdata/edit/master/collectors/python.d.plugin/go_expvar/README.md
+custom_edit_url: "https://github.com/netdata/netdata/edit/master/collectors/python.d.plugin/go_expvar/README.md"
sidebar_label: "Go applications"
+learn_status: "Published"
+learn_topic_type: "References"
+learn_rel_path: "References/Collectors references/Application Performance Monitoring"
-->
# Go applications monitoring with Netdata
@@ -209,8 +212,8 @@ See [this issue](https://github.com/netdata/netdata/pull/1902#issuecomment-28449
Please see these two links to the official Netdata documentation for more information about the values:
-- [External plugins - charts](/collectors/plugins.d/README.md#chart)
-- [Chart variables](/collectors/python.d.plugin/README.md#global-variables-order-and-chart)
+- [External plugins - charts](https://github.com/netdata/netdata/blob/master/collectors/plugins.d/README.md#chart)
+- [Chart variables](https://github.com/netdata/netdata/blob/master/collectors/python.d.plugin/README.md#global-variables-order-and-chart)
**Line definitions**
@@ -233,7 +236,7 @@ hidden: False
```
Please see the following link for more information about the options and their default values:
-[External plugins - dimensions](/collectors/plugins.d/README.md#dimension)
+[External plugins - dimensions](https://github.com/netdata/netdata/blob/master/collectors/plugins.d/README.md#dimension)
Apart from top-level expvars, this plugin can also parse expvars stored in a multi-level map;
All dicts in the resulting JSON document are then flattened to one level.
@@ -255,7 +258,7 @@ the first defined key wins and all subsequent keys with the same name are ignore
## Enable the collector
The `go_expvar` collector is disabled by default. To enable it, use `edit-config` from the Netdata [config
-directory](/docs/configure/nodes.md), which is typically at `/etc/netdata`, to edit the `python.d.conf` file.
+directory](https://github.com/netdata/netdata/blob/master/docs/configure/nodes.md), which is typically at `/etc/netdata`, to edit the `python.d.conf` file.
```bash
cd /etc/netdata # Replace this path with your Netdata config directory, if different
@@ -268,7 +271,7 @@ restart netdata`, or the appropriate method for your system, to finish enabling
## Configuration
Edit the `python.d/go_expvar.conf` configuration file using `edit-config` from the Netdata [config
-directory](/docs/configure/nodes.md), which is typically at `/etc/netdata`.
+directory](https://github.com/netdata/netdata/blob/master/docs/configure/nodes.md), which is typically at `/etc/netdata`.
```bash
cd /etc/netdata # Replace this path with your Netdata config directory, if different
diff --git a/collectors/python.d.plugin/haproxy/README.md b/collectors/python.d.plugin/haproxy/README.md
index f16e7258..1aa1a214 100644
--- a/collectors/python.d.plugin/haproxy/README.md
+++ b/collectors/python.d.plugin/haproxy/README.md
@@ -1,7 +1,10 @@
<!--
title: "HAProxy monitoring with Netdata"
-custom_edit_url: https://github.com/netdata/netdata/edit/master/collectors/python.d.plugin/haproxy/README.md
-sidebar_label: "HAProxy"
+custom_edit_url: "https://github.com/netdata/netdata/edit/master/collectors/python.d.plugin/haproxy/README.md"
+sidebar_label: "haproxy-python.d.plugin"
+learn_status: "Published"
+learn_topic_type: "References"
+learn_rel_path: "References/Collectors references/Webapps"
-->
# HAProxy monitoring with Netdata
@@ -39,7 +42,7 @@ It produces:
## Configuration
Edit the `python.d/haproxy.conf` configuration file using `edit-config` from the Netdata [config
-directory](/docs/configure/nodes.md), which is typically at `/etc/netdata`.
+directory](https://github.com/netdata/netdata/blob/master/docs/configure/nodes.md), which is typically at `/etc/netdata`.
```bash
cd /etc/netdata # Replace this path with your Netdata config directory, if different
diff --git a/collectors/python.d.plugin/hddtemp/README.md b/collectors/python.d.plugin/hddtemp/README.md
index d8aba62d..6a253b5b 100644
--- a/collectors/python.d.plugin/hddtemp/README.md
+++ b/collectors/python.d.plugin/hddtemp/README.md
@@ -1,7 +1,10 @@
<!--
title: "Hard drive temperature monitoring with Netdata"
-custom_edit_url: https://github.com/netdata/netdata/edit/master/collectors/python.d.plugin/hddtemp/README.md
+custom_edit_url: "https://github.com/netdata/netdata/edit/master/collectors/python.d.plugin/hddtemp/README.md"
sidebar_label: "Hard drive temperature"
+learn_status: "Published"
+learn_topic_type: "References"
+learn_rel_path: "References/Collectors references/Hardware"
-->
# Hard drive temperature monitoring with Netdata
@@ -16,7 +19,7 @@ It produces one chart **Temperature** with dynamic number of dimensions (one per
## Configuration
Edit the `python.d/hddtemp.conf` configuration file using `edit-config` from the Netdata [config
-directory](/docs/configure/nodes.md), which is typically at `/etc/netdata`.
+directory](https://github.com/netdata/netdata/blob/master/docs/configure/nodes.md), which is typically at `/etc/netdata`.
```bash
cd /etc/netdata # Replace this path with your Netdata config directory, if different
diff --git a/collectors/python.d.plugin/hpssa/README.md b/collectors/python.d.plugin/hpssa/README.md
index c1d21827..72dc7803 100644
--- a/collectors/python.d.plugin/hpssa/README.md
+++ b/collectors/python.d.plugin/hpssa/README.md
@@ -1,7 +1,10 @@
<!--
title: "HP Smart Storage Arrays monitoring with Netdata"
-custom_edit_url: https://github.com/netdata/netdata/edit/master/collectors/python.d.plugin/hpssa/README.md
+custom_edit_url: "https://github.com/netdata/netdata/edit/master/collectors/python.d.plugin/hpssa/README.md"
sidebar_label: "HP Smart Storage Arrays"
+learn_status: "Published"
+learn_topic_type: "References"
+learn_rel_path: "References/Collectors references/Storage"
-->
# HP Smart Storage Arrays monitoring with Netdata
@@ -51,7 +54,7 @@ systemctl restart netdata.service
## Enable the collector
The `hpssa` collector is disabled by default. To enable it, use `edit-config` from the
-Netdata [config directory](/docs/configure/nodes.md), which is typically at `/etc/netdata`, to edit the `python.d.conf`
+Netdata [config directory](https://github.com/netdata/netdata/blob/master/docs/configure/nodes.md), which is typically at `/etc/netdata`, to edit the `python.d.conf`
file.
```bash
@@ -60,12 +63,12 @@ sudo ./edit-config python.d.conf
```
Change the value of the `hpssa` setting to `yes`. Save the file and restart the Netdata Agent with `sudo systemctl
-restart netdata`, or the [appropriate method](/docs/configure/start-stop-restart.md) for your system.
+restart netdata`, or the [appropriate method](https://github.com/netdata/netdata/blob/master/docs/configure/start-stop-restart.md) for your system.
## Configuration
Edit the `python.d/hpssa.conf` configuration file using `edit-config` from the
-Netdata [config directory](/docs/configure/nodes.md), which is typically at `/etc/netdata`.
+Netdata [config directory](https://github.com/netdata/netdata/blob/master/docs/configure/nodes.md), which is typically at `/etc/netdata`.
```bash
cd /etc/netdata # Replace this path with your Netdata config directory, if different
@@ -79,5 +82,5 @@ ssacli_path: /usr/sbin/ssacli
```
Save the file and restart the Netdata Agent with `sudo systemctl restart netdata`, or the [appropriate
-method](/docs/configure/start-stop-restart.md) for your system.
+method](https://github.com/netdata/netdata/blob/master/docs/configure/start-stop-restart.md) for your system.
diff --git a/collectors/python.d.plugin/icecast/README.md b/collectors/python.d.plugin/icecast/README.md
index c122f76a..6fca34ba 100644
--- a/collectors/python.d.plugin/icecast/README.md
+++ b/collectors/python.d.plugin/icecast/README.md
@@ -1,7 +1,10 @@
<!--
title: "Icecast monitoring with Netdata"
-custom_edit_url: https://github.com/netdata/netdata/edit/master/collectors/python.d.plugin/icecast/README.md
+custom_edit_url: "https://github.com/netdata/netdata/edit/master/collectors/python.d.plugin/icecast/README.md"
sidebar_label: "Icecast"
+learn_status: "Published"
+learn_topic_type: "References"
+learn_rel_path: "References/Collectors references/Networking"
-->
# Icecast monitoring with Netdata
@@ -21,7 +24,7 @@ It produces the following charts:
## Configuration
Edit the `python.d/icecast.conf` configuration file using `edit-config` from the Netdata [config
-directory](/docs/configure/nodes.md), which is typically at `/etc/netdata`.
+directory](https://github.com/netdata/netdata/blob/master/docs/configure/nodes.md), which is typically at `/etc/netdata`.
```bash
cd /etc/netdata # Replace this path with your Netdata config directory, if different
diff --git a/collectors/python.d.plugin/ipfs/README.md b/collectors/python.d.plugin/ipfs/README.md
index 3a7c4363..8f5e53b1 100644
--- a/collectors/python.d.plugin/ipfs/README.md
+++ b/collectors/python.d.plugin/ipfs/README.md
@@ -1,7 +1,10 @@
<!--
title: "IPFS monitoring with Netdata"
-custom_edit_url: https://github.com/netdata/netdata/edit/master/collectors/python.d.plugin/ipfs/README.md
+custom_edit_url: "https://github.com/netdata/netdata/edit/master/collectors/python.d.plugin/ipfs/README.md"
sidebar_label: "IPFS"
+learn_status: "Published"
+learn_topic_type: "References"
+learn_rel_path: "References/Collectors references/Storage"
-->
# IPFS monitoring with Netdata
@@ -20,7 +23,7 @@ It produces the following charts:
## Configuration
Edit the `python.d/ipfs.conf` configuration file using `edit-config` from the Netdata [config
-directory](/docs/configure/nodes.md), which is typically at `/etc/netdata`.
+directory](https://github.com/netdata/netdata/blob/master/docs/configure/nodes.md), which is typically at `/etc/netdata`.
```bash
cd /etc/netdata # Replace this path with your Netdata config directory, if different
diff --git a/collectors/python.d.plugin/litespeed/README.md b/collectors/python.d.plugin/litespeed/README.md
index b58b23d7..b9bad463 100644
--- a/collectors/python.d.plugin/litespeed/README.md
+++ b/collectors/python.d.plugin/litespeed/README.md
@@ -1,7 +1,10 @@
<!--
title: "LiteSpeed monitoring with Netdata"
-custom_edit_url: https://github.com/netdata/netdata/edit/master/collectors/python.d.plugin/litespeed/README.md
+custom_edit_url: "https://github.com/netdata/netdata/edit/master/collectors/python.d.plugin/litespeed/README.md"
sidebar_label: "LiteSpeed"
+learn_status: "Published"
+learn_topic_type: "References"
+learn_rel_path: "References/Collectors references/Application Performance Monitoring"
-->
# LiteSpeed monitoring with Netdata
@@ -53,7 +56,7 @@ It produces:
## Configuration
Edit the `python.d/litespeed.conf` configuration file using `edit-config` from the Netdata [config
-directory](/docs/configure/nodes.md), which is typically at `/etc/netdata`.
+directory](https://github.com/netdata/netdata/blob/master/docs/configure/nodes.md), which is typically at `/etc/netdata`.
```bash
cd /etc/netdata # Replace this path with your Netdata config directory, if different
diff --git a/collectors/python.d.plugin/logind/Makefile.inc b/collectors/python.d.plugin/logind/Makefile.inc
deleted file mode 100644
index adadab12..00000000
--- a/collectors/python.d.plugin/logind/Makefile.inc
+++ /dev/null
@@ -1,13 +0,0 @@
-# SPDX-License-Identifier: GPL-3.0-or-later
-
-# THIS IS NOT A COMPLETE Makefile
-# IT IS INCLUDED BY ITS PARENT'S Makefile.am
-# IT IS REQUIRED TO REFERENCE ALL FILES RELATIVE TO THE PARENT
-
-# install these files
-dist_python_DATA += logind/logind.chart.py
-dist_pythonconfig_DATA += logind/logind.conf
-
-# do not install these files, but include them in the distribution
-dist_noinst_DATA += logind/README.md logind/Makefile.inc
-
diff --git a/collectors/python.d.plugin/logind/README.md b/collectors/python.d.plugin/logind/README.md
deleted file mode 100644
index 442d388d..00000000
--- a/collectors/python.d.plugin/logind/README.md
+++ /dev/null
@@ -1,86 +0,0 @@
-<!--
-title: "systemd-logind monitoring with Netdata"
-custom_edit_url: https://github.com/netdata/netdata/edit/master/collectors/python.d.plugin/logind/README.md
-sidebar_label: "systemd-logind"
--->
-
-# Systemd-Logind monitoring with Netdata
-
-Monitors active sessions, users, and seats tracked by `systemd-logind` or `elogind`.
-
-It provides the following charts:
-
-1. **Sessions** Tracks the total number of sessions.
-
- - Graphical: Local graphical sessions (running X11, or Wayland, or something else).
- - Console: Local console sessions.
- - Remote: Remote sessions.
-
-2. **Users** Tracks total number of unique user logins of each type.
-
- - Graphical
- - Console
- - Remote
-
-3. **Seats** Total number of seats in use.
-
- - Seats
-
-## Enable the collector
-
-The `logind` collector is disabled by default. To enable it, use `edit-config` from the Netdata [config
-directory](/docs/configure/nodes.md), which is typically at `/etc/netdata`, to edit the `python.d.conf` file.
-
-```bash
-cd /etc/netdata # Replace this path with your Netdata config directory, if different
-sudo ./edit-config python.d.conf
-```
-
-Change the value of the `logind` setting to `yes`. Save the file and restart the Netdata Agent with `sudo systemctl
-restart netdata`, or the appropriate method for your system, to finish enabling the `logind` collector.
-
-## Configuration
-
-This module needs no configuration. Just make sure the `netdata` user
-can run the `loginctl` command and get a session list without having to
-specify a path.
-
-This will work with any command that can output data in the _exact_
-same format as `loginctl list-sessions --no-legend`. If you have some
-other command you want to use that outputs data in this format, you can
-specify it using the `command` key like so:
-
-```yaml
-command: '/path/to/other/command'
-```
-
-Edit the `python.d/logind.conf` configuration file using `edit-config` from the Netdata [config
-directory](/docs/configure/nodes.md), which is typically at `/etc/netdata`.
-
-```bash
-cd /etc/netdata # Replace this path with your Netdata config directory, if different
-sudo ./edit-config python.d/logind.conf
-```
-
-## Notes
-
-- This module's ability to track logins is dependent on what PAM services
- are configured to register sessions with logind. In particular, for
- most systems, it will only track TTY logins, local desktop logins,
- and logins through remote shell connections.
-
-- The users chart counts _usernames_ not UID's. This is potentially
- important in configurations where multiple users have the same UID.
-
-- The users chart counts any given user name up to once for _each_ type
- of login. So if the same user has a graphical and a console login on a
- system, they will show up once in the graphical count, and once in the
- console count.
-
-- Because the data collection process is rather expensive, this plugin
- is currently disabled by default, and needs to be explicitly enabled in
- `/etc/netdata/python.d.conf` before it will run.
-
----
-
-
diff --git a/collectors/python.d.plugin/logind/logind.chart.py b/collectors/python.d.plugin/logind/logind.chart.py
deleted file mode 100644
index 70866864..00000000
--- a/collectors/python.d.plugin/logind/logind.chart.py
+++ /dev/null
@@ -1,85 +0,0 @@
-# -*- coding: utf-8 -*-
-# Description: logind netdata python.d module
-# Author: Austin S. Hemmelgarn (Ferroin)
-# SPDX-License-Identifier: GPL-3.0-or-later
-
-from bases.FrameworkServices.ExecutableService import ExecutableService
-
-priority = 59999
-disabled_by_default = True
-
-LOGINCTL_COMMAND = 'loginctl list-sessions --no-legend'
-
-ORDER = [
- 'sessions',
- 'users',
- 'seats',
-]
-
-CHARTS = {
- 'sessions': {
- 'options': [None, 'Logind Sessions', 'sessions', 'sessions', 'logind.sessions', 'stacked'],
- 'lines': [
- ['sessions_graphical', 'Graphical', 'absolute', 1, 1],
- ['sessions_console', 'Console', 'absolute', 1, 1],
- ['sessions_remote', 'Remote', 'absolute', 1, 1]
- ]
- },
- 'users': {
- 'options': [None, 'Logind Users', 'users', 'users', 'logind.users', 'stacked'],
- 'lines': [
- ['users_graphical', 'Graphical', 'absolute', 1, 1],
- ['users_console', 'Console', 'absolute', 1, 1],
- ['users_remote', 'Remote', 'absolute', 1, 1]
- ]
- },
- 'seats': {
- 'options': [None, 'Logind Seats', 'seats', 'seats', 'logind.seats', 'line'],
- 'lines': [
- ['seats', 'Active Seats', 'absolute', 1, 1]
- ]
- }
-}
-
-
-class Service(ExecutableService):
- def __init__(self, configuration=None, name=None):
- ExecutableService.__init__(self, configuration=configuration, name=name)
- self.order = ORDER
- self.definitions = CHARTS
- self.command = LOGINCTL_COMMAND
-
- def _get_data(self):
- ret = {
- 'sessions_graphical': 0,
- 'sessions_console': 0,
- 'sessions_remote': 0,
- }
- users = {
- 'graphical': list(),
- 'console': list(),
- 'remote': list()
- }
- seats = list()
- data = self._get_raw_data()
-
- for item in data:
- fields = item.split()
- if len(fields) == 3:
- users['remote'].append(fields[2])
- ret['sessions_remote'] += 1
- elif len(fields) == 4:
- users['graphical'].append(fields[2])
- ret['sessions_graphical'] += 1
- seats.append(fields[3])
- elif len(fields) == 5:
- users['console'].append(fields[2])
- ret['sessions_console'] += 1
- seats.append(fields[3])
-
- ret['users_graphical'] = len(set(users['graphical']))
- ret['users_console'] = len(set(users['console']))
- ret['users_remote'] = len(set(users['remote']))
- ret['seats'] = len(set(seats))
-
- return ret
diff --git a/collectors/python.d.plugin/logind/logind.conf b/collectors/python.d.plugin/logind/logind.conf
deleted file mode 100644
index 01a859d2..00000000
--- a/collectors/python.d.plugin/logind/logind.conf
+++ /dev/null
@@ -1,60 +0,0 @@
-# netdata python.d.plugin configuration for logind
-#
-# This file is in YaML format. Generally the format is:
-#
-# name: value
-#
-# There are 2 sections:
-# - global variables
-# - one or more JOBS
-#
-# JOBS allow you to collect values from multiple sources.
-# Each source will have its own set of charts.
-#
-# JOB parameters have to be indented (using spaces only, example below).
-
-# ----------------------------------------------------------------------
-# Global Variables
-# These variables set the defaults for all JOBs, however each JOB
-# may define its own, overriding the defaults.
-
-# update_every sets the default data collection frequency.
-# If unset, the python.d.plugin default is used.
-# update_every: 1
-
-# priority controls the order of charts at the netdata dashboard.
-# Lower numbers move the charts towards the top of the page.
-# If unset, the default for python.d.plugin is used.
-# priority: 60000
-
-# penalty indicates whether to apply penalty to update_every in case of failures.
-# Penalty will increase every 5 failed updates in a row. Maximum penalty is 10 minutes.
-# penalty: yes
-
-# autodetection_retry sets the job re-check interval in seconds.
-# The job is not deleted if check fails.
-# Attempts to start the job are made once every autodetection_retry.
-# This feature is disabled by default.
-# autodetection_retry: 0
-
-# ----------------------------------------------------------------------
-# JOBS (data collection sources)
-#
-# The default JOBS share the same *name*. JOBS with the same name
-# are mutually exclusive. Only one of them will be allowed running at
-# any time. This allows autodetection to try several alternatives and
-# pick the one that works.
-#
-# Any number of jobs is supported.
-#
-# All python.d.plugin JOBS (for all its modules) support a set of
-# predefined parameters. These are:
-#
-# job_name:
-# name: myname # the JOB's name as it will appear at the
-# # dashboard (by default is the job_name)
-# # JOBs sharing a name are mutually exclusive
-# update_every: 1 # the JOB's data collection frequency
-# priority: 60000 # the JOB's order on the dashboard
-# penalty: yes # the JOB's penalty
-# autodetection_retry: 0 # the JOB's re-check interval in seconds
diff --git a/collectors/python.d.plugin/megacli/README.md b/collectors/python.d.plugin/megacli/README.md
index 3c99c3de..3900de38 100644
--- a/collectors/python.d.plugin/megacli/README.md
+++ b/collectors/python.d.plugin/megacli/README.md
@@ -1,7 +1,10 @@
<!--
title: "MegaRAID controller monitoring with Netdata"
-custom_edit_url: https://github.com/netdata/netdata/edit/master/collectors/python.d.plugin/megacli/README.md
+custom_edit_url: "https://github.com/netdata/netdata/edit/master/collectors/python.d.plugin/megacli/README.md"
sidebar_label: "MegaRAID controllers"
+learn_status: "Published"
+learn_topic_type: "References"
+learn_rel_path: "References/Collectors references/Devices"
-->
# MegaRAID controller monitoring with Netdata
@@ -53,7 +56,7 @@ systemctl restart netdata.service
## Enable the collector
The `megacli` collector is disabled by default. To enable it, use `edit-config` from the
-Netdata [config directory](/docs/configure/nodes.md), which is typically at `/etc/netdata`, to edit the `python.d.conf`
+Netdata [config directory](https://github.com/netdata/netdata/blob/master/docs/configure/nodes.md), which is typically at `/etc/netdata`, to edit the `python.d.conf`
file.
```bash
@@ -67,7 +70,7 @@ with `sudo systemctl restart netdata`, or the appropriate method for your system
## Configuration
Edit the `python.d/megacli.conf` configuration file using `edit-config` from the
-Netdata [config directory](/docs/configure/nodes.md), which is typically at `/etc/netdata`.
+Netdata [config directory](https://github.com/netdata/netdata/blob/master/docs/configure/nodes.md), which is typically at `/etc/netdata`.
```bash
cd /etc/netdata # Replace this path with your Netdata config directory, if different
@@ -81,6 +84,6 @@ do_battery: yes
```
Save the file and restart the Netdata Agent with `sudo systemctl restart netdata`, or the [appropriate
-method](/docs/configure/start-stop-restart.md) for your system.
+method](https://github.com/netdata/netdata/blob/master/docs/configure/start-stop-restart.md) for your system.
diff --git a/collectors/python.d.plugin/memcached/README.md b/collectors/python.d.plugin/memcached/README.md
index 19139ee9..4158ab19 100644
--- a/collectors/python.d.plugin/memcached/README.md
+++ b/collectors/python.d.plugin/memcached/README.md
@@ -1,7 +1,10 @@
<!--
title: "Memcached monitoring with Netdata"
-custom_edit_url: https://github.com/netdata/netdata/edit/master/collectors/python.d.plugin/memcached/README.md
+custom_edit_url: "https://github.com/netdata/netdata/edit/master/collectors/python.d.plugin/memcached/README.md"
sidebar_label: "Memcached"
+learn_status: "Published"
+learn_topic_type: "References"
+learn_rel_path: "References/Collectors references/Databases"
-->
# Memcached monitoring with Netdata
@@ -76,7 +79,7 @@ Collects memory-caching system performance metrics. It reads server response to
## Configuration
Edit the `python.d/memcached.conf` configuration file using `edit-config` from the Netdata [config
-directory](/docs/configure/nodes.md), which is typically at `/etc/netdata`.
+directory](https://github.com/netdata/netdata/blob/master/docs/configure/nodes.md), which is typically at `/etc/netdata`.
```bash
cd /etc/netdata # Replace this path with your Netdata config directory, if different
diff --git a/collectors/python.d.plugin/mongodb/Makefile.inc b/collectors/python.d.plugin/mongodb/Makefile.inc
deleted file mode 100644
index 784945aa..00000000
--- a/collectors/python.d.plugin/mongodb/Makefile.inc
+++ /dev/null
@@ -1,13 +0,0 @@
-# SPDX-License-Identifier: GPL-3.0-or-later
-
-# THIS IS NOT A COMPLETE Makefile
-# IT IS INCLUDED BY ITS PARENT'S Makefile.am
-# IT IS REQUIRED TO REFERENCE ALL FILES RELATIVE TO THE PARENT
-
-# install these files
-dist_python_DATA += mongodb/mongodb.chart.py
-dist_pythonconfig_DATA += mongodb/mongodb.conf
-
-# do not install these files, but include them in the distribution
-dist_noinst_DATA += mongodb/README.md mongodb/Makefile.inc
-
diff --git a/collectors/python.d.plugin/mongodb/README.md b/collectors/python.d.plugin/mongodb/README.md
deleted file mode 100644
index b6dd9c5f..00000000
--- a/collectors/python.d.plugin/mongodb/README.md
+++ /dev/null
@@ -1,210 +0,0 @@
-<!--
-title: "MongoDB monitoring with Netdata"
-custom_edit_url: https://github.com/netdata/netdata/edit/master/collectors/python.d.plugin/mongodb/README.md
-sidebar_label: "MongoDB"
--->
-
-# MongoDB monitoring with Netdata
-
-Monitors performance and health metrics of MongoDB.
-
-## Requirements
-
-- `python-pymongo` package v2.4+.
-
-You need to install it manually.
-
-Number of charts depends on mongodb version, storage engine and other features (replication):
-
-1. **Read requests**:
-
- - query
- - getmore (operation the cursor executes to get additional data from query)
-
-2. **Write requests**:
-
- - insert
- - delete
- - update
-
-3. **Active clients**:
-
- - readers (number of clients with read operations in progress or queued)
- - writers (number of clients with write operations in progress or queued)
-
-4. **Journal transactions**:
-
- - commits (count of transactions that have been written to the journal)
-
-5. **Data written to the journal**:
-
- - volume (volume of data)
-
-6. **Background flush** (MMAPv1):
-
- - average ms (average time taken by flushes to execute)
- - last ms (time taken by the last flush)
-
-7. **Read tickets** (WiredTiger):
-
- - in use (number of read tickets in use)
- - available (number of available read tickets remaining)
-
-8. **Write tickets** (WiredTiger):
-
- - in use (number of write tickets in use)
- - available (number of available write tickets remaining)
-
-9. **Cursors**:
-
-- opened (number of cursors currently opened by MongoDB for clients)
-- timedOut (number of cursors that have timed)
-- noTimeout (number of open cursors with timeout disabled)
-
-10. **Connections**:
-
- - connected (number of clients currently connected to the database server)
- - unused (number of unused connections available for new clients)
-
-11. **Memory usage metrics**:
-
- - virtual
- - resident (amount of memory used by the database process)
- - mapped
- - non mapped
-
-12. **Page faults**:
-
- - page faults (number of times MongoDB had to request from disk)
-
-13. **Cache metrics** (WiredTiger):
-
- - percentage of bytes currently in the cache (amount of space taken by cached data)
- - percentage of tracked dirty bytes in the cache (amount of space taken by dirty data)
-
-14. **Pages evicted from cache** (WiredTiger):
-
- - modified
- - unmodified
-
-15. **Queued requests**:
-
- - readers (number of read request currently queued)
- - writers (number of write request currently queued)
-
-16. **Errors**:
-
- - msg (number of message assertions raised)
- - warning (number of warning assertions raised)
- - regular (number of regular assertions raised)
- - user (number of assertions corresponding to errors generated by users)
-
-17. **Storage metrics** (one chart for every database)
-
- - dataSize (size of all documents + padding in the database)
- - indexSize (size of all indexes in the database)
- - storageSize (size of all extents in the database)
-
-18. **Documents in the database** (one chart for all databases)
-
-- documents (number of objects in the database among all the collections)
-
-19. **tcmalloc metrics**
-
- - central cache free
- - current total thread cache
- - pageheap free
- - pageheap unmapped
- - thread cache free
- - transfer cache free
- - heap size
-
-20. **Commands total/failed rate**
-
- - count
- - createIndex
- - delete
- - eval
- - findAndModify
- - insert
-
-21. **Locks metrics** (acquireCount metrics - number of times the lock was acquired in the specified mode)
-
- - Global lock
- - Database lock
- - Collection lock
- - Metadata lock
- - oplog lock
-
-22. **Replica set members state**
-
- - state
-
-23. **Oplog window**
-
- - window (interval of time between the oldest and the latest entries in the oplog)
-
-24. **Replication lag**
-
- - member (time when last entry from the oplog was applied for every member)
-
-25. **Replication set member heartbeat latency**
-
- - member (time when last heartbeat was received from replica set member)
-
-## Prerequisite
-
-Create a read-only user for Netdata in the admin database.
-
-1. Authenticate as the admin user.
-
-```
-use admin
-db.auth("admin", "<MONGODB_ADMIN_PASSWORD>")
-```
-
-2. Create a user.
-
-```
-# MongoDB 2.x.
-db.addUser("netdata", "<UNIQUE_PASSWORD>", true)
-
-# MongoDB 3.x or higher.
-db.createUser({
- "user":"netdata",
- "pwd": "<UNIQUE_PASSWORD>",
- "roles" : [
- {role: 'read', db: 'admin' },
- {role: 'clusterMonitor', db: 'admin'},
- {role: 'read', db: 'local' }
- ]
-})
-```
-
-## Configuration
-
-Edit the `python.d/mongodb.conf` configuration file using `edit-config` from the Netdata [config
-directory](/docs/configure/nodes.md), which is typically at `/etc/netdata`.
-
-```bash
-cd /etc/netdata # Replace this path with your Netdata config directory, if different
-sudo ./edit-config python.d/mongodb.conf
-```
-
-Sample:
-
-```yaml
-local:
- name : 'local'
- authdb: 'admin'
- host : '127.0.0.1'
- port : 27017
- user : 'netdata'
- pass : 'netdata'
-```
-
-If no configuration is given, module will attempt to connect to mongodb daemon on `127.0.0.1:27017` address
-
----
-
-
diff --git a/collectors/python.d.plugin/mongodb/mongodb.chart.py b/collectors/python.d.plugin/mongodb/mongodb.chart.py
deleted file mode 100644
index 5e8fec83..00000000
--- a/collectors/python.d.plugin/mongodb/mongodb.chart.py
+++ /dev/null
@@ -1,786 +0,0 @@
-# -*- coding: utf-8 -*-
-# Description: mongodb netdata python.d module
-# Author: ilyam8
-# SPDX-License-Identifier: GPL-3.0-or-later
-
-import ssl
-
-from copy import deepcopy
-from datetime import datetime
-from sys import exc_info
-
-try:
- from pymongo import MongoClient, ASCENDING, DESCENDING, version_tuple
- from pymongo.errors import PyMongoError
-
- PYMONGO = True
-except ImportError:
- PYMONGO = False
-
-from bases.FrameworkServices.SimpleService import SimpleService
-
-REPL_SET_STATES = [
- ('1', 'primary'),
- ('8', 'down'),
- ('2', 'secondary'),
- ('3', 'recovering'),
- ('5', 'startup2'),
- ('4', 'fatal'),
- ('7', 'arbiter'),
- ('6', 'unknown'),
- ('9', 'rollback'),
- ('10', 'removed'),
- ('0', 'startup')
-]
-
-
-def multiply_by_100(value):
- return value * 100
-
-
-DEFAULT_METRICS = [
- ('opcounters.delete', None, None),
- ('opcounters.update', None, None),
- ('opcounters.insert', None, None),
- ('opcounters.query', None, None),
- ('opcounters.getmore', None, None),
- ('globalLock.activeClients.readers', 'activeClients_readers', None),
- ('globalLock.activeClients.writers', 'activeClients_writers', None),
- ('connections.available', 'connections_available', None),
- ('connections.current', 'connections_current', None),
- ('mem.mapped', None, None),
- ('mem.resident', None, None),
- ('mem.virtual', None, None),
- ('globalLock.currentQueue.readers', 'currentQueue_readers', None),
- ('globalLock.currentQueue.writers', 'currentQueue_writers', None),
- ('asserts.msg', None, None),
- ('asserts.regular', None, None),
- ('asserts.user', None, None),
- ('asserts.warning', None, None),
- ('extra_info.page_faults', None, None),
- ('metrics.record.moves', None, None),
- ('backgroundFlushing.average_ms', None, multiply_by_100),
- ('backgroundFlushing.last_ms', None, multiply_by_100),
- ('backgroundFlushing.flushes', None, multiply_by_100),
- ('metrics.cursor.timedOut', None, None),
- ('metrics.cursor.open.total', 'cursor_total', None),
- ('metrics.cursor.open.noTimeout', None, None),
- ('cursors.timedOut', None, None),
- ('cursors.totalOpen', 'cursor_total', None)
-]
-
-DUR = [
- ('dur.commits', None, None),
- ('dur.journaledMB', None, multiply_by_100)
-]
-
-WIREDTIGER = [
- ('wiredTiger.concurrentTransactions.read.available', 'wiredTigerRead_available', None),
- ('wiredTiger.concurrentTransactions.read.out', 'wiredTigerRead_out', None),
- ('wiredTiger.concurrentTransactions.write.available', 'wiredTigerWrite_available', None),
- ('wiredTiger.concurrentTransactions.write.out', 'wiredTigerWrite_out', None),
- ('wiredTiger.cache.bytes currently in the cache', None, None),
- ('wiredTiger.cache.tracked dirty bytes in the cache', None, None),
- ('wiredTiger.cache.maximum bytes configured', None, None),
- ('wiredTiger.cache.unmodified pages evicted', 'unmodified', None),
- ('wiredTiger.cache.modified pages evicted', 'modified', None)
-]
-
-TCMALLOC = [
- ('tcmalloc.generic.current_allocated_bytes', None, None),
- ('tcmalloc.generic.heap_size', None, None),
- ('tcmalloc.tcmalloc.central_cache_free_bytes', None, None),
- ('tcmalloc.tcmalloc.current_total_thread_cache_bytes', None, None),
- ('tcmalloc.tcmalloc.pageheap_free_bytes', None, None),
- ('tcmalloc.tcmalloc.pageheap_unmapped_bytes', None, None),
- ('tcmalloc.tcmalloc.thread_cache_free_bytes', None, None),
- ('tcmalloc.tcmalloc.transfer_cache_free_bytes', None, None)
-]
-
-COMMANDS = [
- ('metrics.commands.count.total', 'count_total', None),
- ('metrics.commands.createIndexes.total', 'createIndexes_total', None),
- ('metrics.commands.delete.total', 'delete_total', None),
- ('metrics.commands.eval.total', 'eval_total', None),
- ('metrics.commands.findAndModify.total', 'findAndModify_total', None),
- ('metrics.commands.insert.total', 'insert_total', None),
- ('metrics.commands.delete.total', 'delete_total', None),
- ('metrics.commands.count.failed', 'count_failed', None),
- ('metrics.commands.createIndexes.failed', 'createIndexes_failed', None),
- ('metrics.commands.delete.failed', 'delete_failed', None),
- ('metrics.commands.eval.failed', 'eval_failed', None),
- ('metrics.commands.findAndModify.failed', 'findAndModify_failed', None),
- ('metrics.commands.insert.failed', 'insert_failed', None),
- ('metrics.commands.delete.failed', 'delete_failed', None)
-]
-
-LOCKS = [
- ('locks.Collection.acquireCount.R', 'Collection_R', None),
- ('locks.Collection.acquireCount.r', 'Collection_r', None),
- ('locks.Collection.acquireCount.W', 'Collection_W', None),
- ('locks.Collection.acquireCount.w', 'Collection_w', None),
- ('locks.Database.acquireCount.R', 'Database_R', None),
- ('locks.Database.acquireCount.r', 'Database_r', None),
- ('locks.Database.acquireCount.W', 'Database_W', None),
- ('locks.Database.acquireCount.w', 'Database_w', None),
- ('locks.Global.acquireCount.R', 'Global_R', None),
- ('locks.Global.acquireCount.r', 'Global_r', None),
- ('locks.Global.acquireCount.W', 'Global_W', None),
- ('locks.Global.acquireCount.w', 'Global_w', None),
- ('locks.Metadata.acquireCount.R', 'Metadata_R', None),
- ('locks.Metadata.acquireCount.w', 'Metadata_w', None),
- ('locks.oplog.acquireCount.r', 'oplog_r', None),
- ('locks.oplog.acquireCount.w', 'oplog_w', None)
-]
-
-DBSTATS = [
- 'dataSize',
- 'indexSize',
- 'storageSize',
- 'objects'
-]
-
-# charts order (can be overridden if you want less charts, or different order)
-ORDER = [
- 'read_operations',
- 'write_operations',
- 'active_clients',
- 'journaling_transactions',
- 'journaling_volume',
- 'background_flush_average',
- 'background_flush_last',
- 'background_flush_rate',
- 'wiredtiger_read',
- 'wiredtiger_write',
- 'cursors',
- 'connections',
- 'memory',
- 'page_faults',
- 'queued_requests',
- 'record_moves',
- 'wiredtiger_cache',
- 'wiredtiger_pages_evicted',
- 'asserts',
- 'locks_collection',
- 'locks_database',
- 'locks_global',
- 'locks_metadata',
- 'locks_oplog',
- 'dbstats_objects',
- 'tcmalloc_generic',
- 'tcmalloc_metrics',
- 'command_total_rate',
- 'command_failed_rate'
-]
-
-CHARTS = {
- 'read_operations': {
- 'options': [None, 'Received read requests', 'requests/s', 'throughput metrics',
- 'mongodb.read_operations', 'line'],
- 'lines': [
- ['query', None, 'incremental'],
- ['getmore', None, 'incremental']
- ]
- },
- 'write_operations': {
- 'options': [None, 'Received write requests', 'requests/s', 'throughput metrics',
- 'mongodb.write_operations', 'line'],
- 'lines': [
- ['insert', None, 'incremental'],
- ['update', None, 'incremental'],
- ['delete', None, 'incremental']
- ]
- },
- 'active_clients': {
- 'options': [None, 'Clients with read or write operations in progress or queued', 'clients',
- 'throughput metrics', 'mongodb.active_clients', 'line'],
- 'lines': [
- ['activeClients_readers', 'readers', 'absolute'],
- ['activeClients_writers', 'writers', 'absolute']
- ]
- },
- 'journaling_transactions': {
- 'options': [None, 'Transactions that have been written to the journal', 'commits',
- 'database performance', 'mongodb.journaling_transactions', 'line'],
- 'lines': [
- ['commits', None, 'absolute']
- ]
- },
- 'journaling_volume': {
- 'options': [None, 'Volume of data written to the journal', 'MiB', 'database performance',
- 'mongodb.journaling_volume', 'line'],
- 'lines': [
- ['journaledMB', 'volume', 'absolute', 1, 100]
- ]
- },
- 'background_flush_average': {
- 'options': [None, 'Average time taken by flushes to execute', 'milliseconds', 'database performance',
- 'mongodb.background_flush_average', 'line'],
- 'lines': [
- ['average_ms', 'time', 'absolute', 1, 100]
- ]
- },
- 'background_flush_last': {
- 'options': [None, 'Time taken by the last flush operation to execute', 'milliseconds', 'database performance',
- 'mongodb.background_flush_last', 'line'],
- 'lines': [
- ['last_ms', 'time', 'absolute', 1, 100]
- ]
- },
- 'background_flush_rate': {
- 'options': [None, 'Flushes rate', 'flushes', 'database performance', 'mongodb.background_flush_rate', 'line'],
- 'lines': [
- ['flushes', 'flushes', 'incremental', 1, 1]
- ]
- },
- 'wiredtiger_read': {
- 'options': [None, 'Read tickets in use and remaining', 'tickets', 'database performance',
- 'mongodb.wiredtiger_read', 'stacked'],
- 'lines': [
- ['wiredTigerRead_available', 'available', 'absolute', 1, 1],
- ['wiredTigerRead_out', 'inuse', 'absolute', 1, 1]
- ]
- },
- 'wiredtiger_write': {
- 'options': [None, 'Write tickets in use and remaining', 'tickets', 'database performance',
- 'mongodb.wiredtiger_write', 'stacked'],
- 'lines': [
- ['wiredTigerWrite_available', 'available', 'absolute', 1, 1],
- ['wiredTigerWrite_out', 'inuse', 'absolute', 1, 1]
- ]
- },
- 'cursors': {
- 'options': [None, 'Currently opened cursors, cursors with timeout disabled and timed out cursors',
- 'cursors', 'database performance', 'mongodb.cursors', 'stacked'],
- 'lines': [
- ['cursor_total', 'opened', 'absolute', 1, 1],
- ['noTimeout', None, 'absolute', 1, 1],
- ['timedOut', None, 'incremental', 1, 1]
- ]
- },
- 'connections': {
- 'options': [None, 'Currently connected clients and unused connections', 'connections',
- 'resource utilization', 'mongodb.connections', 'stacked'],
- 'lines': [
- ['connections_available', 'unused', 'absolute', 1, 1],
- ['connections_current', 'connected', 'absolute', 1, 1]
- ]
- },
- 'memory': {
- 'options': [None, 'Memory metrics', 'MiB', 'resource utilization', 'mongodb.memory', 'stacked'],
- 'lines': [
- ['virtual', None, 'absolute', 1, 1],
- ['resident', None, 'absolute', 1, 1],
- ['nonmapped', None, 'absolute', 1, 1],
- ['mapped', None, 'absolute', 1, 1]
- ]
- },
- 'page_faults': {
- 'options': [None, 'Number of times MongoDB had to fetch data from disk', 'request/s',
- 'resource utilization', 'mongodb.page_faults', 'line'],
- 'lines': [
- ['page_faults', None, 'incremental', 1, 1]
- ]
- },
- 'queued_requests': {
- 'options': [None, 'Currently queued read and write requests', 'requests', 'resource saturation',
- 'mongodb.queued_requests', 'line'],
- 'lines': [
- ['currentQueue_readers', 'readers', 'absolute', 1, 1],
- ['currentQueue_writers', 'writers', 'absolute', 1, 1]
- ]
- },
- 'record_moves': {
- 'options': [None, 'Number of times documents had to be moved on-disk', 'number',
- 'resource saturation', 'mongodb.record_moves', 'line'],
- 'lines': [
- ['moves', None, 'incremental', 1, 1]
- ]
- },
- 'asserts': {
- 'options': [
- None,
- 'Number of message, warning, regular, corresponding to errors generated by users assertions raised',
- 'number', 'errors (asserts)', 'mongodb.asserts', 'line'],
- 'lines': [
- ['msg', None, 'incremental', 1, 1],
- ['warning', None, 'incremental', 1, 1],
- ['regular', None, 'incremental', 1, 1],
- ['user', None, 'incremental', 1, 1]
- ]
- },
- 'wiredtiger_cache': {
- 'options': [None, 'The percentage of the wiredTiger cache that is in use and cache with dirty bytes',
- 'percentage', 'resource utilization', 'mongodb.wiredtiger_cache', 'stacked'],
- 'lines': [
- ['wiredTiger_percent_clean', 'inuse', 'absolute', 1, 1000],
- ['wiredTiger_percent_dirty', 'dirty', 'absolute', 1, 1000]
- ]
- },
- 'wiredtiger_pages_evicted': {
- 'options': [None, 'Pages evicted from the cache',
- 'pages', 'resource utilization', 'mongodb.wiredtiger_pages_evicted', 'stacked'],
- 'lines': [
- ['unmodified', None, 'absolute', 1, 1],
- ['modified', None, 'absolute', 1, 1]
- ]
- },
- 'dbstats_objects': {
- 'options': [None, 'Number of documents in the database among all the collections', 'documents',
- 'storage size metrics', 'mongodb.dbstats_objects', 'stacked'],
- 'lines': []
- },
- 'tcmalloc_generic': {
- 'options': [None, 'Tcmalloc generic metrics', 'MiB', 'tcmalloc', 'mongodb.tcmalloc_generic', 'stacked'],
- 'lines': [
- ['current_allocated_bytes', 'allocated', 'absolute', 1, 1 << 20],
- ['heap_size', 'heap_size', 'absolute', 1, 1 << 20]
- ]
- },
- 'tcmalloc_metrics': {
- 'options': [None, 'Tcmalloc metrics', 'KiB', 'tcmalloc', 'mongodb.tcmalloc_metrics', 'stacked'],
- 'lines': [
- ['central_cache_free_bytes', 'central_cache_free', 'absolute', 1, 1024],
- ['current_total_thread_cache_bytes', 'current_total_thread_cache', 'absolute', 1, 1024],
- ['pageheap_free_bytes', 'pageheap_free', 'absolute', 1, 1024],
- ['pageheap_unmapped_bytes', 'pageheap_unmapped', 'absolute', 1, 1024],
- ['thread_cache_free_bytes', 'thread_cache_free', 'absolute', 1, 1024],
- ['transfer_cache_free_bytes', 'transfer_cache_free', 'absolute', 1, 1024]
- ]
- },
- 'command_total_rate': {
- 'options': [None, 'Commands total rate', 'commands/s', 'commands', 'mongodb.command_total_rate', 'stacked'],
- 'lines': [
- ['count_total', 'count', 'incremental', 1, 1],
- ['createIndexes_total', 'createIndexes', 'incremental', 1, 1],
- ['delete_total', 'delete', 'incremental', 1, 1],
- ['eval_total', 'eval', 'incremental', 1, 1],
- ['findAndModify_total', 'findAndModify', 'incremental', 1, 1],
- ['insert_total', 'insert', 'incremental', 1, 1],
- ['update_total', 'update', 'incremental', 1, 1]
- ]
- },
- 'command_failed_rate': {
- 'options': [None, 'Commands failed rate', 'commands/s', 'commands', 'mongodb.command_failed_rate', 'stacked'],
- 'lines': [
- ['count_failed', 'count', 'incremental', 1, 1],
- ['createIndexes_failed', 'createIndexes', 'incremental', 1, 1],
- ['delete_failed', 'delete', 'incremental', 1, 1],
- ['eval_failed', 'eval', 'incremental', 1, 1],
- ['findAndModify_failed', 'findAndModify', 'incremental', 1, 1],
- ['insert_failed', 'insert', 'incremental', 1, 1],
- ['update_failed', 'update', 'incremental', 1, 1]
- ]
- },
- 'locks_collection': {
- 'options': [None, 'Collection lock. Number of times the lock was acquired in the specified mode',
- 'locks', 'locks metrics', 'mongodb.locks_collection', 'stacked'],
- 'lines': [
- ['Collection_R', 'shared', 'incremental'],
- ['Collection_W', 'exclusive', 'incremental'],
- ['Collection_r', 'intent_shared', 'incremental'],
- ['Collection_w', 'intent_exclusive', 'incremental']
- ]
- },
- 'locks_database': {
- 'options': [None, 'Database lock. Number of times the lock was acquired in the specified mode',
- 'locks', 'locks metrics', 'mongodb.locks_database', 'stacked'],
- 'lines': [
- ['Database_R', 'shared', 'incremental'],
- ['Database_W', 'exclusive', 'incremental'],
- ['Database_r', 'intent_shared', 'incremental'],
- ['Database_w', 'intent_exclusive', 'incremental']
- ]
- },
- 'locks_global': {
- 'options': [None, 'Global lock. Number of times the lock was acquired in the specified mode',
- 'locks', 'locks metrics', 'mongodb.locks_global', 'stacked'],
- 'lines': [
- ['Global_R', 'shared', 'incremental'],
- ['Global_W', 'exclusive', 'incremental'],
- ['Global_r', 'intent_shared', 'incremental'],
- ['Global_w', 'intent_exclusive', 'incremental']
- ]
- },
- 'locks_metadata': {
- 'options': [None, 'Metadata lock. Number of times the lock was acquired in the specified mode',
- 'locks', 'locks metrics', 'mongodb.locks_metadata', 'stacked'],
- 'lines': [
- ['Metadata_R', 'shared', 'incremental'],
- ['Metadata_w', 'intent_exclusive', 'incremental']
- ]
- },
- 'locks_oplog': {
- 'options': [None, 'Lock on the oplog. Number of times the lock was acquired in the specified mode',
- 'locks', 'locks metrics', 'mongodb.locks_oplog', 'stacked'],
- 'lines': [
- ['oplog_r', 'intent_shared', 'incremental'],
- ['oplog_w', 'intent_exclusive', 'incremental']
- ]
- }
-}
-
-DEFAULT_HOST = '127.0.0.1'
-DEFAULT_PORT = 27017
-DEFAULT_TIMEOUT = 100
-DEFAULT_AUTHDB = 'admin'
-
-CONN_PARAM_HOST = 'host'
-CONN_PARAM_PORT = 'port'
-CONN_PARAM_SERVER_SELECTION_TIMEOUT_MS = 'serverselectiontimeoutms'
-CONN_PARAM_SSL_SSL = 'ssl'
-CONN_PARAM_SSL_CERT_REQS = 'ssl_cert_reqs'
-CONN_PARAM_SSL_CA_CERTS = 'ssl_ca_certs'
-CONN_PARAM_SSL_CRL_FILE = 'ssl_crlfile'
-CONN_PARAM_SSL_CERT_FILE = 'ssl_certfile'
-CONN_PARAM_SSL_KEY_FILE = 'ssl_keyfile'
-CONN_PARAM_SSL_PEM_PASSPHRASE = 'ssl_pem_passphrase'
-
-
-class Service(SimpleService):
- def __init__(self, configuration=None, name=None):
- SimpleService.__init__(self, configuration=configuration, name=name)
- self.order = ORDER[:]
- self.definitions = deepcopy(CHARTS)
- self.authdb = self.configuration.get('authdb', DEFAULT_AUTHDB)
- self.user = self.configuration.get('user')
- self.password = self.configuration.get('pass')
- self.metrics_to_collect = deepcopy(DEFAULT_METRICS)
- self.connection = None
- self.do_replica = None
- self.databases = list()
-
- def check(self):
- if not PYMONGO:
- self.error('Pymongo package v2.4+ is needed to use mongodb.chart.py')
- return False
- self.connection, server_status, error = self._create_connection()
- if error:
- self.error(error)
- return False
-
- self.build_metrics_to_collect_(server_status)
-
- try:
- data = self._get_data()
- except (LookupError, SyntaxError, AttributeError):
- self.error('Type: %s, error: %s' % (str(exc_info()[0]), str(exc_info()[1])))
- return False
- if isinstance(data, dict) and data:
- self._data_from_check = data
- self.create_charts_(server_status)
- return True
- self.error('_get_data() returned no data or type is not <dict>')
- return False
-
- def build_metrics_to_collect_(self, server_status):
-
- self.do_replica = 'repl' in server_status
- if 'dur' in server_status:
- self.metrics_to_collect.extend(DUR)
- if 'tcmalloc' in server_status:
- self.metrics_to_collect.extend(TCMALLOC)
- if 'commands' in server_status['metrics']:
- self.metrics_to_collect.extend(COMMANDS)
- if 'wiredTiger' in server_status:
- self.metrics_to_collect.extend(WIREDTIGER)
- has_locks = 'locks' in server_status
- if has_locks and 'Collection' in server_status['locks']:
- self.metrics_to_collect.extend(LOCKS)
-
- def create_charts_(self, server_status):
-
- if 'dur' not in server_status:
- self.order.remove('journaling_transactions')
- self.order.remove('journaling_volume')
-
- if 'backgroundFlushing' not in server_status:
- self.order.remove('background_flush_average')
- self.order.remove('background_flush_last')
- self.order.remove('background_flush_rate')
-
- if 'wiredTiger' not in server_status:
- self.order.remove('wiredtiger_write')
- self.order.remove('wiredtiger_read')
- self.order.remove('wiredtiger_cache')
-
- if 'tcmalloc' not in server_status:
- self.order.remove('tcmalloc_generic')
- self.order.remove('tcmalloc_metrics')
-
- if 'commands' not in server_status['metrics']:
- self.order.remove('command_total_rate')
- self.order.remove('command_failed_rate')
-
- has_no_locks = 'locks' not in server_status
- if has_no_locks or 'Collection' not in server_status['locks']:
- self.order.remove('locks_collection')
- self.order.remove('locks_database')
- self.order.remove('locks_global')
- self.order.remove('locks_metadata')
-
- if has_no_locks or 'oplog' not in server_status['locks']:
- self.order.remove('locks_oplog')
-
- for dbase in self.databases:
- self.order.append('_'.join([dbase, 'dbstats']))
- self.definitions['_'.join([dbase, 'dbstats'])] = {
- 'options': [None, '%s: size of all documents, indexes, extents' % dbase, 'KB',
- 'storage size metrics', 'mongodb.dbstats', 'line'],
- 'lines': [
- ['_'.join([dbase, 'dataSize']), 'documents', 'absolute', 1, 1024],
- ['_'.join([dbase, 'indexSize']), 'indexes', 'absolute', 1, 1024],
- ['_'.join([dbase, 'storageSize']), 'extents', 'absolute', 1, 1024]
- ]}
- self.definitions['dbstats_objects']['lines'].append(['_'.join([dbase, 'objects']), dbase, 'absolute'])
-
- if self.do_replica:
- def create_lines(hosts, string):
- lines = list()
- for host in hosts:
- dim_id = '_'.join([host, string])
- lines.append([dim_id, host, 'absolute', 1, 1000])
- return lines
-
- def create_state_lines(states):
- lines = list()
- for state, description in states:
- dim_id = '_'.join([host, 'state', state])
- lines.append([dim_id, description, 'absolute', 1, 1])
- return lines
-
- all_hosts = server_status['repl']['hosts'] + server_status['repl'].get('arbiters', list())
- this_host = server_status['repl']['me']
- other_hosts = [host for host in all_hosts if host != this_host]
-
- if 'local' in self.databases:
- self.order.append('oplog_window')
- self.definitions['oplog_window'] = {
- 'options': [None, 'Interval of time between the oldest and the latest entries in the oplog',
- 'seconds', 'replication and oplog', 'mongodb.oplog_window', 'line'],
- 'lines': [['timeDiff', 'window', 'absolute', 1, 1000]]}
- # Create "heartbeat delay" chart
- self.order.append('heartbeat_delay')
- self.definitions['heartbeat_delay'] = {
- 'options': [
- None,
- 'Time when last heartbeat was received from the replica set member (lastHeartbeatRecv)',
- 'seconds ago', 'replication and oplog', 'mongodb.replication_heartbeat_delay', 'stacked'],
- 'lines': create_lines(other_hosts, 'heartbeat_lag')}
- # Create "optimedate delay" chart
- self.order.append('optimedate_delay')
- self.definitions['optimedate_delay'] = {
- 'options': [None, 'Time when last entry from the oplog was applied (optimeDate)',
- 'seconds ago', 'replication and oplog', 'mongodb.replication_optimedate_delay', 'stacked'],
- 'lines': create_lines(all_hosts, 'optimedate')}
- # Create "replica set members state" chart
- for host in all_hosts:
- chart_name = '_'.join([host, 'state'])
- self.order.append(chart_name)
- self.definitions[chart_name] = {
- 'options': [None, 'Replica set member (%s) current state' % host, 'state',
- 'replication and oplog', 'mongodb.replication_state', 'line'],
- 'lines': create_state_lines(REPL_SET_STATES)}
-
- def _get_raw_data(self):
- raw_data = dict()
-
- raw_data.update(self.get_server_status() or dict())
- raw_data.update(self.get_db_stats() or dict())
- raw_data.update(self.get_repl_set_get_status() or dict())
- raw_data.update(self.get_get_replication_info() or dict())
-
- return raw_data or None
-
- def get_server_status(self):
- raw_data = dict()
- try:
- raw_data['serverStatus'] = self.connection.admin.command('serverStatus')
- except PyMongoError:
- return None
- else:
- return raw_data
-
- def get_db_stats(self):
- if not self.databases:
- return None
-
- raw_data = dict()
- raw_data['dbStats'] = dict()
- try:
- for dbase in self.databases:
- raw_data['dbStats'][dbase] = self.connection[dbase].command('dbStats')
- return raw_data
- except PyMongoError:
- return None
-
- def get_repl_set_get_status(self):
- if not self.do_replica:
- return None
-
- raw_data = dict()
- try:
- raw_data['replSetGetStatus'] = self.connection.admin.command('replSetGetStatus')
- return raw_data
- except PyMongoError:
- return None
-
- def get_get_replication_info(self):
- if not (self.do_replica and 'local' in self.databases):
- return None
-
- raw_data = dict()
- raw_data['getReplicationInfo'] = dict()
- try:
- raw_data['getReplicationInfo']['ASCENDING'] = self.connection.local.oplog.rs.find().sort(
- '$natural', ASCENDING).limit(1)[0]
- raw_data['getReplicationInfo']['DESCENDING'] = self.connection.local.oplog.rs.find().sort(
- '$natural', DESCENDING).limit(1)[0]
- return raw_data
- except PyMongoError:
- return None
-
- def _get_data(self):
- """
- :return: dict
- """
- raw_data = self._get_raw_data()
-
- if not raw_data:
- return None
-
- data = dict()
- serverStatus = raw_data['serverStatus']
- dbStats = raw_data.get('dbStats')
- replSetGetStatus = raw_data.get('replSetGetStatus')
- getReplicationInfo = raw_data.get('getReplicationInfo')
- utc_now = datetime.utcnow()
-
- # serverStatus
- for metric, new_name, func in self.metrics_to_collect:
- value = serverStatus
- for key in metric.split('.'):
- try:
- value = value[key]
- except KeyError:
- break
-
- if not isinstance(value, dict) and key:
- data[new_name or key] = value if not func else func(value)
-
- if 'mapped' in serverStatus['mem']:
- data['nonmapped'] = data['virtual'] - serverStatus['mem'].get('mappedWithJournal', data['mapped'])
-
- if data.get('maximum bytes configured'):
- maximum = data['maximum bytes configured']
- data['wiredTiger_percent_clean'] = int(data['bytes currently in the cache'] * 100 / maximum * 1000)
- data['wiredTiger_percent_dirty'] = int(data['tracked dirty bytes in the cache'] * 100 / maximum * 1000)
-
- # dbStats
- if dbStats:
- for dbase in dbStats:
- for metric in DBSTATS:
- key = '_'.join([dbase, metric])
- data[key] = dbStats[dbase][metric]
-
- # replSetGetStatus
- if replSetGetStatus:
- other_hosts = list()
- members = replSetGetStatus['members']
- unix_epoch = datetime(1970, 1, 1, 0, 0)
-
- for member in members:
- if not member.get('self'):
- other_hosts.append(member)
-
- # Replica set time diff between current time and time when last entry from the oplog was applied
- if member.get('optimeDate', unix_epoch) != unix_epoch:
- member_optimedate = member['name'] + '_optimedate'
- delta = utc_now - member['optimeDate']
- data[member_optimedate] = int(delta_calculation(delta=delta, multiplier=1000))
-
- # Replica set members state
- member_state = member['name'] + '_state'
- for elem in REPL_SET_STATES:
- state = elem[0]
- data.update({'_'.join([member_state, state]): 0})
- data.update({'_'.join([member_state, str(member['state'])]): member['state']})
-
- # Heartbeat lag calculation
- for other in other_hosts:
- if other['lastHeartbeatRecv'] != unix_epoch:
- node = other['name'] + '_heartbeat_lag'
- delta = utc_now - other['lastHeartbeatRecv']
- data[node] = int(delta_calculation(delta=delta, multiplier=1000))
-
- if getReplicationInfo:
- first_event = getReplicationInfo['ASCENDING']['ts'].as_datetime()
- last_event = getReplicationInfo['DESCENDING']['ts'].as_datetime()
- data['timeDiff'] = int(delta_calculation(delta=last_event - first_event, multiplier=1000))
-
- return data
-
- def build_ssl_connection_params(self):
- conf = self.configuration
-
- def cert_req(v):
- if v is None:
- return None
- if not v:
- return ssl.CERT_NONE
- return ssl.CERT_REQUIRED
-
- ssl_params = {
- CONN_PARAM_SSL_SSL: conf.get(CONN_PARAM_SSL_SSL),
- CONN_PARAM_SSL_CERT_REQS: cert_req(conf.get(CONN_PARAM_SSL_CERT_REQS)),
- CONN_PARAM_SSL_CA_CERTS: conf.get(CONN_PARAM_SSL_CA_CERTS),
- CONN_PARAM_SSL_CRL_FILE: conf.get(CONN_PARAM_SSL_CRL_FILE),
- CONN_PARAM_SSL_CERT_FILE: conf.get(CONN_PARAM_SSL_CERT_FILE),
- CONN_PARAM_SSL_KEY_FILE: conf.get(CONN_PARAM_SSL_KEY_FILE),
- CONN_PARAM_SSL_PEM_PASSPHRASE: conf.get(CONN_PARAM_SSL_PEM_PASSPHRASE),
- }
-
- ssl_params = dict((k, v) for k, v in ssl_params.items() if v is not None)
-
- return ssl_params
-
- def build_connection_params(self):
- conf = self.configuration
- params = {
- CONN_PARAM_HOST: conf.get(CONN_PARAM_HOST, DEFAULT_HOST),
- CONN_PARAM_PORT: conf.get(CONN_PARAM_PORT, DEFAULT_PORT),
- }
- if hasattr(MongoClient, 'server_selection_timeout') or version_tuple[0] >= 4:
- params[CONN_PARAM_SERVER_SELECTION_TIMEOUT_MS] = conf.get('timeout', DEFAULT_TIMEOUT)
-
- params.update(self.build_ssl_connection_params())
- return params
-
- def _create_connection(self):
- params = self.build_connection_params()
- self.debug('creating connection, connection params: {0}'.format(sorted(params)))
-
- try:
- connection = MongoClient(**params)
- if self.user and self.password:
- self.debug('authenticating, user: {0}, password: {1}'.format(self.user, self.password))
- getattr(connection, self.authdb).authenticate(name=self.user, password=self.password)
- else:
- self.debug('skip authenticating, user and password are not set')
- # elif self.user:
- # connection.admin.authenticate(name=self.user, mechanism='MONGODB-X509')
- server_status = connection.admin.command('serverStatus')
- except PyMongoError as error:
- return None, None, str(error)
- else:
- try:
- self.databases = connection.database_names()
- except PyMongoError as error:
- self.info('Can\'t collect databases: %s' % str(error))
- return connection, server_status, None
-
-
-def delta_calculation(delta, multiplier=1):
- if hasattr(delta, 'total_seconds'):
- return delta.total_seconds() * multiplier
- return (delta.microseconds + (delta.seconds + delta.days * 24 * 3600) * 10 ** 6) / 10.0 ** 6 * multiplier
diff --git a/collectors/python.d.plugin/mongodb/mongodb.conf b/collectors/python.d.plugin/mongodb/mongodb.conf
deleted file mode 100644
index 9f660f59..00000000
--- a/collectors/python.d.plugin/mongodb/mongodb.conf
+++ /dev/null
@@ -1,102 +0,0 @@
-# netdata python.d.plugin configuration for mongodb
-#
-# This file is in YaML format. Generally the format is:
-#
-# name: value
-#
-# There are 2 sections:
-# - global variables
-# - one or more JOBS
-#
-# JOBS allow you to collect values from multiple sources.
-# Each source will have its own set of charts.
-#
-# JOB parameters have to be indented (using spaces only, example below).
-
-# ----------------------------------------------------------------------
-# Global Variables
-# These variables set the defaults for all JOBs, however each JOB
-# may define its own, overriding the defaults.
-
-# update_every sets the default data collection frequency.
-# If unset, the python.d.plugin default is used.
-# update_every: 1
-
-# priority controls the order of charts at the netdata dashboard.
-# Lower numbers move the charts towards the top of the page.
-# If unset, the default for python.d.plugin is used.
-# priority: 60000
-
-# penalty indicates whether to apply penalty to update_every in case of failures.
-# Penalty will increase every 5 failed updates in a row. Maximum penalty is 10 minutes.
-# penalty: yes
-
-# autodetection_retry sets the job re-check interval in seconds.
-# The job is not deleted if check fails.
-# Attempts to start the job are made once every autodetection_retry.
-# This feature is disabled by default.
-# autodetection_retry: 0
-
-# ----------------------------------------------------------------------
-# JOBS (data collection sources)
-#
-# The default JOBS share the same *name*. JOBS with the same name
-# are mutually exclusive. Only one of them will be allowed running at
-# any time. This allows autodetection to try several alternatives and
-# pick the one that works.
-#
-# Any number of jobs is supported.
-#
-# All python.d.plugin JOBS (for all its modules) support a set of
-# predefined parameters. These are:
-#
-# job_name:
-# name: myname # the JOB's name as it will appear at the
-# # dashboard (by default is the job_name)
-# # JOBs sharing a name are mutually exclusive
-# update_every: 1 # the JOB's data collection frequency
-# priority: 60000 # the JOB's order on the dashboard
-# penalty: yes # the JOB's penalty
-# autodetection_retry: 0 # the JOB's re-check interval in seconds
-#
-# Additionally to the above, mongodb also supports the following:
-#
-# host: 'IP or HOSTNAME' # type <str> the host to connect to
-# port: PORT # type <int> the port to connect to
-#
-# in all cases, the following can also be set:
-#
-# authdb: 'dbname' # database to authenticate the user against,
-# # defaults to "admin".
-# user: 'username' # the mongodb username to use
-# pass: 'password' # the mongodb password to use
-#
-# SSL connection parameters (https://api.mongodb.com/python/current/examples/tls.html):
-#
-# ssl: yes # connect to the server using TLS
-# ssl_cert_reqs: yes # require a certificate from the server when TLS is enabled
-# ssl_ca_certs: '/path/to/ca.pem' # use a specific set of CA certificates
-# ssl_crlfile: '/path/to/crl.pem' # use a certificate revocation lists
-# ssl_certfile: '/path/to/client.pem' # use a client certificate
-# ssl_keyfile: '/path/to/key.pem' # use a specific client certificate key
-# ssl_pem_passphrase: 'passphrase' # use a passphrase to decrypt encrypted private keys
-#
-
-# ----------------------------------------------------------------------
-# to connect to the mongodb on localhost, without a password:
-# ----------------------------------------------------------------------
-# AUTO-DETECTION JOBS
-# only one of them will run (they have the same name)
-
-local:
- name : 'local'
- host : '127.0.0.1'
- port : 27017
-
-# authsample:
-# name : 'secure'
-# host : 'mongodb.example.com'
-# port : 27017
-# authdb : 'admin'
-# user : 'monitor'
-# pass : 'supersecret'
diff --git a/collectors/python.d.plugin/monit/README.md b/collectors/python.d.plugin/monit/README.md
index 13960256..816143eb 100644
--- a/collectors/python.d.plugin/monit/README.md
+++ b/collectors/python.d.plugin/monit/README.md
@@ -1,34 +1,40 @@
<!--
title: "Monit monitoring with Netdata"
-custom_edit_url: https://github.com/netdata/netdata/edit/master/collectors/python.d.plugin/monit/README.md
+custom_edit_url: "https://github.com/netdata/netdata/edit/master/collectors/python.d.plugin/monit/README.md"
sidebar_label: "Monit"
+learn_status: "Published"
+learn_topic_type: "References"
+learn_rel_path: "References/Collectors references/Storage"
-->
# Monit monitoring with Netdata
-Monit monitoring module. Data is grabbed from stats XML interface (exists for a long time, but not mentioned in official documentation). Mostly this plugin shows statuses of monit targets, i.e. [statuses of specified checks](https://mmonit.com/monit/documentation/monit.html#Service-checks).
+Monit monitoring module. Data is grabbed from stats XML interface (exists for a long time, but not mentioned in official
+documentation). Mostly this plugin shows statuses of monit targets, i.e.
+[statuses of specified checks](https://mmonit.com/monit/documentation/monit.html#Service-checks).
-1. **Filesystems**
+1. **Filesystems**
- - Filesystems
- - Directories
- - Files
- - Pipes
+ - Filesystems
+ - Directories
+ - Files
+ - Pipes
-2. **Applications**
+2. **Applications**
- - Processes (+threads/childs)
- - Programs
+ - Processes (+threads/childs)
+ - Programs
-3. **Network**
+3. **Network**
- - Hosts (+latency)
- - Network interfaces
+ - Hosts (+latency)
+ - Network interfaces
## Configuration
-Edit the `python.d/monit.conf` configuration file using `edit-config` from the Netdata [config
-directory](/docs/configure/nodes.md), which is typically at `/etc/netdata`.
+Edit the `python.d/monit.conf` configuration file using `edit-config` from the
+Netdata [config directory](https://github.com/netdata/netdata/blob/master/docs/configure/nodes.md), which is typically
+at `/etc/netdata`.
```bash
cd /etc/netdata # Replace this path with your Netdata config directory, if different
@@ -39,10 +45,10 @@ Sample:
```yaml
local:
- name : 'local'
- url : 'http://localhost:2812'
- user: : admin
- pass: : monit
+ name: 'local'
+ url: 'http://localhost:2812'
+ user: : admin
+ pass: : monit
```
If no configuration is given, module will attempt to connect to monit as `http://localhost:2812`.
diff --git a/collectors/python.d.plugin/nsd/README.md b/collectors/python.d.plugin/nsd/README.md
index e5183aeb..f99726c3 100644
--- a/collectors/python.d.plugin/nsd/README.md
+++ b/collectors/python.d.plugin/nsd/README.md
@@ -1,7 +1,10 @@
<!--
title: "NSD monitoring with Netdata"
-custom_edit_url: https://github.com/netdata/netdata/edit/master/collectors/python.d.plugin/nsd/README.md
+custom_edit_url: "https://github.com/netdata/netdata/edit/master/collectors/python.d.plugin/nsd/README.md"
sidebar_label: "NSD"
+learn_status: "Published"
+learn_topic_type: "References"
+learn_rel_path: "References/Collectors references/Networking"
-->
# NSD monitoring with Netdata
diff --git a/collectors/python.d.plugin/ntpd/README.md b/collectors/python.d.plugin/ntpd/README.md
index 9832707b..8ae923da 100644
--- a/collectors/python.d.plugin/ntpd/README.md
+++ b/collectors/python.d.plugin/ntpd/README.md
@@ -1,90 +1,14 @@
<!--
title: "NTP daemon monitoring with Netdata"
-custom_edit_url: https://github.com/netdata/netdata/edit/master/collectors/python.d.plugin/ntpd/README.md
+custom_edit_url: "https://github.com/netdata/netdata/edit/master/collectors/python.d.plugin/ntpd/README.md"
sidebar_label: "NTP daemon"
+learn_status: "Published"
+learn_topic_type: "References"
+learn_rel_path: "References/Collectors references/Qos"
-->
# NTP daemon monitoring with Netdata
-Monitors the system variables of the local `ntpd` daemon (optional incl. variables of the polled peers) using the NTP Control Message Protocol via UDP socket, similar to `ntpq`, the [standard NTP query program](http://doc.ntp.org/current-stable/ntpq.html).
-
-## Requirements
-
-- Version: `NTPv4`
-- Local interrogation allowed in `/etc/ntp.conf` (default):
-
-```
-# Local users may interrogate the ntp server more closely.
-restrict 127.0.0.1
-restrict ::1
-```
-
-It produces:
-
-1. system
-
- - offset
- - jitter
- - frequency
- - delay
- - dispersion
- - stratum
- - tc
- - precision
-
-2. peers
-
- - offset
- - delay
- - dispersion
- - jitter
- - rootdelay
- - rootdispersion
- - stratum
- - hmode
- - pmode
- - hpoll
- - ppoll
- - precision
-
-## Configuration
-
-Edit the `python.d/ntpd.conf` configuration file using `edit-config` from the Netdata [config
-directory](/docs/configure/nodes.md), which is typically at `/etc/netdata`.
-
-```bash
-cd /etc/netdata # Replace this path with your Netdata config directory, if different
-sudo ./edit-config python.d/ntpd.conf
-```
-
-Sample:
-
-```yaml
-update_every: 10
-
-host: 'localhost'
-port: '123'
-show_peers: yes
-# hide peers with source address in ranges 127.0.0.0/8 and 192.168.0.0/16
-peer_filter: '(127\..*)|(192\.168\..*)'
-# check for new/changed peers every 60 updates
-peer_rescan: 60
-```
-
-Sample (multiple jobs):
-
-Note: `ntp.conf` on the host `otherhost` must be configured to allow queries from our local host by including a line like `restrict <IP> nomodify notrap nopeer`.
-
-```yaml
-local:
- host: 'localhost'
-
-otherhost:
- host: 'otherhost'
-```
-
-If no configuration is given, module will attempt to connect to `ntpd` on `::1:123` or `127.0.0.1:123` and show charts for the systemvars. Use `show_peers: yes` to also show the charts for configured peers. Local peers in the range `127.0.0.0/8` are hidden by default, use `peer_filter: ''` to show all peers.
-
----
-
-
+This collector is deprecated.
+Use [go.d/ntpd](https://github.com/netdata/go.d.plugin/tree/master/modules/ntpd#ntp-daemon-monitoring-with-netdata)
+instead. \ No newline at end of file
diff --git a/collectors/python.d.plugin/ntpd/ntpd.chart.py b/collectors/python.d.plugin/ntpd/ntpd.chart.py
index 275d2276..077124b4 100644
--- a/collectors/python.d.plugin/ntpd/ntpd.chart.py
+++ b/collectors/python.d.plugin/ntpd/ntpd.chart.py
@@ -9,6 +9,8 @@ import struct
from bases.FrameworkServices.SocketService import SocketService
+disabled_by_default = True
+
# NTP Control Message Protocol constants
MODE = 6
HEADER_FORMAT = '!BBHHHHH'
diff --git a/collectors/python.d.plugin/nvidia_smi/README.md b/collectors/python.d.plugin/nvidia_smi/README.md
index bb416944..ce5473c2 100644
--- a/collectors/python.d.plugin/nvidia_smi/README.md
+++ b/collectors/python.d.plugin/nvidia_smi/README.md
@@ -1,14 +1,17 @@
<!--
title: "Nvidia GPU monitoring with Netdata"
-custom_edit_url: https://github.com/netdata/netdata/edit/master/collectors/python.d.plugin/nvidia_smi/README.md
-sidebar_label: "Nvidia GPUs"
+custom_edit_url: "https://github.com/netdata/netdata/edit/master/collectors/python.d.plugin/nvidia_smi/README.md"
+sidebar_label: "nvidia_smi-python.d.plugin"
+learn_status: "Published"
+learn_topic_type: "References"
+learn_rel_path: "References/Collectors references/Devices"
-->
# Nvidia GPU monitoring with Netdata
Monitors performance metrics (memory usage, fan speed, pcie bandwidth utilization, temperature, etc.) using `nvidia-smi` cli tool.
-> **Warning**: this collector does not work when the Netdata Agent is [running in a container](https://learn.netdata.cloud/docs/agent/packaging/docker).
+> **Warning**: this collector does not work when the Netdata Agent is [running in a container](https://github.com/netdata/netdata/blob/master/packaging/docker/README.md).
## Requirements and Notes
@@ -48,7 +51,7 @@ It produces the following charts:
## Configuration
Edit the `python.d/nvidia_smi.conf` configuration file using `edit-config` from the Netdata [config
-directory](/docs/configure/nodes.md), which is typically at `/etc/netdata`.
+directory](https://github.com/netdata/netdata/blob/master/docs/configure/nodes.md), which is typically at `/etc/netdata`.
```bash
cd /etc/netdata # Replace this path with your Netdata config directory, if different
diff --git a/collectors/python.d.plugin/nvidia_smi/nvidia_smi.chart.py b/collectors/python.d.plugin/nvidia_smi/nvidia_smi.chart.py
index 23e90e65..6affae7b 100644
--- a/collectors/python.d.plugin/nvidia_smi/nvidia_smi.chart.py
+++ b/collectors/python.d.plugin/nvidia_smi/nvidia_smi.chart.py
@@ -22,6 +22,7 @@ EMPTY_ROW_LIMIT = 500
POLLER_BREAK_ROW = '</nvidia_smi_log>'
PCI_BANDWIDTH = 'pci_bandwidth'
+PCI_BANDWIDTH_PERCENT = 'pci_bandwidth_percent'
FAN_SPEED = 'fan_speed'
GPU_UTIL = 'gpu_utilization'
MEM_UTIL = 'mem_utilization'
@@ -38,6 +39,7 @@ USER_NUM = 'user_num'
ORDER = [
PCI_BANDWIDTH,
+ PCI_BANDWIDTH_PERCENT,
FAN_SPEED,
GPU_UTIL,
MEM_UTIL,
@@ -56,7 +58,22 @@ ORDER = [
# https://docs.nvidia.com/gameworks/content/gameworkslibrary/coresdk/nvapi/group__gpupstate.html
POWER_STATES = ['P' + str(i) for i in range(0, 16)]
-
+# PCI Transfer data rate in gigabits per second (Gb/s) per generation
+PCI_SPEED = {
+ "1": 2.5,
+ "2": 5,
+ "3": 8,
+ "4": 16,
+ "5": 32
+}
+# PCI encoding per generation
+PCI_ENCODING = {
+ "1": 2/10,
+ "2": 2/10,
+ "3": 2/130,
+ "4": 2/130,
+ "5": 2/130
+}
def gpu_charts(gpu):
fam = gpu.full_name()
@@ -68,6 +85,13 @@ def gpu_charts(gpu):
['tx_util', 'tx', 'absolute', 1, -1],
]
},
+ PCI_BANDWIDTH_PERCENT: {
+ 'options': [None, 'PCI Express Bandwidth Percent', 'percentage', fam, 'nvidia_smi.pci_bandwidth_percent', 'area'],
+ 'lines': [
+ ['rx_util_percent', 'rx_percent'],
+ ['tx_util_percent', 'tx_percent'],
+ ]
+ },
FAN_SPEED: {
'options': [None, 'Fan Speed', 'percentage', fam, 'nvidia_smi.fan_speed', 'line'],
'lines': [
@@ -327,6 +351,24 @@ class GPU:
return 'gpu{0} {1}'.format(self.num, self.name())
@handle_attr_error
+ def pci_link_gen(self):
+ return self.root.find('pci').find('pci_gpu_link_info').find('pcie_gen').find('max_link_gen').text
+
+ @handle_attr_error
+ def pci_link_width(self):
+ return self.root.find('pci').find('pci_gpu_link_info').find('link_widths').find('max_link_width').text.split('x')[0]
+
+ def pci_bw_max(self):
+ link_gen = self.pci_link_gen()
+ link_width = int(self.pci_link_width())
+ if link_gen not in PCI_SPEED or link_gen not in PCI_ENCODING or not link_width:
+ return None
+ # Maximum PCIe Bandwidth = SPEED * WIDTH * (1 - ENCODING) - 1Gb/s.
+ # see details https://enterprise-support.nvidia.com/s/article/understanding-pcie-configuration-for-maximum-performance
+ # return max bandwidth in kilobytes per second (kB/s)
+ return (PCI_SPEED[link_gen] * link_width * (1- PCI_ENCODING[link_gen]) - 1) * 1000 * 1000 / 8
+
+ @handle_attr_error
def rx_util(self):
return self.root.find('pci').find('rx_util').text.split()[0]
@@ -439,6 +481,15 @@ class GPU:
'power_draw': self.power_draw(),
}
+ pci_bw_max = self.pci_bw_max()
+ if not pci_bw_max:
+ data['rx_util_percent'] = 0
+ data['tx_util_percent'] = 0
+ else :
+ data['rx_util_percent'] = str(int(int(self.rx_util())*100/self.pci_bw_max()))
+ data['tx_util_percent'] = str(int(int(self.tx_util())*100/self.pci_bw_max()))
+
+
for v in POWER_STATES:
data['power_state_' + v.lower()] = 0
p_state = self.power_state()
diff --git a/collectors/python.d.plugin/openldap/README.md b/collectors/python.d.plugin/openldap/README.md
index b0cd1db4..4f29bbb4 100644
--- a/collectors/python.d.plugin/openldap/README.md
+++ b/collectors/python.d.plugin/openldap/README.md
@@ -1,7 +1,10 @@
<!--
title: "OpenLDAP monitoring with Netdata"
-custom_edit_url: https://github.com/netdata/netdata/edit/master/collectors/python.d.plugin/openldap/README.md
+custom_edit_url: "https://github.com/netdata/netdata/edit/master/collectors/python.d.plugin/openldap/README.md"
sidebar_label: "OpenLDAP"
+learn_status: "Published"
+learn_topic_type: "References"
+learn_rel_path: "References/Collectors references/Networking"
-->
# OpenLDAP monitoring with Netdata
@@ -56,7 +59,7 @@ Statistics are taken from LDAP monitoring interface. Manual page, slapd-monitor(
## Configuration
Edit the `python.d/openldap.conf` configuration file using `edit-config` from the Netdata [config
-directory](/docs/configure/nodes.md), which is typically at `/etc/netdata`.
+directory](https://github.com/netdata/netdata/blob/master/docs/configure/nodes.md), which is typically at `/etc/netdata`.
```bash
cd /etc/netdata # Replace this path with your Netdata config directory, if different
diff --git a/collectors/python.d.plugin/oracledb/README.md b/collectors/python.d.plugin/oracledb/README.md
index 88024f8c..78f807d6 100644
--- a/collectors/python.d.plugin/oracledb/README.md
+++ b/collectors/python.d.plugin/oracledb/README.md
@@ -1,7 +1,10 @@
<!--
title: "OracleDB monitoring with Netdata"
-custom_edit_url: https://github.com/netdata/netdata/edit/master/collectors/python.d.plugin/oracledb/README.md
+custom_edit_url: "https://github.com/netdata/netdata/edit/master/collectors/python.d.plugin/oracledb/README.md"
sidebar_label: "OracleDB"
+learn_status: "Published"
+learn_topic_type: "References"
+learn_rel_path: "References/Collectors references/Databases"
-->
# OracleDB monitoring with Netdata
@@ -71,7 +74,7 @@ GRANT SELECT_CATALOG_ROLE TO netdata;
## Configuration
Edit the `python.d/oracledb.conf` configuration file using `edit-config` from the Netdata [config
-directory](/docs/configure/nodes.md), which is typically at `/etc/netdata`.
+directory](https://github.com/netdata/netdata/blob/master/docs/configure/nodes.md), which is typically at `/etc/netdata`.
```bash
cd /etc/netdata # Replace this path with your Netdata config directory, if different
diff --git a/collectors/python.d.plugin/postfix/README.md b/collectors/python.d.plugin/postfix/README.md
index 1a546c61..8d646ad5 100644
--- a/collectors/python.d.plugin/postfix/README.md
+++ b/collectors/python.d.plugin/postfix/README.md
@@ -1,7 +1,10 @@
<!--
title: "Postfix monitoring with Netdata"
-custom_edit_url: https://github.com/netdata/netdata/edit/master/collectors/python.d.plugin/postfix/README.md
+custom_edit_url: "https://github.com/netdata/netdata/edit/master/collectors/python.d.plugin/postfix/README.md"
sidebar_label: "Postfix"
+learn_status: "Published"
+learn_topic_type: "References"
+learn_rel_path: "References/Collectors references/Webapps"
-->
# Postfix monitoring with Netdata
diff --git a/collectors/python.d.plugin/proxysql/README.md b/collectors/python.d.plugin/proxysql/README.md
index 8c6a394f..d6c626b5 100644
--- a/collectors/python.d.plugin/proxysql/README.md
+++ b/collectors/python.d.plugin/proxysql/README.md
@@ -1,106 +1,14 @@
<!--
title: "ProxySQL monitoring with Netdata"
-custom_edit_url: https://github.com/netdata/netdata/edit/master/collectors/python.d.plugin/proxysql/README.md
-sidebar_label: "ProxySQL"
+custom_edit_url: "https://github.com/netdata/netdata/edit/master/collectors/python.d.plugin/proxysql/README.md"
+sidebar_label: "proxysql-python.d.plugin"
+learn_status: "Published"
+learn_topic_type: "References"
+learn_rel_path: "References/Collectors references/Databases"
-->
# ProxySQL monitoring with Netdata
-Monitors database backend and frontend performance metrics.
-
-## Requirements
-
-- python library [MySQLdb](https://github.com/PyMySQL/mysqlclient-python) (faster) or [PyMySQL](https://github.com/PyMySQL/PyMySQL) (slower)
-- `netdata` local user to connect to the ProxySQL server.
-
-To create the `netdata` user, follow [the documentation](https://github.com/sysown/proxysql/wiki/Users-configuration#creating-a-new-user).
-
-## Charts
-
-It produces:
-
-1. **Connections (frontend)**
-
- - connected: number of frontend connections currently connected
- - aborted: number of frontend connections aborted due to invalid credential or max_connections reached
- - non_idle: number of frontend connections that are not currently idle
- - created: number of frontend connections created
-
-2. **Questions (frontend)**
-
- - questions: total number of queries sent from frontends
- - slow_queries: number of queries that ran for longer than the threshold in milliseconds defined in global variable `mysql-long_query_time`
-
-3. **Overall Bandwidth (backends)**
-
- - in
- - out
-
-4. **Status (backends)**
-
- - Backends
- - `1=ONLINE`: backend server is fully operational
- - `2=SHUNNED`: backend sever is temporarily taken out of use because of either too many connection errors in a time that was too short, or replication lag exceeded the allowed threshold
- - `3=OFFLINE_SOFT`: when a server is put into OFFLINE_SOFT mode, new incoming connections aren't accepted anymore, while the existing connections are kept until they became inactive. In other words, connections are kept in use until the current transaction is completed. This allows to gracefully detach a backend
- - `4=OFFLINE_HARD`: when a server is put into OFFLINE_HARD mode, the existing connections are dropped, while new incoming connections aren't accepted either. This is equivalent to deleting the server from a hostgroup, or temporarily taking it out of the hostgroup for maintenance work
- - `-1`: Unknown status
-
-5. **Bandwidth (backends)**
-
- - Backends
- - in
- - out
-
-6. **Queries (backends)**
-
- - Backends
- - queries
-
-7. **Latency (backends)**
-
- - Backends
- - ping time
-
-8. **Pool connections (backends)**
-
- - Backends
- - Used: The number of connections are currently used by ProxySQL for sending queries to the backend server.
- - Free: The number of connections are currently free.
- - Established/OK: The number of connections were established successfully.
- - Error: The number of connections weren't established successfully.
-
-9. **Commands**
-
- - Commands
- - Count
- - Duration (Total duration for each command)
-
-10. **Commands Histogram**
-
- - Commands
- - 100us, 500us, ..., 10s, inf: the total number of commands of the given type which executed within the specified time limit and the previous one.
-
-## Configuration
-
-Edit the `python.d/proxysql.conf` configuration file using `edit-config` from the Netdata [config
-directory](/docs/configure/nodes.md), which is typically at `/etc/netdata`.
-
-```bash
-cd /etc/netdata # Replace this path with your Netdata config directory, if different
-sudo ./edit-config python.d/proxysql.conf
-```
-
-```yaml
-tcpipv4:
- name : 'local'
- user : 'stats'
- pass : 'stats'
- host : '127.0.0.1'
- port : '6032'
-```
-
-If no configuration is given, module will fail to run.
-
----
-
-
+This collector is deprecated.
+Use [go.d/proxysql](https://github.com/netdata/go.d.plugin/tree/master/modules/proxysql#proxysql-monitoring-with-netdata)
+instead. \ No newline at end of file
diff --git a/collectors/python.d.plugin/proxysql/proxysql.chart.py b/collectors/python.d.plugin/proxysql/proxysql.chart.py
index 982c28ee..7e06b7bd 100644
--- a/collectors/python.d.plugin/proxysql/proxysql.chart.py
+++ b/collectors/python.d.plugin/proxysql/proxysql.chart.py
@@ -6,6 +6,8 @@
from bases.FrameworkServices.MySQLService import MySQLService
+disabled_by_default = True
+
def query(table, *params):
return 'SELECT {params} FROM {table}'.format(table=table, params=', '.join(params))
diff --git a/collectors/python.d.plugin/puppet/README.md b/collectors/python.d.plugin/puppet/README.md
index 1b06d181..8b98b8a2 100644
--- a/collectors/python.d.plugin/puppet/README.md
+++ b/collectors/python.d.plugin/puppet/README.md
@@ -1,7 +1,10 @@
<!--
title: "Puppet monitoring with Netdata"
-custom_edit_url: https://github.com/netdata/netdata/edit/master/collectors/python.d.plugin/puppet/README.md
+custom_edit_url: "https://github.com/netdata/netdata/edit/master/collectors/python.d.plugin/puppet/README.md"
sidebar_label: "Puppet"
+learn_status: "Published"
+learn_topic_type: "References"
+learn_rel_path: "References/Collectors references/Provisioning tools"
-->
# Puppet monitoring with Netdata
@@ -33,7 +36,7 @@ Following charts are drawn:
## Configuration
Edit the `python.d/puppet.conf` configuration file using `edit-config` from the Netdata [config
-directory](/docs/configure/nodes.md), which is typically at `/etc/netdata`.
+directory](https://github.com/netdata/netdata/blob/master/docs/configure/nodes.md), which is typically at `/etc/netdata`.
```bash
cd /etc/netdata # Replace this path with your Netdata config directory, if different
diff --git a/collectors/python.d.plugin/python.d.conf b/collectors/python.d.plugin/python.d.conf
index 7b43ee20..41385dac 100644
--- a/collectors/python.d.plugin/python.d.conf
+++ b/collectors/python.d.plugin/python.d.conf
@@ -34,7 +34,6 @@ gc_interval: 300
# boinc: yes
# ceph: yes
# changefinder: no
-# dockerd: yes
# dovecot: yes
# this is just an example
@@ -51,7 +50,6 @@ hpssa: no
# icecast: yes
# ipfs: yes
# litespeed: yes
-logind: no
# megacli: yes
# memcached: yes
# mongodb: yes
@@ -73,7 +71,6 @@ logind: no
# sensors: yes
# smartd_log: yes
# spigotmc: yes
-# springboot: yes
# squid: yes
# traefik: yes
# tomcat: yes
diff --git a/collectors/python.d.plugin/rabbitmq/README.md b/collectors/python.d.plugin/rabbitmq/README.md
index 927adcc6..19df6569 100644
--- a/collectors/python.d.plugin/rabbitmq/README.md
+++ b/collectors/python.d.plugin/rabbitmq/README.md
@@ -1,7 +1,10 @@
<!--
title: "RabbitMQ monitoring with Netdata"
-custom_edit_url: https://github.com/netdata/netdata/edit/master/collectors/python.d.plugin/rabbitmq/README.md
-sidebar_label: "RabbitMQ"
+custom_edit_url: "https://github.com/netdata/netdata/edit/master/collectors/python.d.plugin/rabbitmq/README.md"
+sidebar_label: "rabbitmq-python.d.plugin"
+learn_status: "Published"
+learn_topic_type: "References"
+learn_rel_path: "References/Collectors references/Message brokers"
-->
# RabbitMQ monitoring with Netdata
@@ -93,7 +96,7 @@ Per Vhost charts:
## Configuration
Edit the `python.d/rabbitmq.conf` configuration file using `edit-config` from the Netdata [config
-directory](/docs/configure/nodes.md), which is typically at `/etc/netdata`.
+directory](https://github.com/netdata/netdata/blob/master/docs/configure/nodes.md), which is typically at `/etc/netdata`.
```bash
cd /etc/netdata # Replace this path with your Netdata config directory, if different
diff --git a/collectors/python.d.plugin/rethinkdbs/README.md b/collectors/python.d.plugin/rethinkdbs/README.md
index d3fa3553..578c1c0b 100644
--- a/collectors/python.d.plugin/rethinkdbs/README.md
+++ b/collectors/python.d.plugin/rethinkdbs/README.md
@@ -1,7 +1,10 @@
<!--
title: "RethinkDB monitoring with Netdata"
-custom_edit_url: https://github.com/netdata/netdata/edit/master/collectors/python.d.plugin/rethinkdbs/README.md
+custom_edit_url: "https://github.com/netdata/netdata/edit/master/collectors/python.d.plugin/rethinkdbs/README.md"
sidebar_label: "RethinkDB"
+learn_status: "Published"
+learn_topic_type: "References"
+learn_rel_path: "References/Collectors references/Databases"
-->
# RethinkDB monitoring with Netdata
@@ -10,27 +13,28 @@ Collects database server and cluster statistics.
Following charts are drawn:
-1. **Connected Servers**
+1. **Connected Servers**
- - connected
- - missing
+ - connected
+ - missing
-2. **Active Clients**
+2. **Active Clients**
- - active
+ - active
-3. **Queries** per second
+3. **Queries** per second
- - queries
+ - queries
-4. **Documents** per second
+4. **Documents** per second
- - documents
+ - documents
## Configuration
-Edit the `python.d/rethinkdbs.conf` configuration file using `edit-config` from the Netdata [config
-directory](/docs/configure/nodes.md), which is typically at `/etc/netdata`.
+Edit the `python.d/rethinkdbs.conf` configuration file using `edit-config` from the
+Netdata [config directory](https://github.com/netdata/netdata/blob/master/docs/configure/nodes.md), which is typically
+at `/etc/netdata`.
```bash
cd /etc/netdata # Replace this path with your Netdata config directory, if different
@@ -39,11 +43,11 @@ sudo ./edit-config python.d/rethinkdbs.conf
```yaml
localhost:
- name : 'local'
- host : '127.0.0.1'
- port : 28015
- user : "user"
- password : "pass"
+ name: 'local'
+ host: '127.0.0.1'
+ port: 28015
+ user: "user"
+ password: "pass"
```
When no configuration file is found, module tries to connect to `127.0.0.1:28015`.
diff --git a/collectors/python.d.plugin/retroshare/README.md b/collectors/python.d.plugin/retroshare/README.md
index 297df9fc..142b7d5b 100644
--- a/collectors/python.d.plugin/retroshare/README.md
+++ b/collectors/python.d.plugin/retroshare/README.md
@@ -1,7 +1,10 @@
<!--
title: "RetroShare monitoring with Netdata"
-custom_edit_url: https://github.com/netdata/netdata/edit/master/collectors/python.d.plugin/retroshare/README.md
+custom_edit_url: "https://github.com/netdata/netdata/edit/master/collectors/python.d.plugin/retroshare/README.md"
sidebar_label: "RetroShare"
+learn_status: "Published"
+learn_topic_type: "References"
+learn_rel_path: "References/Collectors references/Apm"
-->
# RetroShare monitoring with Netdata
@@ -22,7 +25,7 @@ This module produces the following charts:
## Configuration
Edit the `python.d/retroshare.conf` configuration file using `edit-config` from the Netdata [config
-directory](/docs/configure/nodes.md), which is typically at `/etc/netdata`.
+directory](https://github.com/netdata/netdata/blob/master/docs/configure/nodes.md), which is typically at `/etc/netdata`.
```bash
cd /etc/netdata # Replace this path with your Netdata config directory, if different
diff --git a/collectors/python.d.plugin/riakkv/README.md b/collectors/python.d.plugin/riakkv/README.md
index fe62c671..5e533a41 100644
--- a/collectors/python.d.plugin/riakkv/README.md
+++ b/collectors/python.d.plugin/riakkv/README.md
@@ -1,7 +1,10 @@
<!--
title: "Riak KV monitoring with Netdata"
-custom_edit_url: https://github.com/netdata/netdata/edit/master/collectors/python.d.plugin/riakkv/README.md
+custom_edit_url: "https://github.com/netdata/netdata/edit/master/collectors/python.d.plugin/riakkv/README.md"
sidebar_label: "Riak KV"
+learn_status: "Published"
+learn_topic_type: "References"
+learn_rel_path: "References/Collectors references/Databases"
-->
# Riak KV monitoring with Netdata
@@ -103,7 +106,7 @@ listed
## Configuration
Edit the `python.d/riakkv.conf` configuration file using `edit-config` from the Netdata [config
-directory](/docs/configure/nodes.md), which is typically at `/etc/netdata`.
+directory](https://github.com/netdata/netdata/blob/master/docs/configure/nodes.md), which is typically at `/etc/netdata`.
```bash
cd /etc/netdata # Replace this path with your Netdata config directory, if different
diff --git a/collectors/python.d.plugin/samba/README.md b/collectors/python.d.plugin/samba/README.md
index 767df12d..41ae1c5b 100644
--- a/collectors/python.d.plugin/samba/README.md
+++ b/collectors/python.d.plugin/samba/README.md
@@ -1,7 +1,10 @@
<!--
title: "Samba monitoring with Netdata"
-custom_edit_url: https://github.com/netdata/netdata/edit/master/collectors/python.d.plugin/samba/README.md
+custom_edit_url: "https://github.com/netdata/netdata/edit/master/collectors/python.d.plugin/samba/README.md"
sidebar_label: "Samba"
+learn_status: "Published"
+learn_topic_type: "References"
+learn_rel_path: "References/Collectors references/Apps"
-->
# Samba monitoring with Netdata
@@ -95,7 +98,7 @@ systemctl restart netdata.service
## Enable the collector
The `samba` collector is disabled by default. To enable it, use `edit-config` from the
-Netdata [config directory](/docs/configure/nodes.md), which is typically at `/etc/netdata`, to edit the `python.d.conf`
+Netdata [config directory](https://github.com/netdata/netdata/blob/master/docs/configure/nodes.md), which is typically at `/etc/netdata`, to edit the `python.d.conf`
file.
```bash
@@ -104,12 +107,12 @@ sudo ./edit-config python.d.conf
```
Change the value of the `samba` setting to `yes`. Save the file and restart the Netdata Agent with `sudo systemctl
-restart netdata`, or the [appropriate method](/docs/configure/start-stop-restart.md) for your system.
+restart netdata`, or the [appropriate method](https://github.com/netdata/netdata/blob/master/docs/configure/start-stop-restart.md) for your system.
## Configuration
Edit the `python.d/samba.conf` configuration file using `edit-config` from the
-Netdata [config directory](/docs/configure/nodes.md), which is typically at `/etc/netdata`.
+Netdata [config directory](https://github.com/netdata/netdata/blob/master/docs/configure/nodes.md), which is typically at `/etc/netdata`.
```bash
cd /etc/netdata # Replace this path with your Netdata config directory, if different
diff --git a/collectors/python.d.plugin/sensors/README.md b/collectors/python.d.plugin/sensors/README.md
index e791195d..f5f43585 100644
--- a/collectors/python.d.plugin/sensors/README.md
+++ b/collectors/python.d.plugin/sensors/README.md
@@ -1,7 +1,10 @@
<!--
title: "Linux machine sensors monitoring with Netdata"
-custom_edit_url: https://github.com/netdata/netdata/edit/master/collectors/python.d.plugin/sensors/README.md
-sidebar_label: "Linux machine sensors"
+custom_edit_url: "https://github.com/netdata/netdata/edit/master/collectors/python.d.plugin/sensors/README.md"
+sidebar_label: "sensors-python.d.plugin"
+learn_status: "Published"
+learn_topic_type: "References"
+learn_rel_path: "References/Collectors references/Devices"
-->
# Linux machine sensors monitoring with Netdata
@@ -13,7 +16,7 @@ Charts are created dynamically.
## Configuration
Edit the `python.d/sensors.conf` configuration file using `edit-config` from the Netdata [config
-directory](/docs/configure/nodes.md), which is typically at `/etc/netdata`.
+directory](https://github.com/netdata/netdata/blob/master/docs/configure/nodes.md), which is typically at `/etc/netdata`.
```bash
cd /etc/netdata # Replace this path with your Netdata config directory, if different
@@ -26,7 +29,7 @@ There have been reports from users that on certain servers, ACPI ring buffer err
We are tracking such cases in issue [#827](https://github.com/netdata/netdata/issues/827).
Please join this discussion for help.
-When `lm-sensors` doesn't work on your device (e.g. for RPi temperatures), use [the legacy bash collector](https://learn.netdata.cloud/docs/agent/collectors/charts.d.plugin/sensors)
+When `lm-sensors` doesn't work on your device (e.g. for RPi temperatures), use [the legacy bash collector](https://github.com/netdata/netdata/blob/master/collectors/charts.d.plugin/sensors/README.md)
---
diff --git a/collectors/python.d.plugin/smartd_log/README.md b/collectors/python.d.plugin/smartd_log/README.md
index eef34ce4..7c1e845f 100644
--- a/collectors/python.d.plugin/smartd_log/README.md
+++ b/collectors/python.d.plugin/smartd_log/README.md
@@ -1,7 +1,10 @@
<!--
title: "Storage devices monitoring with Netdata"
-custom_edit_url: https://github.com/netdata/netdata/edit/master/collectors/python.d.plugin/smartd_log/README.md
+custom_edit_url: "https://github.com/netdata/netdata/edit/master/collectors/python.d.plugin/smartd_log/README.md"
sidebar_label: "S.M.A.R.T. attributes"
+learn_status: "Published"
+learn_topic_type: "References"
+learn_rel_path: "References/Collectors references/Devices"
-->
# Storage devices monitoring with Netdata
@@ -106,7 +109,7 @@ Otherwise, all the smartd `.csv` files may get written to `/var/lib/smartmontool
## Configuration
Edit the `python.d/smartd_log.conf` configuration file using `edit-config` from the Netdata [config
-directory](/docs/configure/nodes.md), which is typically at `/etc/netdata`.
+directory](https://github.com/netdata/netdata/blob/master/docs/configure/nodes.md), which is typically at `/etc/netdata`.
```bash
cd /etc/netdata # Replace this path with your Netdata config directory, if different
diff --git a/collectors/python.d.plugin/spigotmc/README.md b/collectors/python.d.plugin/spigotmc/README.md
index 06483188..6d8e4b62 100644
--- a/collectors/python.d.plugin/spigotmc/README.md
+++ b/collectors/python.d.plugin/spigotmc/README.md
@@ -1,7 +1,10 @@
<!--
title: "SpigotMC monitoring with Netdata"
-custom_edit_url: https://github.com/netdata/netdata/edit/master/collectors/python.d.plugin/spigotmc/README.md
+custom_edit_url: "https://github.com/netdata/netdata/edit/master/collectors/python.d.plugin/spigotmc/README.md"
sidebar_label: "SpigotMC"
+learn_status: "Published"
+learn_topic_type: "References"
+learn_rel_path: "References/Collectors references/Webapps"
-->
# SpigotMC monitoring with Netdata
@@ -18,7 +21,7 @@ the data returned by the `tps` or `list` console commands.
## Configuration
Edit the `python.d/spigotmc.conf` configuration file using `edit-config` from the Netdata [config
-directory](/docs/configure/nodes.md), which is typically at `/etc/netdata`.
+directory](https://github.com/netdata/netdata/blob/master/docs/configure/nodes.md), which is typically at `/etc/netdata`.
```bash
cd /etc/netdata # Replace this path with your Netdata config directory, if different
diff --git a/collectors/python.d.plugin/springboot/Makefile.inc b/collectors/python.d.plugin/springboot/Makefile.inc
deleted file mode 100644
index 06775f93..00000000
--- a/collectors/python.d.plugin/springboot/Makefile.inc
+++ /dev/null
@@ -1,13 +0,0 @@
-# SPDX-License-Identifier: GPL-3.0-or-later
-
-# THIS IS NOT A COMPLETE Makefile
-# IT IS INCLUDED BY ITS PARENT'S Makefile.am
-# IT IS REQUIRED TO REFERENCE ALL FILES RELATIVE TO THE PARENT
-
-# install these files
-dist_python_DATA += springboot/springboot.chart.py
-dist_pythonconfig_DATA += springboot/springboot.conf
-
-# do not install these files, but include them in the distribution
-dist_noinst_DATA += springboot/README.md springboot/Makefile.inc
-
diff --git a/collectors/python.d.plugin/springboot/README.md b/collectors/python.d.plugin/springboot/README.md
deleted file mode 100644
index cdbc9a90..00000000
--- a/collectors/python.d.plugin/springboot/README.md
+++ /dev/null
@@ -1,145 +0,0 @@
-<!--
-title: "Java Spring Boot 2 application monitoring with Netdata"
-custom_edit_url: https://github.com/netdata/netdata/edit/master/collectors/python.d.plugin/springboot/README.md
-sidebar_label: "Java Spring Boot 2 applications"
--->
-
-# Java Spring Boot 2 application monitoring with Netdata
-
-Monitors one or more Java Spring-boot applications depending on configuration.
-Netdata can be used to monitor running Java [Spring Boot](https://spring.io/) applications that expose their metrics with the use of the **Spring Boot Actuator** included in Spring Boot library.
-
-## Configuration
-
-The Spring Boot Actuator exposes these metrics over HTTP and is very easy to use:
-
-- add `org.springframework.boot:spring-boot-starter-actuator` to your application dependencies
-- set `endpoints.metrics.sensitive=false` in your `application.properties`
-
-You can create custom Metrics by add and inject a PublicMetrics in your application.
-This is a example to add custom metrics:
-
-```java
-package com.example;
-
-import org.springframework.boot.actuate.endpoint.PublicMetrics;
-import org.springframework.boot.actuate.metrics.Metric;
-import org.springframework.stereotype.Service;
-
-import java.lang.management.ManagementFactory;
-import java.lang.management.MemoryPoolMXBean;
-import java.util.ArrayList;
-import java.util.Collection;
-
-@Service
-public class HeapPoolMetrics implements PublicMetrics {
-
- private static final String PREFIX = "mempool.";
- private static final String KEY_EDEN = PREFIX + "eden";
- private static final String KEY_SURVIVOR = PREFIX + "survivor";
- private static final String KEY_TENURED = PREFIX + "tenured";
-
- @Override
- public Collection<Metric<?>> metrics() {
- Collection<Metric<?>> result = new ArrayList<>(4);
- for (MemoryPoolMXBean mem : ManagementFactory.getMemoryPoolMXBeans()) {
- String poolName = mem.getName();
- String name = null;
- if (poolName.indexOf("Eden Space") != -1) {
- name = KEY_EDEN;
- } else if (poolName.indexOf("Survivor Space") != -1) {
- name = KEY_SURVIVOR;
- } else if (poolName.indexOf("Tenured Gen") != -1 || poolName.indexOf("Old Gen") != -1) {
- name = KEY_TENURED;
- }
-
- if (name != null) {
- result.add(newMemoryMetric(name, mem.getUsage().getMax()));
- result.add(newMemoryMetric(name + ".init", mem.getUsage().getInit()));
- result.add(newMemoryMetric(name + ".committed", mem.getUsage().getCommitted()));
- result.add(newMemoryMetric(name + ".used", mem.getUsage().getUsed()));
- }
- }
- return result;
- }
-
- private Metric<Long> newMemoryMetric(String name, long bytes) {
- return new Metric<>(name, bytes / 1024);
- }
-}
-```
-
-Please refer [Spring Boot Actuator: Production-ready Features](https://docs.spring.io/spring-boot/docs/current/reference/html/production-ready-features.html#production-ready) and [81. Actuator - Part IX. ‘How-to’ guides](https://docs.spring.io/spring-boot/docs/current/reference/html/howto.html#howto-actuator) for more information.
-
-## Charts
-
-1. **Response Codes** in requests/s
-
- - 1xx
- - 2xx
- - 3xx
- - 4xx
- - 5xx
- - others
-
-2. **Threads**
-
- - daemon
- - total
-
-3. **GC Time** in milliseconds and **GC Operations** in operations/s
-
- - Copy
- - MarkSweep
- - ...
-
-4. **Heap Memory Usage** in KB
-
- - used
- - committed
-
-## Usage
-
-Edit the `python.d/springboot.conf` configuration file using `edit-config` from the Netdata [config
-directory](/docs/configure/nodes.md), which is typically at `/etc/netdata`.
-
-```bash
-cd /etc/netdata # Replace this path with your Netdata config directory, if different
-sudo ./edit-config python.d/springboot.conf
-```
-
-This module defines some common charts, and you can add custom charts by change the configurations.
-
-The configuration format is like:
-
-```yaml
-<id>:
- name: '<name>'
- url: '<metrics endpoint>' # ex. http://localhost:8080/metrics
- user: '<username>' # optional
- pass: '<password>' # optional
- defaults:
- [<chart-id>]: true|false
- extras:
- - id: '<chart-id>'
- options:
- title: '***'
- units: '***'
- family: '***'
- context: 'springboot.***'
- charttype: 'stacked' | 'area' | 'line'
- lines:
- - { dimension: 'myapp_ok', name: 'ok', algorithm: 'absolute', multiplier: 1, divisor: 1} # it shows "myapp.ok" metrics
- - { dimension: 'myapp_ng', name: 'ng', algorithm: 'absolute', multiplier: 1, divisor: 1} # it shows "myapp.ng" metrics
-```
-
-By default, it creates `response_code`, `threads`, `gc_time`, `gc_ope` abd `heap` charts.
-You can disable the default charts by set `defaults.<chart-id>: false`.
-
-The dimension name of extras charts should replace `.` to `_`.
-
-Please check
-[springboot.conf](https://raw.githubusercontent.com/netdata/netdata/master/collectors/python.d.plugin/springboot/springboot.conf)
-for more examples.
-
-
diff --git a/collectors/python.d.plugin/springboot/springboot.chart.py b/collectors/python.d.plugin/springboot/springboot.chart.py
deleted file mode 100644
index dbe11d6b..00000000
--- a/collectors/python.d.plugin/springboot/springboot.chart.py
+++ /dev/null
@@ -1,160 +0,0 @@
-# -*- coding: utf-8 -*-
-# Description: tomcat netdata python.d module
-# Author: Wing924
-# SPDX-License-Identifier: GPL-3.0-or-later
-
-import json
-
-from bases.FrameworkServices.UrlService import UrlService
-
-DEFAULT_ORDER = [
- 'response_code',
- 'threads',
- 'gc_time',
- 'gc_ope',
- 'heap',
-]
-
-DEFAULT_CHARTS = {
- 'response_code': {
- 'options': [None, "Response Codes", "requests/s", "response", "springboot.response_code", "stacked"],
- 'lines': [
- ["resp_other", 'Other', 'incremental'],
- ["resp_1xx", '1xx', 'incremental'],
- ["resp_2xx", '2xx', 'incremental'],
- ["resp_3xx", '3xx', 'incremental'],
- ["resp_4xx", '4xx', 'incremental'],
- ["resp_5xx", '5xx', 'incremental'],
- ]
- },
- 'threads': {
- 'options': [None, "Threads", "current threads", "threads", "springboot.threads", "area"],
- 'lines': [
- ["threads_daemon", 'daemon', 'absolute'],
- ["threads", 'total', 'absolute'],
- ]
- },
- 'gc_time': {
- 'options': [None, "GC Time", "milliseconds", "garbage collection", "springboot.gc_time", "stacked"],
- 'lines': [
- ["gc_copy_time", 'Copy', 'incremental'],
- ["gc_marksweepcompact_time", 'MarkSweepCompact', 'incremental'],
- ["gc_parnew_time", 'ParNew', 'incremental'],
- ["gc_concurrentmarksweep_time", 'ConcurrentMarkSweep', 'incremental'],
- ["gc_ps_scavenge_time", 'PS Scavenge', 'incremental'],
- ["gc_ps_marksweep_time", 'PS MarkSweep', 'incremental'],
- ["gc_g1_young_generation_time", 'G1 Young Generation', 'incremental'],
- ["gc_g1_old_generation_time", 'G1 Old Generation', 'incremental'],
- ]
- },
- 'gc_ope': {
- 'options': [None, "GC Operations", "operations/s", "garbage collection", "springboot.gc_ope", "stacked"],
- 'lines': [
- ["gc_copy_count", 'Copy', 'incremental'],
- ["gc_marksweepcompact_count", 'MarkSweepCompact', 'incremental'],
- ["gc_parnew_count", 'ParNew', 'incremental'],
- ["gc_concurrentmarksweep_count", 'ConcurrentMarkSweep', 'incremental'],
- ["gc_ps_scavenge_count", 'PS Scavenge', 'incremental'],
- ["gc_ps_marksweep_count", 'PS MarkSweep', 'incremental'],
- ["gc_g1_young_generation_count", 'G1 Young Generation', 'incremental'],
- ["gc_g1_old_generation_count", 'G1 Old Generation', 'incremental'],
- ]
- },
- 'heap': {
- 'options': [None, "Heap Memory Usage", "KiB", "heap memory", "springboot.heap", "area"],
- 'lines': [
- ["heap_committed", 'committed', "absolute"],
- ["heap_used", 'used', "absolute"],
- ]
- }
-}
-
-
-class ExtraChartError(ValueError):
- pass
-
-
-class Service(UrlService):
- def __init__(self, configuration=None, name=None):
- UrlService.__init__(self, configuration=configuration, name=name)
- self.url = self.configuration.get('url', "http://localhost:8080/metrics")
- self._setup_charts()
-
- def _get_data(self):
- """
- Format data received from http request
- :return: dict
- """
- raw_data = self._get_raw_data()
- if not raw_data:
- return None
-
- try:
- data = json.loads(raw_data)
- except ValueError:
- self.debug('%s is not a valid JSON page' % self.url)
- return None
-
- result = {
- 'resp_1xx': 0,
- 'resp_2xx': 0,
- 'resp_3xx': 0,
- 'resp_4xx': 0,
- 'resp_5xx': 0,
- 'resp_other': 0,
- }
-
- for key, value in data.iteritems():
- if 'counter.status.' in key:
- status_type = key[15:16] + 'xx'
- if status_type[0] not in '12345':
- status_type = 'other'
- result['resp_' + status_type] += value
- else:
- result[key.replace('.', '_')] = value
-
- return result or None
-
- def _setup_charts(self):
- self.order = []
- self.definitions = {}
- defaults = self.configuration.get('defaults', {})
-
- for chart in DEFAULT_ORDER:
- if defaults.get(chart, True):
- self.order.append(chart)
- self.definitions[chart] = DEFAULT_CHARTS[chart]
-
- for extra in self.configuration.get('extras', []):
- self._add_extra_chart(extra)
- self.order.append(extra['id'])
-
- def _add_extra_chart(self, chart):
- chart_id = chart.get('id', None) or self.die('id is not defined in extra chart')
- options = chart.get('options', None) or self.die('option is not defined in extra chart: %s' % chart_id)
- lines = chart.get('lines', None) or self.die('lines is not defined in extra chart: %s' % chart_id)
-
- title = options.get('title', None) or self.die('title is missing: %s' % chart_id)
- units = options.get('units', None) or self.die('units is missing: %s' % chart_id)
- family = options.get('family', title)
- context = options.get('context', 'springboot.' + title)
- charttype = options.get('charttype', 'line')
-
- result = {
- 'options': [None, title, units, family, context, charttype],
- 'lines': [],
- }
-
- for line in lines:
- dimension = line.get('dimension', None) or self.die('dimension is missing: %s' % chart_id)
- name = line.get('name', dimension)
- algorithm = line.get('algorithm', 'absolute')
- multiplier = line.get('multiplier', 1)
- divisor = line.get('divisor', 1)
- result['lines'].append([dimension, name, algorithm, multiplier, divisor])
-
- self.definitions[chart_id] = result
-
- @staticmethod
- def die(error_message):
- raise ExtraChartError(error_message)
diff --git a/collectors/python.d.plugin/springboot/springboot.conf b/collectors/python.d.plugin/springboot/springboot.conf
deleted file mode 100644
index 0cb369cd..00000000
--- a/collectors/python.d.plugin/springboot/springboot.conf
+++ /dev/null
@@ -1,118 +0,0 @@
-# netdata python.d.plugin configuration for springboot
-#
-# This file is in YaML format. Generally the format is:
-#
-# name: value
-#
-# There are 2 sections:
-# - global variables
-# - one or more JOBS
-#
-# JOBS allow you to collect values from multiple sources.
-# Each source will have its own set of charts.
-#
-# JOB parameters have to be indented (using spaces only, example below).
-
-# ----------------------------------------------------------------------
-# Global Variables
-# These variables set the defaults for all JOBs, however each JOB
-# may define its own, overriding the defaults.
-
-# update_every sets the default data collection frequency.
-# If unset, the python.d.plugin default is used.
-# update_every: 1
-
-# priority controls the order of charts at the netdata dashboard.
-# Lower numbers move the charts towards the top of the page.
-# If unset, the default for python.d.plugin is used.
-# priority: 60000
-
-# penalty indicates whether to apply penalty to update_every in case of failures.
-# Penalty will increase every 5 failed updates in a row. Maximum penalty is 10 minutes.
-# penalty: yes
-
-# autodetection_retry sets the job re-check interval in seconds.
-# The job is not deleted if check fails.
-# Attempts to start the job are made once every autodetection_retry.
-# This feature is disabled by default.
-# autodetection_retry: 0
-
-# ----------------------------------------------------------------------
-# JOBS (data collection sources)
-#
-# Any number of jobs is supported.
-#
-# All python.d.plugin JOBS (for all its modules) support a set of
-# predefined parameters. These are:
-#
-# job_name:
-# name: myname # the JOB's name as it will appear at the
-# # dashboard (by default is the job_name)
-# # JOBs sharing a name are mutually exclusive
-# update_every: 1 # the JOB's data collection frequency
-# priority: 60000 # the JOB's order on the dashboard
-# penalty: yes # the JOB's penalty
-# autodetection_retry: 0 # the JOB's re-check interval in seconds
-#
-# Additionally to the above, this plugin also supports the following:
-#
-# url: 'http://127.0.0.1/metrics' # the URL of the spring boot actuator metrics
-#
-# if the URL is password protected, the following are supported:
-#
-# user: 'username'
-# pass: 'password'
-#
-# defaults:
-# [chart_id]: true | false # enables/disables default charts, defaults true.
-# extras: {} # defines extra charts to monitor, please see the example below
-# - id: [chart_id]
-# options: {}
-# lines: []
-#
-# If all defaults is disabled and no extra charts are defined, this module will disable itself, as it has no data to
-# collect.
-#
-# Configuration example
-# ---------------------
-# example:
-# name: 'example'
-# url: 'http://localhost:8080/metrics'
-# defaults:
-# response_code: true
-# threads: true
-# gc_time: true
-# gc_ope: true
-# heap: false
-# extras:
-# - id: 'heap'
-# options: { title: 'Heap Memory Usage', units: 'KB', family: 'heap memory', context: 'springboot.heap', charttype: 'stacked' }
-# lines:
-# - { dimension: 'mem_free', name: 'free'}
-# - { dimension: 'mempool_eden_used', name: 'eden', algorithm: 'absolute', multiplier: 1, divisor: 1}
-# - { dimension: 'mempool_survivor_used', name: 'survivor', algorithm: 'absolute', multiplier: 1, divisor: 1}
-# - { dimension: 'mempool_tenured_used', name: 'tenured', algorithm: 'absolute', multiplier: 1, divisor: 1}
-# - id: 'heap_eden'
-# options: { title: 'Eden Memory Usage', units: 'KB', family: 'heap memory', context: 'springboot.heap_eden', charttype: 'area' }
-# lines:
-# - { dimension: 'mempool_eden_used', name: 'used'}
-# - { dimension: 'mempool_eden_committed', name: 'committed'}
-# - id: 'heap_survivor'
-# options: { title: 'Survivor Memory Usage', units: 'KB', family: 'heap memory', context: 'springboot.heap_survivor', charttype: 'area' }
-# lines:
-# - { dimension: 'mempool_survivor_used', name: 'used'}
-# - { dimension: 'mempool_survivor_committed', name: 'committed'}
-# - id: 'heap_tenured'
-# options: { title: 'Tenured Memory Usage', units: 'KB', family: 'heap memory', context: 'springboot.heap_tenured', charttype: 'area' }
-# lines:
-# - { dimension: 'mempool_tenured_used', name: 'used'}
-# - { dimension: 'mempool_tenured_committed', name: 'committed'}
-
-
-local:
- name: 'local'
- url: 'http://localhost:8080/metrics'
-
-local_ip:
- name: 'local'
- url: 'http://127.0.0.1:8080/metrics'
diff --git a/collectors/python.d.plugin/squid/README.md b/collectors/python.d.plugin/squid/README.md
index c29b69a1..ac6c8371 100644
--- a/collectors/python.d.plugin/squid/README.md
+++ b/collectors/python.d.plugin/squid/README.md
@@ -1,7 +1,10 @@
<!--
title: "Squid monitoring with Netdata"
-custom_edit_url: https://github.com/netdata/netdata/edit/master/collectors/python.d.plugin/squid/README.md
+custom_edit_url: "https://github.com/netdata/netdata/edit/master/collectors/python.d.plugin/squid/README.md"
sidebar_label: "Squid"
+learn_status: "Published"
+learn_topic_type: "References"
+learn_rel_path: "References/Collectors references/Webapps"
-->
# Squid monitoring with Netdata
@@ -35,7 +38,7 @@ It produces following charts:
## Configuration
Edit the `python.d/squid.conf` configuration file using `edit-config` from the Netdata [config
-directory](/docs/configure/nodes.md), which is typically at `/etc/netdata`.
+directory](https://github.com/netdata/netdata/blob/master/docs/configure/nodes.md), which is typically at `/etc/netdata`.
```bash
cd /etc/netdata # Replace this path with your Netdata config directory, if different
diff --git a/collectors/python.d.plugin/tomcat/README.md b/collectors/python.d.plugin/tomcat/README.md
index b7525b88..66ed6d97 100644
--- a/collectors/python.d.plugin/tomcat/README.md
+++ b/collectors/python.d.plugin/tomcat/README.md
@@ -1,7 +1,10 @@
<!--
title: "Apache Tomcat monitoring with Netdata"
-custom_edit_url: https://github.com/netdata/netdata/edit/master/collectors/python.d.plugin/tomcat/README.md
+custom_edit_url: "https://github.com/netdata/netdata/edit/master/collectors/python.d.plugin/tomcat/README.md"
sidebar_label: "Tomcat"
+learn_status: "Published"
+learn_topic_type: "References"
+learn_rel_path: "References/Collectors references/Webapps"
-->
# Apache Tomcat monitoring with Netdata
@@ -30,7 +33,7 @@ Charts:
## Configuration
Edit the `python.d/tomcat.conf` configuration file using `edit-config` from the Netdata [config
-directory](/docs/configure/nodes.md), which is typically at `/etc/netdata`.
+directory](https://github.com/netdata/netdata/blob/master/docs/configure/nodes.md), which is typically at `/etc/netdata`.
```bash
cd /etc/netdata # Replace this path with your Netdata config directory, if different
diff --git a/collectors/python.d.plugin/tor/README.md b/collectors/python.d.plugin/tor/README.md
index b57d77c0..c6680376 100644
--- a/collectors/python.d.plugin/tor/README.md
+++ b/collectors/python.d.plugin/tor/README.md
@@ -1,7 +1,10 @@
<!--
title: "Tor monitoring with Netdata"
-custom_edit_url: https://github.com/netdata/netdata/edit/master/collectors/python.d.plugin/tor/README.md
+custom_edit_url: "https://github.com/netdata/netdata/edit/master/collectors/python.d.plugin/tor/README.md"
sidebar_label: "Tor"
+learn_status: "Published"
+learn_topic_type: "References"
+learn_rel_path: "References/Collectors references/Apps"
-->
# Tor monitoring with Netdata
@@ -23,7 +26,7 @@ It produces only one chart:
## Configuration
Edit the `python.d/tor.conf` configuration file using `edit-config` from the Netdata [config
-directory](/docs/configure/nodes.md), which is typically at `/etc/netdata`.
+directory](https://github.com/netdata/netdata/blob/master/docs/configure/nodes.md), which is typically at `/etc/netdata`.
```bash
cd /etc/netdata # Replace this path with your Netdata config directory, if different
diff --git a/collectors/python.d.plugin/traefik/README.md b/collectors/python.d.plugin/traefik/README.md
index 251cdf2e..cf30a82a 100644
--- a/collectors/python.d.plugin/traefik/README.md
+++ b/collectors/python.d.plugin/traefik/README.md
@@ -1,7 +1,10 @@
<!--
title: "Traefik monitoring with Netdata"
-custom_edit_url: https://github.com/netdata/netdata/edit/master/collectors/python.d.plugin/traefik/README.md
-sidebar_label: "Traefik"
+custom_edit_url: "https://github.com/netdata/netdata/edit/master/collectors/python.d.plugin/traefik/README.md"
+sidebar_label: "traefik-python.d.plugin"
+learn_status: "Published"
+learn_topic_type: "References"
+learn_rel_path: "References/Collectors references/Webapps"
-->
# Traefik monitoring with Netdata
@@ -10,45 +13,46 @@ Uses the `health` API to provide statistics.
It produces:
-1. **Responses** by statuses
+1. **Responses** by statuses
- - success (1xx, 2xx, 304)
- - error (5xx)
- - redirect (3xx except 304)
- - bad (4xx)
- - other (all other responses)
+ - success (1xx, 2xx, 304)
+ - error (5xx)
+ - redirect (3xx except 304)
+ - bad (4xx)
+ - other (all other responses)
-2. **Responses** by codes
+2. **Responses** by codes
- - 2xx (successful)
- - 5xx (internal server errors)
- - 3xx (redirect)
- - 4xx (bad)
- - 1xx (informational)
- - other (non-standart responses)
+ - 2xx (successful)
+ - 5xx (internal server errors)
+ - 3xx (redirect)
+ - 4xx (bad)
+ - 1xx (informational)
+ - other (non-standart responses)
-3. **Detailed Response Codes** requests/s (number of responses for each response code family individually)
+3. **Detailed Response Codes** requests/s (number of responses for each response code family individually)
-4. **Requests**/s
+4. **Requests**/s
- - request statistics
+ - request statistics
-5. **Total response time**
+5. **Total response time**
- - sum of all response time
+ - sum of all response time
-6. **Average response time**
+6. **Average response time**
-7. **Average response time per iteration**
+7. **Average response time per iteration**
-8. **Uptime**
+8. **Uptime**
- - Traefik server uptime
+ - Traefik server uptime
## Configuration
-Edit the `python.d/traefik.conf` configuration file using `edit-config` from the Netdata [config
-directory](/docs/configure/nodes.md), which is typically at `/etc/netdata`.
+Edit the `python.d/traefik.conf` configuration file using `edit-config` from the
+Netdata [config directory](https://github.com/netdata/netdata/blob/master/docs/configure/nodes.md), which is typically
+at `/etc/netdata`.
```bash
cd /etc/netdata # Replace this path with your Netdata config directory, if different
@@ -60,11 +64,11 @@ Needs only `url` to server's `health`
Here is an example for local server:
```yaml
-update_every : 1
-priority : 60000
+update_every: 1
+priority: 60000
local:
- url : 'http://localhost:8080/health'
+ url: 'http://localhost:8080/health'
```
Without configuration, module attempts to connect to `http://localhost:8080/health`.
diff --git a/collectors/python.d.plugin/uwsgi/README.md b/collectors/python.d.plugin/uwsgi/README.md
index 58db1a41..dcc2dc38 100644
--- a/collectors/python.d.plugin/uwsgi/README.md
+++ b/collectors/python.d.plugin/uwsgi/README.md
@@ -1,7 +1,10 @@
<!--
title: "uWSGI monitoring with Netdata"
-custom_edit_url: https://github.com/netdata/netdata/edit/master/collectors/python.d.plugin/uwsgi/README.md
+custom_edit_url: "https://github.com/netdata/netdata/edit/master/collectors/python.d.plugin/uwsgi/README.md"
sidebar_label: "uWSGI"
+learn_status: "Published"
+learn_topic_type: "References"
+learn_rel_path: "References/Collectors references/Webapps"
-->
# uWSGI monitoring with Netdata
@@ -29,7 +32,7 @@ Following charts are drawn:
## Configuration
Edit the `python.d/uwsgi.conf` configuration file using `edit-config` from the Netdata [config
-directory](/docs/configure/nodes.md), which is typically at `/etc/netdata`.
+directory](https://github.com/netdata/netdata/blob/master/docs/configure/nodes.md), which is typically at `/etc/netdata`.
```bash
cd /etc/netdata # Replace this path with your Netdata config directory, if different
diff --git a/collectors/python.d.plugin/varnish/README.md b/collectors/python.d.plugin/varnish/README.md
index 018905f0..ebcc00c5 100644
--- a/collectors/python.d.plugin/varnish/README.md
+++ b/collectors/python.d.plugin/varnish/README.md
@@ -1,7 +1,10 @@
<!--
title: "Varnish Cache monitoring with Netdata"
-custom_edit_url: https://github.com/netdata/netdata/edit/master/collectors/python.d.plugin/varnish/README.md
+custom_edit_url: "https://github.com/netdata/netdata/edit/master/collectors/python.d.plugin/varnish/README.md"
sidebar_label: "Varnish Cache"
+learn_status: "Published"
+learn_topic_type: "References"
+learn_rel_path: "References/Collectors references/Webapps"
-->
# Varnish Cache monitoring with Netdata
@@ -45,7 +48,7 @@ For every storage (SMF, SMA, or MSE):
## Configuration
Edit the `python.d/varnish.conf` configuration file using `edit-config` from the Netdata [config
-directory](/docs/configure/nodes.md), which is typically at `/etc/netdata`.
+directory](https://github.com/netdata/netdata/blob/master/docs/configure/nodes.md), which is typically at `/etc/netdata`.
```bash
cd /etc/netdata # Replace this path with your Netdata config directory, if different
diff --git a/collectors/python.d.plugin/w1sensor/README.md b/collectors/python.d.plugin/w1sensor/README.md
index b6d2b2d6..12a14a19 100644
--- a/collectors/python.d.plugin/w1sensor/README.md
+++ b/collectors/python.d.plugin/w1sensor/README.md
@@ -1,7 +1,10 @@
<!--
title: "1-Wire Sensors monitoring with Netdata"
-custom_edit_url: https://github.com/netdata/netdata/edit/master/collectors/python.d.plugin/w1sensor/README.md
+custom_edit_url: "https://github.com/netdata/netdata/edit/master/collectors/python.d.plugin/w1sensor/README.md"
sidebar_label: "1-Wire sensors"
+learn_status: "Published"
+learn_topic_type: "References"
+learn_rel_path: "References/Collectors references/Remotes/Devices"
-->
# 1-Wire Sensors monitoring with Netdata
@@ -16,7 +19,7 @@ Charts are created dynamically based on the number of detected sensors.
## Configuration
Edit the `python.d/w1sensor.conf` configuration file using `edit-config` from the Netdata [config
-directory](/docs/configure/nodes.md), which is typically at `/etc/netdata`.
+directory](https://github.com/netdata/netdata/blob/master/docs/configure/nodes.md), which is typically at `/etc/netdata`.
```bash
cd /etc/netdata # Replace this path with your Netdata config directory, if different
diff --git a/collectors/python.d.plugin/w1sensor/w1sensor.chart.py b/collectors/python.d.plugin/w1sensor/w1sensor.chart.py
index c4f847bf..66797ced 100644
--- a/collectors/python.d.plugin/w1sensor/w1sensor.chart.py
+++ b/collectors/python.d.plugin/w1sensor/w1sensor.chart.py
@@ -15,7 +15,7 @@ update_every = 5
W1_DIR = '/sys/bus/w1/devices/'
# Lines matching the following regular expression contain a temperature value
-RE_TEMP = re.compile(r' t=(\d+)')
+RE_TEMP = re.compile(r' t=(-?\d+)')
ORDER = [
'temp',
diff --git a/collectors/python.d.plugin/zscores/README.md b/collectors/python.d.plugin/zscores/README.md
index 4f84a6c1..d89aa6a0 100644
--- a/collectors/python.d.plugin/zscores/README.md
+++ b/collectors/python.d.plugin/zscores/README.md
@@ -1,14 +1,18 @@
<!--
title: "zscores"
description: "Use statistical anomaly detection to narrow your focus and shorten root cause analysis."
-custom_edit_url: https://github.com/netdata/netdata/edit/master/collectors/python.d.plugin/zscores/README.md
+custom_edit_url: "https://github.com/netdata/netdata/edit/master/collectors/python.d.plugin/zscores/README.md"
+sidebar_label: "zscores"
+learn_status: "Published"
+learn_topic_type: "References"
+learn_rel_path: "References/Collectors references/Uncategorized"
-->
# Z-Scores - basic anomaly detection for your key metrics and charts
Smoothed, rolling [Z-Scores](https://en.wikipedia.org/wiki/Standard_score) for selected metrics or charts.
-This collector uses the [Netdata rest api](https://learn.netdata.cloud/docs/agent/web/api) to get the `mean` and `stddev`
+This collector uses the [Netdata rest api](https://github.com/netdata/netdata/blob/master/web/api/README.md) to get the `mean` and `stddev`
for each dimension on specified charts over a time range (defined by `train_secs` and `offset_secs`). For each dimension
it will calculate a Z-Score as `z = (x - mean) / stddev` (clipped at `z_clip`). Scores are then smoothed over
time (`z_smooth_n`) and, if `mode: 'per_chart'`, aggregated across dimensions to a smoothed, rolling chart level Z-Score
diff --git a/collectors/slabinfo.plugin/README.md b/collectors/slabinfo.plugin/README.md
index 2f490466..320b1fc9 100644
--- a/collectors/slabinfo.plugin/README.md
+++ b/collectors/slabinfo.plugin/README.md
@@ -1,6 +1,10 @@
<!--
title: "slabinfo.plugin"
-custom_edit_url: https://github.com/netdata/netdata/edit/master/collectors/slabinfo.plugin/README.md
+custom_edit_url: "https://github.com/netdata/netdata/edit/master/collectors/slabinfo.plugin/README.md"
+sidebar_label: "slabinfo.plugin"
+learn_status: "Published"
+learn_topic_type: "References"
+learn_rel_path: "References/Collectors references/System metrics"
-->
# slabinfo.plugin
diff --git a/collectors/slabinfo.plugin/slabinfo.c b/collectors/slabinfo.plugin/slabinfo.c
index 2e47ee22..52b53cd2 100644
--- a/collectors/slabinfo.plugin/slabinfo.c
+++ b/collectors/slabinfo.plugin/slabinfo.c
@@ -142,14 +142,14 @@ struct slabinfo *read_file_slabinfo() {
if(unlikely(!ff)) {
ff = procfile_reopen(ff, PLUGIN_SLABINFO_PROCFILE, " ,:" , PROCFILE_FLAG_DEFAULT);
if(unlikely(!ff)) {
- error("<- Cannot open file '%s", PLUGIN_SLABINFO_PROCFILE);
+ collector_error("<- Cannot open file '%s", PLUGIN_SLABINFO_PROCFILE);
exit(1);
}
}
ff = procfile_readall(ff);
if(unlikely(!ff)) {
- error("<- Cannot read file '%s'", PLUGIN_SLABINFO_PROCFILE);
+ collector_error("<- Cannot read file '%s'", PLUGIN_SLABINFO_PROCFILE);
exit(0);
}
@@ -336,6 +336,7 @@ void usage(void) {
}
int main(int argc, char **argv) {
+ stderror = stderr;
clocks_init();
program_name = argv[0];
@@ -350,7 +351,7 @@ int main(int argc, char **argv) {
n = (int) str2l(argv[i]);
if (n > 0) {
if (n >= UPDATE_EVERY_MAX) {
- error("Invalid interval value: %s", argv[i]);
+ collector_error("Invalid interval value: %s", argv[i]);
exit(1);
}
freq = n;
@@ -383,7 +384,7 @@ int main(int argc, char **argv) {
if(freq >= update_every)
update_every = freq;
else if(freq)
- error("update frequency %d seconds is too small for slabinfo. Using %d.", freq, update_every);
+ collector_error("update frequency %d seconds is too small for slabinfo. Using %d.", freq, update_every);
// Call the main function. Time drift to be added
diff --git a/collectors/statsd.plugin/README.md b/collectors/statsd.plugin/README.md
index b46ca28d..d65476ff 100644
--- a/collectors/statsd.plugin/README.md
+++ b/collectors/statsd.plugin/README.md
@@ -1,7 +1,11 @@
<!--
title: "statsd.plugin"
description: "The Netdata Agent is a fully-featured StatsD server that collects metrics from any custom application and visualizes them in real-time."
-custom_edit_url: https://github.com/netdata/netdata/edit/master/collectors/statsd.plugin/README.md
+custom_edit_url: "https://github.com/netdata/netdata/edit/master/collectors/statsd.plugin/README.md"
+sidebar_label: "statsd.plugin"
+learn_status: "Published"
+learn_topic_type: "References"
+learn_rel_path: "References/Collectors references/Apm"
-->
StatsD is a system to collect data from any application. Applications send metrics to it, usually via non-blocking UDP communication, and StatsD servers collect these metrics, perform a few simple calculations on them and push them to backend time-series databases.
@@ -25,11 +29,11 @@ On synthetic charts, we can have alarms as with any metric and chart.
- [K6 load testing tool](https://k6.io)
- **Description:** k6 is a developer-centric, free and open-source load testing tool built for making performance testing a productive and enjoyable experience.
- - [Documentation](/collectors/statsd.plugin/k6.md)
+ - [Documentation](https://github.com/netdata/netdata/blob/master/collectors/statsd.plugin/k6.md)
- [Configuration](https://github.com/netdata/netdata/blob/master/collectors/statsd.plugin/k6.conf)
- [Asterisk](https://www.asterisk.org/)
- **Description:** Asterisk is an Open Source PBX and telephony toolkit.
- - [Documentation](/collectors/statsd.plugin/asterisk.md)
+ - [Documentation](https://github.com/netdata/netdata/blob/master/collectors/statsd.plugin/asterisk.md)
- [Configuration](https://github.com/netdata/netdata/blob/master/collectors/statsd.plugin/asterisk.conf)
## Metrics supported by Netdata
@@ -202,7 +206,7 @@ Netdata can visualize StatsD collected metrics in 2 ways:
### Private metric charts
-Private charts are controlled with `create private charts for metrics matching = *`. This setting accepts a space-separated list of [simple patterns](/libnetdata/simple_pattern/README.md). Netdata will create private charts for all metrics **by default**.
+Private charts are controlled with `create private charts for metrics matching = *`. This setting accepts a space-separated list of [simple patterns](https://github.com/netdata/netdata/blob/master/libnetdata/simple_pattern/README.md). Netdata will create private charts for all metrics **by default**.
For example, to render charts for all `myapp.*` metrics, except `myapp.*.badmetric`, use:
@@ -210,7 +214,7 @@ For example, to render charts for all `myapp.*` metrics, except `myapp.*.badmetr
create private charts for metrics matching = !myapp.*.badmetric myapp.*
```
-You can specify Netdata StatsD to have a different `memory mode` than the rest of the Netdata Agent. You can read more about `memory mode` in the [documentation](/database/README.md).
+You can specify Netdata StatsD to have a different `memory mode` than the rest of the Netdata Agent. You can read more about `memory mode` in the [documentation](https://github.com/netdata/netdata/blob/master/database/README.md).
The default behavior is to use the same settings as the rest of the Netdata Agent. If you wish to change them, edit the following settings:
- `private charts memory mode`
@@ -289,7 +293,7 @@ Synthetic charts are organized in
- **charts for each application** aka family in Netdata Dashboard.
- **StatsD metrics for each chart** /aka charts and context Netdata Dashboard.
-> You can read more about how the Netdata Agent organizes information in the relevant [documentation](/web/README.md)
+> You can read more about how the Netdata Agent organizes information in the relevant [documentation](https://github.com/netdata/netdata/blob/master/web/README.md)
For each application you need to create a `.conf` file in `/etc/netdata/statsd.d`.
@@ -326,7 +330,7 @@ Using the above configuration `myapp` should get its own section on the dashboar
`[app]` starts a new application definition. The supported settings in this section are:
- `name` defines the name of the app.
-- `metrics` is a Netdata [simple pattern](/libnetdata/simple_pattern/README.md). This pattern should match all the possible StatsD metrics that will be participating in the application `myapp`.
+- `metrics` is a Netdata [simple pattern](https://github.com/netdata/netdata/blob/master/libnetdata/simple_pattern/README.md). This pattern should match all the possible StatsD metrics that will be participating in the application `myapp`.
- `private charts = yes|no`, enables or disables private charts for the metrics matched.
- `gaps when not collected = yes|no`, enables or disables gaps on the charts of the application in case that no metrics are collected.
- `memory mode` sets the memory mode for all charts of the application. The default is the global default for Netdata (not the global default for StatsD private charts). We suggest not to use this (we have commented it out in the example) and let your app use the global default for Netdata, which is our dbengine.
@@ -352,7 +356,7 @@ So, the format is this:
dimension = [pattern] METRIC NAME TYPE MULTIPLIER DIVIDER OPTIONS
```
-`pattern` is a keyword. When set, `METRIC` is expected to be a Netdata [simple pattern](/libnetdata/simple_pattern/README.md) that will be used to match all the StatsD metrics to be added to the chart. So, `pattern` automatically matches any number of StatsD metrics, all of which will be added as separate chart dimensions.
+`pattern` is a keyword. When set, `METRIC` is expected to be a Netdata [simple pattern](https://github.com/netdata/netdata/blob/master/libnetdata/simple_pattern/README.md) that will be used to match all the StatsD metrics to be added to the chart. So, `pattern` automatically matches any number of StatsD metrics, all of which will be added as separate chart dimensions.
`TYPE`, `MULTIPLIER`, `DIVIDER` and `OPTIONS` are optional.
diff --git a/collectors/statsd.plugin/asterisk.md b/collectors/statsd.plugin/asterisk.md
index 94da94ee..9d794811 100644
--- a/collectors/statsd.plugin/asterisk.md
+++ b/collectors/statsd.plugin/asterisk.md
@@ -1,8 +1,10 @@
<!--
title: "Asterisk monitoring with Netdata"
-custom_edit_url: https://github.com/netdata/netdata/edit/master/collectors/statsd.plugin/asterisk.md
-
+custom_edit_url: "https://github.com/netdata/netdata/edit/master/collectors/statsd.plugin/asterisk.md"
sidebar_label: "Asterisk"
+learn_status: "Published"
+learn_topic_type: "References"
+learn_rel_path: "References/Collectors references/Apm/Statsd"
-->
# Asterisk monitoring with Netdata
diff --git a/collectors/statsd.plugin/k6.md b/collectors/statsd.plugin/k6.md
index 4f8c7013..7a1e3677 100644
--- a/collectors/statsd.plugin/k6.md
+++ b/collectors/statsd.plugin/k6.md
@@ -1,8 +1,10 @@
<!--
title: "K6 load test monitoring with Netdata"
-custom_edit_url: https://github.com/netdata/netdata/edit/master/collectors/statsd.plugin/k6.md
-
+custom_edit_url: "https://github.com/netdata/netdata/edit/master/collectors/statsd.plugin/k6.md"
sidebar_label: "K6 Load Testing"
+learn_status: "Published"
+learn_topic_type: "References"
+learn_rel_path: "References/Collectors references/Apm/Statsd"
-->
# K6 Load Testing monitoring with Netdata
diff --git a/collectors/statsd.plugin/statsd.c b/collectors/statsd.plugin/statsd.c
index 67d7ed2e..d15129b9 100644
--- a/collectors/statsd.plugin/statsd.c
+++ b/collectors/statsd.plugin/statsd.c
@@ -234,7 +234,8 @@ typedef struct statsd_app {
// global statsd data
struct collection_thread_status {
- int status;
+ SPINLOCK spinlock;
+ bool running;
size_t max_sockets;
netdata_thread_t thread;
@@ -433,7 +434,7 @@ static inline NETDATA_DOUBLE statsd_parse_float(const char *v, NETDATA_DOUBLE de
char *e = NULL;
value = str2ndd(v, &e);
if(unlikely(e && *e))
- error("STATSD: excess data '%s' after value '%s'", e, v);
+ collector_error("STATSD: excess data '%s' after value '%s'", e, v);
}
else
value = def;
@@ -455,7 +456,7 @@ static inline long long statsd_parse_int(const char *v, long long def) {
char *e = NULL;
value = str2ll(v, &e);
if(unlikely(e && *e))
- error("STATSD: excess data '%s' after value '%s'", e, v);
+ collector_error("STATSD: excess data '%s' after value '%s'", e, v);
}
else
value = def;
@@ -483,7 +484,7 @@ static inline void statsd_process_gauge(STATSD_METRIC *m, const char *value, con
if(!is_metric_useful_for_collection(m)) return;
if(unlikely(!value || !*value)) {
- error("STATSD: metric '%s' of type gauge, with empty value is ignored.", m->name);
+ collector_error("STATSD: metric '%s' of type gauge, with empty value is ignored.", m->name);
return;
}
@@ -531,7 +532,7 @@ static inline void statsd_process_histogram_or_timer(STATSD_METRIC *m, const cha
if(!is_metric_useful_for_collection(m)) return;
if(unlikely(!value || !*value)) {
- error("STATSD: metric of type %s, with empty value is ignored.", type);
+ collector_error("STATSD: metric of type %s, with empty value is ignored.", type);
return;
}
@@ -594,7 +595,7 @@ static inline void statsd_process_set(STATSD_METRIC *m, const char *value) {
}
if (unlikely(!m->set.dict)) {
- m->set.dict = dictionary_create(STATSD_DICTIONARY_OPTIONS);
+ m->set.dict = dictionary_create_advanced(STATSD_DICTIONARY_OPTIONS, &dictionary_stats_category_collectors, 0);
dictionary_register_insert_callback(m->set.dict, dictionary_metric_set_value_insert_callback, m);
m->set.unique = 0;
}
@@ -634,7 +635,7 @@ static inline void statsd_process_dictionary(STATSD_METRIC *m, const char *value
statsd_reset_metric(m);
if (unlikely(!m->dictionary.dict)) {
- m->dictionary.dict = dictionary_create(STATSD_DICTIONARY_OPTIONS);
+ m->dictionary.dict = dictionary_create_advanced(STATSD_DICTIONARY_OPTIONS, &dictionary_stats_category_collectors, 0);
dictionary_register_insert_callback(m->dictionary.dict, dictionary_metric_dict_value_insert_callback, m);
m->dictionary.unique = 0;
}
@@ -873,21 +874,19 @@ struct statsd_tcp {
char buffer[];
};
-#ifdef HAVE_RECVMMSG
struct statsd_udp {
- int *running;
+ struct collection_thread_status *status;
STATSD_SOCKET_DATA_TYPE type;
+
+#ifdef HAVE_RECVMMSG
size_t size;
struct iovec *iovecs;
struct mmsghdr *msgs;
-};
#else
-struct statsd_udp {
int *running;
- STATSD_SOCKET_DATA_TYPE type;
char buffer[STATSD_UDP_BUFFER_SIZE];
-};
#endif
+};
// new TCP client connected
static void *statsd_add_callback(POLLINFO *pi, short int *events, void *data) {
@@ -1097,9 +1096,11 @@ static int statsd_snd_callback(POLLINFO *pi, short int *events) {
void statsd_collector_thread_cleanup(void *data) {
struct statsd_udp *d = data;
- *d->running = 0;
+ netdata_spinlock_lock(&d->status->spinlock);
+ d->status->running = false;
+ netdata_spinlock_unlock(&d->status->spinlock);
- info("cleaning up...");
+ collector_info("cleaning up...");
#ifdef HAVE_RECVMMSG
size_t i;
@@ -1114,9 +1115,15 @@ void statsd_collector_thread_cleanup(void *data) {
worker_unregister();
}
+static bool statsd_should_stop(void) {
+ return !service_running(SERVICE_COLLECTORS);
+}
+
void *statsd_collector_thread(void *ptr) {
struct collection_thread_status *status = ptr;
- status->status = 1;
+ netdata_spinlock_lock(&status->spinlock);
+ status->running = true;
+ netdata_spinlock_unlock(&status->spinlock);
worker_register("STATSD");
worker_register_job_name(WORKER_JOB_TYPE_TCP_CONNECTED, "tcp connect");
@@ -1124,10 +1131,10 @@ void *statsd_collector_thread(void *ptr) {
worker_register_job_name(WORKER_JOB_TYPE_RCV_DATA, "receive");
worker_register_job_name(WORKER_JOB_TYPE_SND_DATA, "send");
- info("STATSD collector thread started with taskid %d", gettid());
+ collector_info("STATSD collector thread started with taskid %d", gettid());
struct statsd_udp *d = callocz(sizeof(struct statsd_udp), 1);
- d->running = &status->status;
+ d->status = status;
netdata_thread_cleanup_push(statsd_collector_thread_cleanup, d);
@@ -1152,6 +1159,7 @@ void *statsd_collector_thread(void *ptr) {
, statsd_rcv_callback
, statsd_snd_callback
, NULL
+ , statsd_should_stop
, NULL // No access control pattern
, 0 // No dns lookups for access control pattern
, (void *)d
@@ -1329,7 +1337,7 @@ static int statsd_readfile(const char *filename, STATSD_APP *app, STATSD_APP_CHA
else if(app) {
if(!strcmp(s, "dictionary")) {
if(!app->dict)
- app->dict = dictionary_create(DICT_OPTION_SINGLE_THREADED);
+ app->dict = dictionary_create_advanced(DICT_OPTION_SINGLE_THREADED, &dictionary_stats_category_collectors, 0);
dict = app->dict;
}
@@ -1934,7 +1942,7 @@ static inline void statsd_flush_dictionary(STATSD_METRIC *m) {
if(m->dictionary.unique >= statsd.dictionary_max_unique) {
if(!(m->options & STATSD_METRIC_OPTION_COLLECTION_FULL_LOGGED)) {
m->options |= STATSD_METRIC_OPTION_COLLECTION_FULL_LOGGED;
- info(
+ collector_info(
"STATSD dictionary '%s' reach max of %zu items - try increasing 'dictionaries max unique dimensions' in netdata.conf",
m->name,
m->dictionary.unique);
@@ -2307,7 +2315,7 @@ static inline void statsd_flush_index_metrics(STATSD_INDEX *index, void (*flush_
if(unlikely(!(m->options & STATSD_METRIC_OPTION_PRIVATE_CHART_CHECKED))) {
if(unlikely(statsd.private_charts >= statsd.max_private_charts_hard)) {
debug(D_STATSD, "STATSD: metric '%s' will not be charted, because the hard limit of the maximum number of charts has been reached.", m->name);
- info("STATSD: metric '%s' will not be charted, because the hard limit of the maximum number of charts (%zu) has been reached. Increase the number of charts by editing netdata.conf, [statsd] section.", m->name, statsd.max_private_charts_hard);
+ collector_info("STATSD: metric '%s' will not be charted, because the hard limit of the maximum number of charts (%zu) has been reached. Increase the number of charts by editing netdata.conf, [statsd] section.", m->name, statsd.max_private_charts_hard);
m->options &= ~STATSD_METRIC_OPTION_PRIVATE_CHART_ENABLED;
}
else {
@@ -2353,22 +2361,24 @@ static int statsd_listen_sockets_setup(void) {
static void statsd_main_cleanup(void *data) {
struct netdata_static_thread *static_thread = (struct netdata_static_thread *)data;
static_thread->enabled = NETDATA_MAIN_THREAD_EXITING;
- info("cleaning up...");
+ collector_info("cleaning up...");
if (statsd.collection_threads_status) {
int i;
for (i = 0; i < statsd.threads; i++) {
- if(statsd.collection_threads_status[i].status) {
- info("STATSD: stopping data collection thread %d...", i + 1);
+ netdata_spinlock_lock(&statsd.collection_threads_status[i].spinlock);
+ if(statsd.collection_threads_status[i].running) {
+ collector_info("STATSD: stopping data collection thread %d...", i + 1);
netdata_thread_cancel(statsd.collection_threads_status[i].thread);
}
else {
- info("STATSD: data collection thread %d found stopped.", i + 1);
+ collector_info("STATSD: data collection thread %d found stopped.", i + 1);
}
+ netdata_spinlock_unlock(&statsd.collection_threads_status[i].spinlock);
}
}
- info("STATSD: closing sockets...");
+ collector_info("STATSD: closing sockets...");
listen_sockets_close(&statsd.sockets);
// destroy the dictionaries
@@ -2380,7 +2390,7 @@ static void statsd_main_cleanup(void *data) {
dictionary_destroy(statsd.sets.dict);
dictionary_destroy(statsd.timers.dict);
- info("STATSD: cleanup completed.");
+ collector_info("STATSD: cleanup completed.");
static_thread->enabled = NETDATA_MAIN_THREAD_EXITED;
worker_unregister();
@@ -2412,13 +2422,13 @@ void *statsd_main(void *ptr) {
netdata_thread_cleanup_push(statsd_main_cleanup, ptr);
- statsd.gauges.dict = dictionary_create(STATSD_DICTIONARY_OPTIONS);
- statsd.meters.dict = dictionary_create(STATSD_DICTIONARY_OPTIONS);
- statsd.counters.dict = dictionary_create(STATSD_DICTIONARY_OPTIONS);
- statsd.histograms.dict = dictionary_create(STATSD_DICTIONARY_OPTIONS);
- statsd.dictionaries.dict = dictionary_create(STATSD_DICTIONARY_OPTIONS);
- statsd.sets.dict = dictionary_create(STATSD_DICTIONARY_OPTIONS);
- statsd.timers.dict = dictionary_create(STATSD_DICTIONARY_OPTIONS);
+ statsd.gauges.dict = dictionary_create_advanced(STATSD_DICTIONARY_OPTIONS, &dictionary_stats_category_collectors, 0);
+ statsd.meters.dict = dictionary_create_advanced(STATSD_DICTIONARY_OPTIONS, &dictionary_stats_category_collectors, 0);
+ statsd.counters.dict = dictionary_create_advanced(STATSD_DICTIONARY_OPTIONS, &dictionary_stats_category_collectors, 0);
+ statsd.histograms.dict = dictionary_create_advanced(STATSD_DICTIONARY_OPTIONS, &dictionary_stats_category_collectors, 0);
+ statsd.dictionaries.dict = dictionary_create_advanced(STATSD_DICTIONARY_OPTIONS, &dictionary_stats_category_collectors, 0);
+ statsd.sets.dict = dictionary_create_advanced(STATSD_DICTIONARY_OPTIONS, &dictionary_stats_category_collectors, 0);
+ statsd.timers.dict = dictionary_create_advanced(STATSD_DICTIONARY_OPTIONS, &dictionary_stats_category_collectors, 0);
dictionary_register_insert_callback(statsd.gauges.dict, dictionary_metric_insert_callback, &statsd.gauges);
dictionary_register_insert_callback(statsd.meters.dict, dictionary_metric_insert_callback, &statsd.meters);
@@ -2444,7 +2454,7 @@ void *statsd_main(void *ptr) {
statsd.update_every = default_rrd_update_every;
statsd.update_every = (int)config_get_number(CONFIG_SECTION_STATSD, "update every (flushInterval)", statsd.update_every);
if(statsd.update_every < default_rrd_update_every) {
- error("STATSD: minimum flush interval %d given, but the minimum is the update every of netdata. Using %d", statsd.update_every, default_rrd_update_every);
+ collector_error("STATSD: minimum flush interval %d given, but the minimum is the update every of netdata. Using %d", statsd.update_every, default_rrd_update_every);
statsd.update_every = default_rrd_update_every;
}
@@ -2461,7 +2471,7 @@ void *statsd_main(void *ptr) {
statsd.histogram_percentile = (double)config_get_float(CONFIG_SECTION_STATSD, "histograms and timers percentile (percentThreshold)", statsd.histogram_percentile);
if(isless(statsd.histogram_percentile, 0) || isgreater(statsd.histogram_percentile, 100)) {
- error("STATSD: invalid histograms and timers percentile %0.5f given", statsd.histogram_percentile);
+ collector_error("STATSD: invalid histograms and timers percentile %0.5f given", statsd.histogram_percentile);
statsd.histogram_percentile = 95.0;
}
{
@@ -2508,7 +2518,7 @@ void *statsd_main(void *ptr) {
#ifdef STATSD_MULTITHREADED
statsd.threads = (int)config_get_number(CONFIG_SECTION_STATSD, "threads", processors);
if(statsd.threads < 1) {
- error("STATSD: Invalid number of threads %d, using %d", statsd.threads, processors);
+ collector_error("STATSD: Invalid number of threads %d, using %d", statsd.threads, processors);
statsd.threads = processors;
config_set_number(CONFIG_SECTION_STATSD, "collector threads", statsd.threads);
}
@@ -2526,7 +2536,7 @@ void *statsd_main(void *ptr) {
statsd_listen_sockets_setup();
if(!statsd.sockets.opened) {
- error("STATSD: No statsd sockets to listen to. statsd will be disabled.");
+ collector_error("STATSD: No statsd sockets to listen to. statsd will be disabled.");
goto cleanup;
}
@@ -2536,7 +2546,8 @@ void *statsd_main(void *ptr) {
for(i = 0; i < statsd.threads ;i++) {
statsd.collection_threads_status[i].max_sockets = max_sockets / statsd.threads;
char tag[NETDATA_THREAD_TAG_MAX + 1];
- snprintfz(tag, NETDATA_THREAD_TAG_MAX, "STATSD_COLLECTOR[%d]", i + 1);
+ snprintfz(tag, NETDATA_THREAD_TAG_MAX, "STATSD_IN[%d]", i + 1);
+ netdata_spinlock_init(&statsd.collection_threads_status[i].spinlock);
netdata_thread_create(&statsd.collection_threads_status[i].thread, tag, NETDATA_THREAD_OPTION_DEFAULT, statsd_collector_thread, &statsd.collection_threads_status[i]);
}
@@ -2753,7 +2764,7 @@ void *statsd_main(void *ptr) {
usec_t step = statsd.update_every * USEC_PER_SEC;
heartbeat_t hb;
heartbeat_init(&hb);
- while(!netdata_exit) {
+ while(service_running(SERVICE_COLLECTORS)) {
worker_is_idle();
heartbeat_next(&hb, step);
@@ -2781,7 +2792,7 @@ void *statsd_main(void *ptr) {
worker_is_busy(WORKER_STATSD_FLUSH_STATS);
statsd_update_all_app_charts();
- if(unlikely(netdata_exit))
+ if(unlikely(!service_running(SERVICE_COLLECTORS)))
break;
if(global_statistics_enabled) {
diff --git a/collectors/tc.plugin/README.md b/collectors/tc.plugin/README.md
index 32c20f49..bf8655a4 100644
--- a/collectors/tc.plugin/README.md
+++ b/collectors/tc.plugin/README.md
@@ -1,6 +1,10 @@
<!--
title: "tc.plugin"
-custom_edit_url: https://github.com/netdata/netdata/edit/master/collectors/tc.plugin/README.md
+custom_edit_url: "https://github.com/netdata/netdata/edit/master/collectors/tc.plugin/README.md"
+sidebar_label: "tc.plugin"
+learn_status: "Published"
+learn_topic_type: "References"
+learn_rel_path: "References/Collectors references/Networking"
-->
# tc.plugin
@@ -67,7 +71,7 @@ QoS is about 2 features:
When your system is under a DDoS attack, it will get a lot more bandwidth compared to the one it can handle and probably your applications will crash. Setting a limit on the inbound traffic using QoS, will protect your servers (throttle the requests) and depending on the size of the attack may allow your legitimate users to access the server, while the attack is taking place.
- Using QoS together with a [SYNPROXY](/collectors/proc.plugin/README.md) will provide a great degree of protection against most DDoS attacks. Actually when I wrote that article, a few folks tried to DDoS the Netdata demo site to see in real-time the SYNPROXY operation. They did not do it right, but anyway a great deal of requests reached the Netdata server. What saved Netdata was QoS. The Netdata demo server has QoS installed, so the requests were throttled and the server did not even reach the point of resource starvation. Read about it [here](/collectors/proc.plugin/README.md).
+ Using QoS together with a [SYNPROXY](https://github.com/netdata/netdata/blob/master/collectors/proc.plugin/README.md) will provide a great degree of protection against most DDoS attacks. Actually when I wrote that article, a few folks tried to DDoS the Netdata demo site to see in real-time the SYNPROXY operation. They did not do it right, but anyway a great deal of requests reached the Netdata server. What saved Netdata was QoS. The Netdata demo server has QoS installed, so the requests were throttled and the server did not even reach the point of resource starvation. Read about it [here](https://github.com/netdata/netdata/blob/master/collectors/proc.plugin/README.md).
On top of all these, QoS is extremely light. You will configure it once, and this is it. It will not bother you again and it will not use any noticeable CPU resources, especially on application and database servers.
diff --git a/collectors/tc.plugin/plugin_tc.c b/collectors/tc.plugin/plugin_tc.c
index a2e72ee3..a8ceca44 100644
--- a/collectors/tc.plugin/plugin_tc.c
+++ b/collectors/tc.plugin/plugin_tc.c
@@ -89,7 +89,7 @@ static bool tc_class_conflict_callback(const DICTIONARY_ITEM *item __maybe_unuse
struct tc_class *c = old_value; (void)c;
struct tc_class *new_c = new_value; (void)new_c;
- error("TC: class '%s' is already in device '%s'. Ignoring duplicate.", dictionary_acquired_item_name(item), string2str(d->id));
+ collector_error("TC: class '%s' is already in device '%s'. Ignoring duplicate.", dictionary_acquired_item_name(item), string2str(d->id));
tc_class_free_callback(item, new_value, data);
@@ -98,7 +98,7 @@ static bool tc_class_conflict_callback(const DICTIONARY_ITEM *item __maybe_unuse
static void tc_class_index_init(struct tc_device *d) {
if(!d->classes) {
- d->classes = dictionary_create(DICT_OPTION_DONT_OVERWRITE_VALUE | DICT_OPTION_SINGLE_THREADED);
+ d->classes = dictionary_create_advanced(DICT_OPTION_DONT_OVERWRITE_VALUE | DICT_OPTION_SINGLE_THREADED, &dictionary_stats_category_collectors, 0);
dictionary_register_delete_callback(d->classes, tc_class_free_callback, d);
dictionary_register_conflict_callback(d->classes, tc_class_conflict_callback, d);
@@ -144,8 +144,9 @@ static void tc_device_free_callback(const DICTIONARY_ITEM *item __maybe_unused,
static void tc_device_index_init() {
if(!tc_device_root_index) {
- tc_device_root_index = dictionary_create(
- DICT_OPTION_DONT_OVERWRITE_VALUE | DICT_OPTION_SINGLE_THREADED | DICT_OPTION_ADD_IN_FRONT);
+ tc_device_root_index = dictionary_create_advanced(
+ DICT_OPTION_DONT_OVERWRITE_VALUE | DICT_OPTION_SINGLE_THREADED | DICT_OPTION_ADD_IN_FRONT,
+ &dictionary_stats_category_collectors, 0);
dictionary_register_insert_callback(tc_device_root_index, tc_device_add_callback, NULL);
dictionary_register_delete_callback(tc_device_root_index, tc_device_free_callback, NULL);
@@ -276,7 +277,7 @@ static inline void tc_device_commit(struct tc_device *d) {
}
if(unlikely(updated_classes && updated_qdiscs)) {
- error("TC: device '%s' has active both classes (%d) and qdiscs (%d). Will render only qdiscs.", string2str(d->id), updated_classes, updated_qdiscs);
+ collector_error("TC: device '%s' has active both classes (%d) and qdiscs (%d). Will render only qdiscs.", string2str(d->id), updated_classes, updated_qdiscs);
// set all classes to !updated
dfe_start_read(d->classes, c) {
@@ -352,7 +353,7 @@ static inline void tc_device_commit(struct tc_device *d) {
}
//if(unlikely(!c->hasparent)) {
- // if(root) error("TC: multiple root class/qdisc for device '%s' (old: '%s', new: '%s')", d->id, root->id, c->id);
+ // if(root) collector_error("TC: multiple root class/qdisc for device '%s' (old: '%s', new: '%s')", d->id, root->id, c->id);
// root = c;
// debug(D_TC_LOOP, "TC: found root class/qdisc '%s'", root->id);
//}
@@ -855,14 +856,14 @@ static void tc_main_cleanup(void *ptr) {
struct netdata_static_thread *static_thread = (struct netdata_static_thread *)ptr;
static_thread->enabled = NETDATA_MAIN_THREAD_EXITING;
- info("cleaning up...");
+ collector_info("cleaning up...");
if(tc_child_pid) {
- info("TC: killing with SIGTERM tc-qos-helper process %d", tc_child_pid);
+ collector_info("TC: killing with SIGTERM tc-qos-helper process %d", tc_child_pid);
if(killpid(tc_child_pid) != -1) {
siginfo_t info;
- info("TC: waiting for tc plugin child process pid %d to exit...", tc_child_pid);
+ collector_info("TC: waiting for tc plugin child process pid %d to exit...", tc_child_pid);
waitid(P_PID, (id_t) tc_child_pid, &info, WEXITED);
}
@@ -929,7 +930,7 @@ void *tc_main(void *ptr) {
snprintfz(command, TC_LINE_MAX, "%s/tc-qos-helper.sh", netdata_configured_primary_plugins_dir);
char *tc_script = config_get("plugin:tc", "script to run to get tc values", command);
- while(!netdata_exit) {
+ while(service_running(SERVICE_COLLECTORS)) {
FILE *fp_child_input, *fp_child_output;
struct tc_device *device = NULL;
struct tc_class *class = NULL;
@@ -939,13 +940,13 @@ void *tc_main(void *ptr) {
fp_child_output = netdata_popen(command, (pid_t *)&tc_child_pid, &fp_child_input);
if(unlikely(!fp_child_output)) {
- error("TC: Cannot popen(\"%s\", \"r\").", command);
+ collector_error("TC: Cannot popen(\"%s\", \"r\").", command);
goto cleanup;
}
char buffer[TC_LINE_MAX+1] = "";
while(fgets(buffer, TC_LINE_MAX, fp_child_output) != NULL) {
- if(unlikely(netdata_exit)) break;
+ if(unlikely(!service_running(SERVICE_COLLECTORS))) break;
buffer[TC_LINE_MAX] = '\0';
// debug(D_TC_LOOP, "TC: read '%s'", buffer);
@@ -1162,13 +1163,13 @@ void *tc_main(void *ptr) {
class = NULL;
}
- if(unlikely(netdata_exit))
+ if(unlikely(!service_running(SERVICE_COLLECTORS)))
goto cleanup;
if(code == 1 || code == 127) {
// 1 = DISABLE
// 127 = cannot even run it
- error("TC: tc-qos-helper.sh exited with code %d. Disabling it.", code);
+ collector_error("TC: tc-qos-helper.sh exited with code %d. Disabling it.", code);
goto cleanup;
}
diff --git a/collectors/timex.plugin/README.md b/collectors/timex.plugin/README.md
index 18665f80..ba202075 100644
--- a/collectors/timex.plugin/README.md
+++ b/collectors/timex.plugin/README.md
@@ -1,7 +1,11 @@
<!--
title: "timex.plugin"
description: "Monitor the system clock synchronization state."
-custom_edit_url: https://github.com/netdata/netdata/edit/master/collectors/timex.plugin/README.md
+custom_edit_url: "https://github.com/netdata/netdata/edit/master/collectors/timex.plugin/README.md"
+sidebar_label: "timex.plugin"
+learn_status: "Published"
+learn_topic_type: "References"
+learn_rel_path: "References/Collectors references/System metrics"
-->
# timex.plugin
@@ -19,7 +23,7 @@ An unsynchronized clock may indicate a hardware clock error, or an issue with UT
## Configuration
-Edit the `netdata.conf` configuration file using [`edit-config`](/docs/configure/nodes.md#use-edit-config-to-edit-configuration-files) from the [Netdata config directory](/docs/configure/nodes.md#the-netdata-config-directory), which is typically at `/etc/netdata`.
+Edit the `netdata.conf` configuration file using [`edit-config`](https://github.com/netdata/netdata/blob/master/docs/configure/nodes.md#use-edit-config-to-edit-configuration-files) from the [Netdata config directory](https://github.com/netdata/netdata/blob/master/docs/configure/nodes.md#the-netdata-config-directory), which is typically at `/etc/netdata`.
Scroll down to the `[plugin:timex]` section to find the available options:
diff --git a/collectors/timex.plugin/plugin_timex.c b/collectors/timex.plugin/plugin_timex.c
index 46cfc579..84147c85 100644
--- a/collectors/timex.plugin/plugin_timex.c
+++ b/collectors/timex.plugin/plugin_timex.c
@@ -64,7 +64,7 @@ void *timex_main(void *ptr)
usec_t step = update_every * USEC_PER_SEC;
heartbeat_t hb;
heartbeat_init(&hb);
- while (!netdata_exit) {
+ while (service_running(SERVICE_COLLECTORS)) {
worker_is_idle();
heartbeat_next(&hb, step);
worker_is_busy(0);
diff --git a/collectors/xenstat.plugin/README.md b/collectors/xenstat.plugin/README.md
index 8cbe086f..11c2bfdb 100644
--- a/collectors/xenstat.plugin/README.md
+++ b/collectors/xenstat.plugin/README.md
@@ -1,6 +1,10 @@
<!--
title: "xenstat.plugin"
-custom_edit_url: https://github.com/netdata/netdata/edit/master/collectors/xenstat.plugin/README.md
+custom_edit_url: "https://github.com/netdata/netdata/edit/master/collectors/xenstat.plugin/README.md"
+sidebar_label: "xenstat.plugin"
+learn_status: "Published"
+learn_topic_type: "References"
+learn_rel_path: "References/Collectors references/Virtualized environments/Virtualize hosts"
-->
# xenstat.plugin
diff --git a/collectors/xenstat.plugin/xenstat_plugin.c b/collectors/xenstat.plugin/xenstat_plugin.c
index ea98b9bb..b0cfa0b2 100644
--- a/collectors/xenstat.plugin/xenstat_plugin.c
+++ b/collectors/xenstat.plugin/xenstat_plugin.c
@@ -920,6 +920,7 @@ static void xenstat_send_domain_metrics() {
}
int main(int argc, char **argv) {
+ stderror = stderr;
clocks_init();
// ------------------------------------------------------------------------
diff --git a/configure.ac b/configure.ac
index 7ab7db41..8c001322 100644
--- a/configure.ac
+++ b/configure.ac
@@ -208,10 +208,10 @@ AC_ARG_ENABLE(
[enable_ml="detect"]
)
AC_ARG_ENABLE(
- [ml_tests],
- [AS_HELP_STRING([--enable-ml-tests], [Enable anomaly detection tests @<:@no@:>@])],
- [enable_ml_tests="yes"],
- [enable_ml_tests="no"]
+ [aclk_ssl_debug],
+ [AS_HELP_STRING([--enable-aclk-ssl-debug], [Enables possibility for SSL key logging @<:@default no@:>@])],
+ [aclk_ssl_debug="yes"],
+ [aclk_ssl_debug="no"]
)
# -----------------------------------------------------------------------------
@@ -251,6 +251,7 @@ AC_SEARCH_LIBS([clock_gettime], [rt posix4])
AC_CHECK_FUNCS([clock_gettime])
AC_CHECK_FUNCS([sched_setscheduler sched_getscheduler sched_getparam sched_get_priority_min sched_get_priority_max getpriority setpriority nice])
AC_CHECK_FUNCS([recvmmsg])
+AC_CHECK_FUNCS([close_range])
AC_TYPE_INT8_T
AC_TYPE_INT16_T
@@ -713,6 +714,11 @@ if test "${with_bundled_protobuf}" != "no"; then
fi
fi
+AM_CONDITIONAL([MQTT_WSS_DEBUG], [test "${aclk_ssl_debug}" = "yes"])
+if test "${aclk_ssl_debug}" = "yes"; then
+ AC_DEFINE([MQTT_WSS_DEBUG], [1], [ACLK SSL allow debugging])
+fi
+
if test "${with_bundled_protobuf}" != "yes"; then
PKG_CHECK_MODULES(
[PROTOBUF],
@@ -807,7 +813,7 @@ if test "$enable_cloud" != "no"; then
if test "$can_enable_ng" = "yes"; then
enable_aclk="yes"
AC_DEFINE([ENABLE_ACLK], [1], [netdata ACLK])
- OPTIONAL_ACLK_CFLAGS="-I \$(abs_top_srcdir)/mqtt_websockets/src/include -I \$(abs_top_srcdir)/mqtt_websockets/c-rbuf/include -I \$(abs_top_srcdir)/mqtt_websockets/MQTT-C/include -I \$(abs_top_srcdir)/aclk/aclk-schemas"
+ OPTIONAL_ACLK_CFLAGS="-I \$(abs_top_srcdir)/mqtt_websockets/src/include -I \$(abs_top_srcdir)/mqtt_websockets/c-rbuf/include -I \$(abs_top_srcdir)/aclk/aclk-schemas"
OPTIONAL_PROTOBUF_CFLAGS="${PROTOBUF_CFLAGS}"
CXX11FLAG="-std=c++11"
OPTIONAL_PROTOBUF_LIBS="${PROTOBUF_LIBS}"
@@ -1169,19 +1175,6 @@ if test "${build_ml}" = "yes"; then
OPTIONAL_ML_LIBS=""
fi
-# Decide if we should build ML tests.
-if test "${build_ml}" = "yes" -a "${enable_ml_tests}" = "yes" -a "${have_gtest}" = "yes"; then
- build_ml_tests="yes"
-else
- build_ml_tests="no"
-fi
-
-AM_CONDITIONAL([ENABLE_ML_TESTS], [test "${build_ml_tests}" = "yes"])
-if test "${build_ml_tests}" = "yes"; then
- AC_DEFINE([ENABLE_ML_TESTS], [1], [anomaly detection tests])
- OPTIONAL_ML_TESTS_CFLAGS="${OPTIONAL_GTEST_CFLAGS}"
- OPTIONAL_ML_TESTS_LIBS="${OPTIONAL_GTEST_LIBS}"
-fi
# -----------------------------------------------------------------------------
# ebpf.plugin
@@ -1601,7 +1594,7 @@ CFLAGS="${originalCFLAGS} ${OPTIONAL_LTO_CFLAGS} ${OPTIONAL_PROTOBUF_CFLAGS} ${O
${OPTIONAL_LIBCAP_CFLAGS} ${OPTIONAL_IPMIMONITORING_CFLAGS} ${OPTIONAL_CUPS_CFLAGS} ${OPTIONAL_XENSTAT_FLAGS} \
${OPTIONAL_KINESIS_CFLAGS} ${OPTIONAL_PUBSUB_CFLAGS} ${OPTIONAL_PROMETHEUS_REMOTE_WRITE_CFLAGS} \
${OPTIONAL_MONGOC_CFLAGS} ${LWS_CFLAGS} ${OPTIONAL_JSONC_STATIC_CFLAGS} ${OPTIONAL_BPF_CFLAGS} ${JUDY_CFLAGS} \
- ${OPTIONAL_ACLK_CFLAGS} ${OPTIONAL_ML_CFLAGS} ${OPTIONAL_ML_TESTS_CFLAGS} ${OPTIONAL_OS_DEP_CFLAGS}"
+ ${OPTIONAL_ACLK_CFLAGS} ${OPTIONAL_ML_CFLAGS} ${OPTIONAL_OS_DEP_CFLAGS}"
CXXFLAGS="${CFLAGS} ${CXX11FLAG}"
@@ -1655,8 +1648,6 @@ AC_SUBST([OPTIONAL_GTEST_CFLAGS])
AC_SUBST([OPTIONAL_GTEST_LIBS])
AC_SUBST([OPTIONAL_ML_CFLAGS])
AC_SUBST([OPTIONAL_ML_LIBS])
-AC_SUBST([OPTIONAL_ML_TESTS_CFLAGS])
-AC_SUBST([OPTIONAL_ML_TESTS_LIBS])
# -----------------------------------------------------------------------------
# Check if cmocka is available - needed for unit testing
@@ -1702,7 +1693,6 @@ AC_CONFIG_FILES([
collectors/charts.d.plugin/Makefile
collectors/diskspace.plugin/Makefile
collectors/timex.plugin/Makefile
- collectors/fping.plugin/Makefile
collectors/ioping.plugin/Makefile
collectors/freebsd.plugin/Makefile
collectors/freeipmi.plugin/Makefile
@@ -1740,7 +1730,7 @@ AC_CONFIG_FILES([
libnetdata/Makefile
libnetdata/tests/Makefile
libnetdata/adaptive_resortable_list/Makefile
- libnetdata/arrayalloc/Makefile
+ libnetdata/aral/Makefile
libnetdata/avl/Makefile
libnetdata/buffer/Makefile
libnetdata/clocks/Makefile
@@ -1749,6 +1739,7 @@ AC_CONFIG_FILES([
libnetdata/dictionary/Makefile
libnetdata/ebpf/Makefile
libnetdata/eval/Makefile
+ libnetdata/july/Makefile
libnetdata/locks/Makefile
libnetdata/log/Makefile
libnetdata/onewayalloc/Makefile
diff --git a/contrib/debian/netdata.postinst b/contrib/debian/netdata.postinst
index daea8cb4..5cce2c3d 100644
--- a/contrib/debian/netdata.postinst
+++ b/contrib/debian/netdata.postinst
@@ -16,7 +16,7 @@ dpkg-maintscript-helper dir_to_symlink \
/var/lib/netdata/www/static /usr/share/netdata/www/static 1.18.1~ netdata -- "$@"
case "$1" in
- configure|recnfigure)
+ configure|reconfigure)
if ! getent group netdata > /dev/null; then
addgroup --quiet --system netdata
fi
@@ -30,6 +30,11 @@ case "$1" in
usermod -a -G $item netdata
fi
done
+ # Netdata must be able to read /etc/pve/qemu-server/* and /etc/pve/lxc/*
+ # for reading VMs/containers names, CPU and memory limits on Proxmox.
+ if [ -d "/etc/pve" ] && getent group "www-data" > /dev/null 2>&1; then
+ usermod -a -G www-data netdata
+ fi
if ! dpkg-statoverride --list /var/lib/netdata > /dev/null 2>&1; then
dpkg-statoverride --update --add netdata netdata 0755 /var/lib/netdata
diff --git a/contrib/debian/rules b/contrib/debian/rules
index 5be3e81c..a4820cd4 100755
--- a/contrib/debian/rules
+++ b/contrib/debian/rules
@@ -95,7 +95,6 @@ override_dh_installdocs:
find . \
-name README.md \
- -not -path './.travis/*' \
-not -path './debian/*' \
-not -path './contrib/*' \
-exec cp \
diff --git a/contribution-guidelines.md b/contribution-guidelines.md
new file mode 100644
index 00000000..f6592980
--- /dev/null
+++ b/contribution-guidelines.md
@@ -0,0 +1,817 @@
+# Docs Development Guidelines
+
+Welcome to our docs developer guidelines!
+
+We store documentation related to Netdata inside of
+the [`netdata/netdata` repository](https://github.com/netdata/netdata) on GitHub.
+
+The Netdata team aggregates and publishes all documentation at [learn.netdata.cloud](/) using
+[Docusaurus](https://v2.docusaurus.io/) over at the [`netdata/learn` repository](https://github.com/netdata/learn).
+
+## Before you get started
+
+Anyone interested in contributing to documentation should first read the [Netdata style guide](#styling-guide) further
+down below and the [Netdata Community Code of Conduct](https://github.com/netdata/.github/blob/main/CODE_OF_CONDUCT.md).
+
+Netdata's documentation uses Markdown syntax. If you're not familiar with Markdown, read
+the [Mastering Markdown](https://guides.github.com/features/mastering-markdown/) guide from GitHub for the basics on
+creating paragraphs, styled text, lists, tables, and more, and read further down about some special
+occasions [while writing in MDX](#mdx-and-markdown).
+
+### Netdata's Documentation structure
+
+Netdata's documentation is separated into 5 categories.
+
+- **Getting Started**: This section’s purpose is to present “What is Netdata” and for whom is it for while also
+ presenting all the ways Netdata can be deployed. That includes Netdata’s platform support, Standalone deployment,
+ Parent-child deployments, deploying on Kubernetes and also deploying on IoT nodes.
+ - Stored in **WIP**
+ - Published in **WIP**
+- **Concepts**: This section’s purpose is to take a pitch on all the aspects of Netdata. We present the functionality of
+ each component/idea and support it with examples but we don’t go deep into technical details.
+ - Stored in the `/docs/concepts` directory in the `netdata/netdata` repository.
+ - Published in **WIP**
+- **Tasks**: This section's purpose is to break down any operation into a series of fundamental tasks for the Netdata
+ solution.
+ - Stored in the `/docs/tasks` directory in the `netdata/netdata` repository.
+ - Published in **WIP**
+- **References**: This section’s purpose is to explain thoroughly every part of Netdata. That covers settings,
+ configurations and so on.
+ - Stored near the component they refer to.
+ - Published in **WIP**
+- **Collectors References**: This section’s purpose is to explain thoroughly every collector that Netdata supports and
+ it's configuration options.
+ - Stored in stored near the collector they refer to.
+ - Published in **WIP**
+
+## How to contribute
+
+The easiest way to contribute to Netdata's documentation is to edit a file directly on GitHub. This is perfect for small
+fixes to a single document, such as fixing a typo or clarifying a confusing sentence.
+
+Click on the **Edit this page** button on any published document on [Netdata Learn](https://learn.netdata.cloud). Each
+page has two of these buttons: One beneath the table of contents, and another at the end of the document, which take you
+to GitHub's code editor. Make your suggested changes, keeping the [Netdata style guide](#styling-guide)
+in mind, and use the ***Preview changes*** button to ensure your Markdown syntax works as expected.
+
+Under the **Commit changes** header, write descriptive title for your requested change. Click the **Commit changes**
+button to initiate your pull request (PR).
+
+Jump down to our instructions on [PRs](#making-a-pull-request) for your next steps.
+
+### Edit locally
+
+Editing documentation locally is the preferred method for complex changes that span multiple documents or change the
+documentation's style or structure.
+
+Create a fork of the Netdata Agent repository by visit the [Netdata repository](https://github.com/netdata/netdata) and
+clicking on the **Fork** button.
+
+GitHub will ask you where you want to clone the repository. When finished, you end up at the index of your forked
+Netdata Agent repository. Clone your fork to your local machine:
+
+```bash
+git clone https://github.com/YOUR-GITHUB-USERNAME/netdata.git
+```
+
+Create a new branch using `git checkout -b BRANCH-NAME`. Use your favorite text editor to make your changes, keeping
+the [Netdata style guide](https://github.com/netdata/netdata/blob/master/docs/contributing/style-guide.md) in mind. Add, commit, and push changes to your fork. When you're
+finished, visit the [Netdata Agent Pull requests](https://github.com/netdata/netdata/pulls) to create a new pull request
+based on the changes you made in the new branch of your fork.
+
+### Making a pull request
+
+Pull requests (PRs) should be concise and informative. See our [PR guidelines](/contribute/handbook#pr-guidelines) for
+specifics.
+
+- The title must follow the [imperative mood](https://en.wikipedia.org/wiki/Imperative_mood) and be no more than ~50
+ characters.
+- The description should explain what was changed and why. Verify that you tested any code or processes that you are
+ trying to change.
+
+The Netdata team will review your PR and assesses it for correctness, conciseness, and overall quality. We may point to
+specific sections and ask for additional information or other fixes.
+
+After merging your PR, the Netdata team rebuilds the [documentation site](https://learn.netdata.cloud) to publish the
+changed documentation.
+
+## Writing Docs
+
+We have three main types of Docs: **References**, **Concepts** and **Tasks**.
+
+### Metadata Tags
+
+All of the Docs however have what we call "metadata" tags. these help to organize the document upon publishing.
+
+So let's go through the different necessary metadata tags to get a document properly published on Learn:
+
+- Docusaurus Specific:\
+ These metadata tags are parsed automatically by Docusaurus and are rendered in the published document. **Note**:
+ Netdata only uses the Docusaurus metadata tags releveant for our documentation infrastructure.
+ - `title: "The title of the document"` : Here we specify the title of our document, which is going to be converted
+ to the heading of the published page.
+ - `description: "The description of the file"`: Here we give a description of what this file is about.
+ - `custom_edit_url: https://github.com/netdata/netdata/edit/master/collectors/COLLECTORS.md`: Here is an example of
+ the link that the user will be redirected to if he clicks the "Edit this page button", as you see it leads
+ directly to the edit page of the source file.
+- Netdata Learn specific:
+ - `learn_status: "..."`
+ - The options for this tag are:
+ - `"published"`
+ - `"unpublished"`
+ - `learn_topic_type: "..."`
+ - The options for this tag are:
+ - `"Getting Started"`
+ - `"Concepts"`
+ - `"Tasks"`
+ - `"References"`
+ - `"Collectors References"`
+ - This is the Topic that the file belongs to, and this is going to resemble the start directory of the file's
+ path on Learn for example if we write `"Concepts"` in the field, then the file is going to be placed
+ under `/Concepts/....` inside Learn.
+ - `learn_rel_path: "/example/"`
+ - This tag represents the rest of the path, without the filename in the end, so in this case if the file is a
+ Concept, it would go under `Concepts/example/filename.md`. If you want to place the file under the "root"
+ topic folder, input `"/"`.
+ - ⚠️ In case any of these "Learn" tags are missing or falsely inputted the file will remain unpublished. This is by
+ design to prevent non-properly tagged files from getting published.
+
+While Docusaurus can make use of more metadata tags than the above, these are the minimum we require to publish the file
+on Learn.
+
+### Doc Templates
+
+These are the templates we use for our Documentation files:
+
+<details>
+<summary>Reference Docs</summary>
+
+The template that is used for Reference files is:
+
+```
+ <!--
+ title: "Apache monitoring with Netdata"
+ description: "Monitor the health and performance of Apache web servers with zero configuration, per-second metric granularity, and interactive visualizations."
+ custom_edit_url: https://github.com/netdata/go.d.plugin/edit/master/modules/apache/README.md
+ learn_topic_type: "Collector References"
+ learn_rel_path: "/sample_category"
+ learn_status: "published"
+ -->
+```
+
+## Configuration files
+
+### Data collection
+
+```
+go.d/apache.conf
+```
+
+To make changes, see `the ./edit-config task <link>`
+
+### Alerts
+
+none
+
+## Requirements to run this module
+
+- none
+
+## Requirement on the monitored application
+
+- `Apache` with enabled [`mod_status`](https://httpd.apache.org/docs/2.4/mod/mod_status.html)
+
+## Auto detection
+
+### Single node installation
+
+. . . we autodetect localhost:port and what configurations are defaults
+
+### Kubernetes installations
+
+. . . Service discovery, click here
+
+## Metrics
+
+Columns: Context | description (of the context) | units (of the context) | dimensions | alerts
+
+- Requests in `requests/s`
+- Connections in `connections`
+- Async Connections in `connections`
+- Scoreboard in `connections`
+- Bandwidth in `kilobits/s`
+- Workers in `workers`
+- Lifetime Average Number Of Requests Per Second in `requests/s`
+- Lifetime Average Number Of Bytes Served Per Second in `KiB/s`
+- Lifetime Average Response Size in `KiB`
+
+### Labels
+
+just like <https://github.com/netdata/go.d.plugin/tree/master/modules/k8s_state#labels>
+
+## Alerts
+
+collapsible content for every alert, just like the alert guides
+
+## Configuration options
+
+Table with all the configuration options available.
+
+Columns: name | description | default
+
+## Configuration example
+
+Needs only `url` to server's `server-status?auto`. Here is an example for 2 servers:
+
+```yaml
+jobs:
+ - name: local
+ url: http://127.0.0.1/server-status?auto
+ - name: remote
+ url: http://203.0.113.10/server-status?auto
+```
+
+For all available options please see
+module [configuration file](https://github.com/netdata/go.d.plugin/blob/master/config/go.d/apache.conf).
+
+## Troubleshoot
+
+backlink to the task to run this module in debug mode
+
+</details>
+
+<details>
+<summary>Task Docs</summary>
+
+The template that is used for Task files is:
+
+```
+ <!--
+ title: "Task title"
+ description: "Task description"
+ custom_edit_url: https://github.com/netdata/netdata/edit/master/docs/Tasks/sampletask.md
+ learn_topic_type: "Tasks"
+ learn_rel_path: "/sample_category"
+ learn_status: "published"
+ -->
+```
+
+## Description
+
+A small description of the Task.
+
+## Prerequisites
+
+Describe all the information that the user needs to know before proceeding with the task.
+
+## Context
+
+Describe the background information of the Task, the purpose of the Task, and what will the user achieve by completing
+it.
+
+## Steps
+
+A task consists of steps, here provide the actions needed from the user, so he can complete the task correctly.
+
+## Result
+
+Describe the expected output/ outcome of the result.
+
+## Example
+
+Provide any examples needed for the Task
+
+</details>
+
+<details>
+<summary>Concept Docs</summary>
+
+The template of the Concept files is:
+
+```
+ <!--
+ title: "Concept title"
+ description: "Concept description"
+ custom_edit_url: https://github.com/netdata/netdata/edit/master/docs/Concepts/sampleconcept.md
+ learn_topic_type: "Concepts"
+ learn_rel_path: "/sample_category"
+ learn_status: "published"
+ -->
+```
+
+## Description
+
+In our concepts we have a more loose structure, the goal is to communicate the "concept" to the user, starting with
+simple language that even a new user can understand, and building from there.
+
+</details>
+
+## Styling Guide
+
+The *Netdata style guide* establishes editorial guidelines for any writing produced by the Netdata team or the Netdata
+community, including documentation, articles, in-product UX copy, and more. Both internal Netdata teams and external
+contributors to any of Netdata's open-source projects should reference and adhere to this style guide as much as
+possible.
+
+Netdata's writing should **empower** and **educate**. You want to help people understand Netdata's value, encourage them
+to learn more, and ultimately use Netdata's products to democratize monitoring in their organizations. To achieve these
+goals, your writing should be:
+
+- **Clear**. Use simple words and sentences. Use strong, direct, and active language that encourages readers to action.
+- **Concise**. Provide solutions and answers as quickly as possible. Give users the information they need right now,
+ along with opportunities to learn more.
+- **Universal**. Think of yourself as a guide giving a tour of Netdata's products, features, and capabilities to a
+ diverse group of users. Write to reach the widest possible audience.
+
+You can achieve these goals by reading and adhering to the principles outlined below.
+
+## Voice and tone
+
+One way we write empowering, educational content is by using a consistent voice and an appropriate tone.
+
+*Voice* is like your personality, which doesn't really change day to day.
+
+*Tone* is how you express your personality. Your expression changes based on your attitude or mood, or based on who
+you're around. In writing, your reflect tone in your word choice, punctuation, sentence structure, or even the use of
+emoji.
+
+The same idea about voice and tone applies to organizations, too. Our voice shouldn't change much between two pieces of
+content, no matter who wrote each, but the tone might be quite different based on who we think is reading.
+
+For example, a [blog post](https://www.netdata.cloud/blog/) and a [press release](https://www.netdata.cloud/news/)
+should have a similar voice, despite most often being written by different people. However, blog posts are relaxed and
+witty, while press releases are focused and academic. You won't see any emoji in a press release.
+
+### Voice
+
+Netdata's voice is authentic, passionate, playful, and respectful.
+
+- **Authentic** writing is honest and fact-driven. Focus on Netdata's strength while accurately communicating what
+ Netdata can and cannot do, and emphasize technical accuracy over hard sells and marketing jargon.
+- **Passionate** writing is strong and direct. Be a champion for the product or feature you're writing about, and let
+ your unique personality and writing style shine.
+- **Playful** writing is friendly, thoughtful, and engaging. Don't take yourself too seriously, as long as it's not at
+ the expense of Netdata or any of its users.
+- **Respectful** writing treats people the way you want to be treated. Prioritize giving solutions and answers as
+ quickly as possible.
+
+### Tone
+
+Netdata's tone is fun and playful, but clarity and conciseness comes first. We also tend to be informal, and aren't
+afraid of a playful joke or two.
+
+While we have general standards for voice and tone, we do want every individual's unique writing style to reflect in
+published content.
+
+## Universal communication
+
+Netdata is a global company in every sense, with employees, contributors, and users from around the world. We strive to
+communicate in a way that is clear and easily understood by everyone.
+
+Here are some guidelines, pointers, and questions to be aware of as you write to ensure your writing is universal. Some
+of these are expanded into individual sections in
+the [language, grammar, and mechanics](#language-grammar-and-mechanics) section below.
+
+- Would this language make sense to someone who doesn't work here?
+- Could someone quickly scan this document and understand the material?
+- Create an information hierarchy with key information presented first and clearly called out to improve scannability.
+- Avoid directional language like "sidebar on the right of the page" or "header at the top of the page" since
+ presentation elements may adapt for devices.
+- Use descriptive links rather than "click here" or "learn more".
+- Include alt text for images and image links.
+- Ensure any information contained within a graphic element is also available as plain text.
+- Avoid idioms that may not be familiar to the user or that may not make sense when translated.
+- Avoid local, cultural, or historical references that may be unfamiliar to users.
+- Prioritize active, direct language.
+- Avoid referring to someone's age unless it is directly relevant; likewise, avoid referring to people with age-related
+ descriptors like "young" or "elderly."
+- Avoid disability-related idioms like "lame" or "falling on deaf ears." Don't refer to a person's disability unless
+ it’s directly relevant to what you're writing.
+- Don't call groups of people "guys." Don't call women "girls."
+- Avoid gendered terms in favor of neutral alternatives, like "server" instead of "waitress" and "businessperson"
+ instead of "businessman."
+- When writing about a person, use their communicated pronouns. When in doubt, just ask or use their name. It's OK to
+ use "they" as a singular pronoun.
+
+> Some of these guidelines were adapted from MailChimp under the Creative Commons license.
+
+## Language, grammar, and mechanics
+
+To ensure Netdata's writing is clear, concise, and universal, we have established standards for language, grammar, and
+certain writing mechanics. However, if you're writing about Netdata for an external publication, such as a guest blog
+post, follow that publication's style guide or standards, while keeping
+the [preferred spelling of Netdata terms](#netdata-specific-terms) in mind.
+
+### Active voice
+
+Active voice is more concise and easier to understand compared to passive voice. When using active voice, the subject of
+the sentence is action. In passive voice, the subject is acted upon. A famous example of passive voice is the phrase
+"mistakes were made."
+
+| | |
+|-----------------|-------------------------------------------------------------------------------------------|
+| Not recommended | When an alarm is triggered by a metric, a notification is sent by Netdata. |
+| **Recommended** | When a metric triggers an alarm, Netdata sends a notification to your preferred endpoint. |
+
+### Second person
+
+Use the second person ("you") to give instructions or "talk" directly to users.
+
+In these situations, avoid "we," "I," "let's," and "us," particularly in documentation. The "you" pronoun can also be
+implied, depending on your sentence structure.
+
+One valid exception is when a member of the Netdata team or community wants to write about said team or community.
+
+| | |
+|--------------------------------|--------------------------------------------------------------|
+| Not recommended | To install Netdata, we should try the one-line installer... |
+| **Recommended** | To install Netdata, you should try the one-line installer... |
+| **Recommended**, implied "you" | To install Netdata, try the one-line installer... |
+
+### "Easy" or "simple"
+
+Using words that imply the complexity of a task or feature goes against our policy
+of [universal communication](#universal-communication). If you claim that a task is easy and the reader struggles to
+complete it, you may inadvertently discourage them.
+
+However, if you give users two options and want to relay that one option is genuinely less complex than another, be
+specific about how and why.
+
+For example, don't write, "Netdata's one-line installer is the easiest way to install Netdata." Instead, you might want
+to say, "Netdata's one-line installer requires fewer steps than manually installing from source."
+
+### Slang, metaphors, and jargon
+
+A particular word, phrase, or metaphor you're familiar with might not translate well to the other cultures featured
+among Netdata's global community. We recommended you avoid slang or colloquialisms in your writing.
+
+In addition, don't use abbreviations that have not yet been defined in the content. See our section on
+[abbreviations](#abbreviations-acronyms-and-initialisms) for additional guidance.
+
+If you must use industry jargon, such as "mean time to resolution," define the term as clearly and concisely as you can.
+
+> Netdata helps you reduce your organization's mean time to resolution (MTTR), which is the average time the responsible
+> team requires to repair a system and resolve an ongoing incident.
+
+### Spelling
+
+While the Netdata team is mostly *not* American, we still aspire to use American spelling whenever possible, as it is
+the standard for the monitoring industry.
+
+See the [word list](#word-list) for spellings of specific words.
+
+### Capitalization
+
+Follow the general [English standards](https://owl.purdue.edu/owl/general_writing/mechanics/help_with_capitals.html) for
+capitalization. In summary:
+
+- Capitalize the first word of every new sentence.
+- Don't use uppercase for emphasis. (Netdata is the BEST!)
+- Capitalize the names of brands, software, products, and companies according to their official guidelines. (Netdata,
+ Docker, Apache, NGINX)
+- Avoid camel case (NetData) or all caps (NETDATA).
+
+Whenever you refer to the company Netdata, Inc., or the open-source monitoring agent the company develops, capitalize
+**Netdata**.
+
+However, if you are referring to a process, user, or group on a Linux system, use lowercase and fence the word in an
+inline code block: `` `netdata` ``.
+
+| | |
+|-----------------|------------------------------------------------------------------------------------------------|
+| Not recommended | The netdata agent, which spawns the netdata process, is actively maintained by netdata, inc. |
+| **Recommended** | The Netdata Agent, which spawns the `netdata` process, is actively maintained by Netdata, Inc. |
+
+#### Capitalization of document titles and page headings
+
+Document titles and page headings should use sentence case. That means you should only capitalize the first word.
+
+If you need to use the name of a brand, software, product, and company, capitalize it according to their official
+guidelines.
+
+Also, don't put a period (`.`) or colon (`:`) at the end of a title or header.
+
+| | |
+|-----------------|-----------------------------------------------------------------------------------------------------|
+| Not recommended | Getting Started Guide <br />Service Discovery and Auto-Detection: <br />Install netdata with docker |
+| **Recommended** | Getting started guide <br />Service discovery and auto-detection <br />Install Netdata with Docker |
+
+### Abbreviations (acronyms and initialisms)
+
+Use abbreviations (including [acronyms and initialisms](https://www.dictionary.com/e/acronym-vs-abbreviation/)) in
+documentation when one exists, when it's widely accepted within the monitoring/sysadmin community, and when it improves
+the readability of a document.
+
+When introducing an abbreviation to a document for the first time, give the reader both the spelled-out version and the
+shortened version at the same time. For example:
+
+> Use Netdata to monitor Extended Berkeley Packet Filter (eBPF) metrics in real-time.
+> After you define an abbreviation, don't switch back and forth. Use only the abbreviation for the rest of the document.
+
+You can also use abbreviations in a document's title to keep the title short and relevant. If you do this, you should
+still introduce the spelled-out name alongside the abbreviation as soon as possible.
+
+### Clause order
+
+When instructing users to take action, give them the context first. By placing the context in an initial clause at the
+beginning of the sentence, users can immediately know if they want to read more, follow a link, or skip ahead.
+
+| | |
+|-----------------|--------------------------------------------------------------------------------|
+| Not recommended | Read the reference guide if you'd like to learn more about custom dashboards. |
+| **Recommended** | If you'd like to learn more about custom dashboards, read the reference guide. |
+
+### Oxford comma
+
+The Oxford comma is the comma used after the second-to-last item in a list of three or more items. It appears just
+before "and" or "or."
+
+| | |
+|-----------------|------------------------------------------------------------------------------|
+| Not recommended | Netdata can monitor RAM, disk I/O, MySQL queries per second and lm-sensors. |
+| **Recommended** | Netdata can monitor RAM, disk I/O, MySQL queries per second, and lm-sensors. |
+
+### Future releases or features
+
+Do not mention future releases or upcoming features in writing unless they have been previously communicated via a
+public roadmap.
+
+In particular, documentation must describe, as accurately as possible, the Netdata Agent _as of
+the [latest commit](https://github.com/netdata/netdata/commits/master) in the GitHub repository_. For Netdata Cloud,
+documentation must reflect the *current state* of [production](https://app.netdata.cloud).
+
+### Informational links
+
+Every link should clearly state its destination. Don't use words like "here" to describe where a link will take your
+reader.
+
+| | |
+|-----------------|-----------------------------------------------------------------------------------------------------------------------------------------|
+| Not recommended | To install Netdata, click [here](https://github.com/netdata/netdata/blob/master/packaging/installer/README.md). |
+| **Recommended** | To install Netdata, read the [installation instructions](https://github.com/netdata/netdata/blob/master/packaging/installer/README.md). |
+
+Use links as often as required to provide necessary context. Blog posts and guides require less hyperlinks than
+documentation. See the section on [linking between documentation](#linking-between-documentation) for guidance on the
+Markdown syntax and path structure of inter-documentation links.
+
+### Contractions
+
+Contractions like "you'll" or "they're" are acceptable in most Netdata writing. They're both authentic and playful, and
+reinforce the idea that you, as a writer, are guiding users through a particular idea, process, or feature.
+
+Contractions are generally not used in press releases or other media engagements.
+
+### Emoji
+
+Emoji can add fun and character to your writing, but should be used sparingly and only if it matches the content's tone
+and desired audience.
+
+### Switching Linux users
+
+Netdata documentation often suggests that users switch from their normal user to the `netdata` user to run specific
+commands. Use the following command to instruct users to make the switch:
+
+```bash
+sudo su -s /bin/bash netdata
+```
+
+### Hostname/IP address of a node
+
+Use `NODE` instead of an actual or example IP address/hostname when referencing the process of navigating to a dashboard
+or API endpoint in a browser.
+
+| | |
+|-----------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| Not recommended | Navigate to `http://example.com:19999` in your browser to see Netdata's dashboard. <br />Navigate to `http://203.0.113.0:19999` in your browser to see Netdata's dashboard. |
+| **Recommended** | Navigate to `http://NODE:19999` in your browser to see Netdata's dashboard. |
+
+If you worry that `NODE` doesn't provide enough context for the user, particularly in documentation or guides designed
+for beginners, you can provide an explanation:
+
+> With the Netdata Agent running, visit `http://NODE:19999/api/v1/info` in your browser, replacing `NODE` with the IP
+> address or hostname of your Agent.
+
+### Paths and running commands
+
+When instructing users to run a Netdata-specific command, don't assume the path to said command, because not every
+Netdata Agent installation will have commands under the same paths. When applicable, help them navigate to the correct
+path, providing a recommendation or instructions on how to view the running configuration, which includes the correct
+paths.
+
+For example, the [configuration](https://github.com/netdata/netdata/blob/master/docs/configure/nodes.md) doc first
+teaches users how to find the Netdata config
+directory and navigate to it, then runs commands from the `/etc/netdata` path so that the instructions are more
+universal.
+
+Don't include full paths, beginning from the system's root (`/`), as these might not work on certain systems.
+
+| | |
+|-----------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| Not recommended | Use `edit-config` to edit Netdata's configuration: `sudo /etc/netdata/edit-config netdata.conf`. |
+| **Recommended** | Use `edit-config` to edit Netdata's configuration by first navigating to your [Netdata config directory](https://github.com/netdata/netdata/blob/master/docs/configure/nodes.md#the-netdata-config-directory), which is typically at `/etc/netdata`, then running `sudo edit-config netdata.conf`. |
+
+### `sudo`
+
+Include `sudo` before a command if you believe most Netdata users will need to elevate privileges to run it. This makes
+our writing more universal, and users on `sudo`-less systems are generally already aware that they need to run commands
+differently.
+
+For example, most users need to use `sudo` with the `edit-config` script, because the Netdata config directory is owned
+by the `netdata` user. Same goes for restarting the Netdata Agent with `systemctl`.
+
+| | |
+|-----------------|----------------------------------------------------------------------------------------------------------------------------------------------|
+| Not recommended | Run `edit-config netdata.conf` to configure the Netdata Agent. <br />Run `systemctl restart netdata` to restart the Netdata Agent. |
+| **Recommended** | Run `sudo edit-config netdata.conf` to configure the Netdata Agent. <br />Run `sudo systemctl restart netdata` to restart the Netdata Agent. |
+
+## Markdown syntax
+
+Netdata's documentation uses Markdown syntax.
+
+If you're not familiar with Markdown, read the [Mastering
+Markdown](https://guides.github.com/features/mastering-markdown/) guide from GitHub for the basics on creating
+paragraphs, styled text, lists, tables, and more.
+
+The following sections describe situations in which a specific syntax is required.
+
+### Syntax standards (`remark-lint`)
+
+The Netdata team uses [`remark-lint`](https://github.com/remarkjs/remark-lint) for Markdown code styling.
+
+- Use a maximum of 120 characters per line.
+- Begin headings with hashes, such as `# H1 heading`, `## H2 heading`, and so on.
+- Use `_` for italics/emphasis.
+- Use `**` for bold.
+- Use dashes `-` to begin an unordered list, and put a single space after the dash.
+- Tables should be padded so that pipes line up vertically with added whitespace.
+
+If you want to see all the settings, open the
+[`remarkrc.js`](https://github.com/netdata/netdata/blob/master/.remarkrc.js) file in the `netdata/netdata` repository.
+
+### MDX and markdown
+
+While writing in Docusaurus, you might want to take leverage of it's features that are supported in MDX formatted files.
+One of those that we use is [Tabs](https://docusaurus.io/docs/next/markdown-features/tabs). They use an HTML syntax,
+which requires some changes in the way we write markdown inside them.
+
+In detail:
+
+Due to a bug with docusaurus, we prefer to use `<h1>heading</h1> instead of # H1` so that docusaurus doesn't render the
+contents of all Tabs on the right hand side, while not being able to navigate
+them [relative link](https://github.com/facebook/docusaurus/issues/7008).
+
+You can use markdown syntax for every other styling you want to do except Admonitions:
+For admonitions, follow [this](https://docusaurus.io/docs/markdown-features/admonitions#usage-in-jsx) guide to use
+admonitions inside JSX. While writing in JSX, all the markdown stylings have to be in HTML format to be rendered
+properly.
+
+### Frontmatter
+
+Every document must begin with frontmatter, followed by an H1 (`#`) heading.
+
+Unlike typical Markdown frontmatter, Netdata uses HTML comments (`<!--`, `-->`) to begin and end the frontmatter block.
+These HTML comments are later converted into typical frontmatter syntax when building [Netdata
+Learn](https://learn.netdata.cloud).
+
+Frontmatter *must* contain the following variables:
+
+- A `title` that quickly and distinctly describes the document's content.
+- A `description` that elaborates on the purpose or goal of the document using no less than 100 characters and no more
+ than 155 characters.
+- A `custom_edit_url` that links directly to the GitHub URL where another user could suggest additional changes to the
+ published document.
+
+Some documents, like the Ansible guide and others in the `/docs/guides` folder, require an `image` variable as well. In
+this case, replace `/docs` with `/img/seo`, and then rebuild the remainder of the path to the document in question. End
+the path with `.png`. A member of the Netdata team will assist in creating the image when publishing the content.
+
+For example, here is the frontmatter for the guide about [deploying the Netdata Agent with
+Ansible](https://github.com/netdata/netdata/blob/master/docs/guides/deploy/ansible.md).
+
+```markdown
+<!--
+title: Deploy Netdata with Ansible
+description: "Deploy an infrastructure monitoring solution in minutes with the Netdata Agent and Ansible. Use and customize a simple playbook for monitoring as code."
+image: /img/seo/guides/deploy/ansible.png
+custom_edit_url: https://github.com/netdata/netdata/edit/master/docs/guides/deploy/ansible.md
+-->
+
+# Deploy Netdata with Ansible
+
+...
+```
+
+Questions about frontmatter in documentation? [Ask on our community
+forum](https://community.netdata.cloud/c/blog-posts-and-articles/6).
+
+### Admonitions
+
+In addition to basic markdown syntax, we also encourage the use of admonition syntax, which allows for a more
+aesthetically seamless presentation of supplemental information. For general instructions on using admonitions, feel
+free to read this [feature guide](https://docusaurus.io/docs/markdown-features/admonitions).
+
+We encourage the use of **Note** admonitions to provide important supplemental information to a user within a task step,
+reference item, or concept passage.
+
+Additionally, you should use a **Caution** admonition to provide necessary information to present any risk to a user's
+setup or data.
+
+**Danger** admonitions should be avoided, as these admonitions are typically applied to reduce physical or bodily harm
+to an individual.
+
+### Linking between documentation
+
+Documentation should link to relevant pages whenever it's relevant and provides valuable context to the reader.
+
+We link between markdown documents by using its GitHub absolute link for
+instance `[short description of what we reference](https://github.com/netdata/netdata/blob/master/contribution-guidelines.md)`
+
+### References to UI elements
+
+When referencing a user interface (UI) element in Netdata, reference the label text of the link/button with Markdown's
+(`**bold text**`) tag.
+
+```markdown
+Click the **Sign in** button.
+```
+
+Avoid directional language whenever possible. Not every user can use instructions like "look at the top-left corner" to
+find their way around an interface, and interfaces often change between devices. If you must use directional language,
+try to supplement the text with an [image](#images).
+
+### Images
+
+Don't rely on images to convey features, ideas, or instructions. Accompany every image with descriptive alt text.
+
+In Markdown, use the standard image syntax, `![](/docs/agent/contributing)`, and place the alt text between the
+brackets `[]`. Here's an example
+using our logo:
+
+```markdown
+![The Netdata logo](/docs/agent/web/gui/static/img/netdata-logomark.svg)
+```
+
+Reference in-product text, code samples, and terminal output with actual text content, not screen captures or other
+images. Place the text in an appropriate element, such as a blockquote or code block, so all users can parse the
+information.
+
+### Syntax highlighting
+
+Our documentation site at [learn.netdata.cloud](https://learn.netdata.cloud) uses
+[Prism](https://v2.docusaurus.io/docs/markdown-features#syntax-highlighting) for syntax highlighting. Netdata
+documentation will use the following for the most part: `c`, `python`, `js`, `shell`, `markdown`, `bash`, `css`, `html`,
+and `go`. If no language is specified, Prism tries to guess the language based on its content.
+
+Include the language directly after the three backticks (```` ``` ````) that start the code block. For highlighting C
+code, for example:
+
+````c
+```c
+inline char *health_stock_config_dir(void) {
+ char buffer[FILENAME_MAX + 1];
+ snprintfz(buffer, FILENAME_MAX, "%s/health.d", netdata_configured_stock_config_dir);
+ return config_get(CONFIG_SECTION_DIRECTORIES, "stock health config", buffer);
+}
+```
+````
+
+And the prettified result:
+
+```c
+inline char *health_stock_config_dir(void) {
+ char buffer[FILENAME_MAX + 1];
+ snprintfz(buffer, FILENAME_MAX, "%s/health.d", netdata_configured_stock_config_dir);
+ return config_get(CONFIG_SECTION_DIRECTORIES, "stock health config", buffer);
+}
+```
+
+Prism also supports titles and line highlighting. See
+the [Docusaurus documentation](https://v2.docusaurus.io/docs/markdown-features#code-blocks) for more information.
+
+## Word list
+
+The following tables describe the standard spelling, capitalization, and usage of words found in Netdata's writing.
+
+### Netdata-specific terms
+
+| Term | Definition |
+|----------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| **claimed node** | A node that you've proved ownership of by completing the [connecting to Cloud process](https://github.com/netdata/netdata/blob/master/claim/README.md). The claimed node will then appear in your Space and any War Rooms you added it to. |
+| **Netdata** | The company behind the open-source Netdata Agent and the Netdata Cloud web application. Never use *netdata* or *NetData*. <br /><br />**Note:** You should use "Netdata" when referencing any general element, function, or part of the user experience. In general, focus on the user's goals, actions, and solutions rather than what the company provides. For example, write *Learn more about enabling alarm notifications on your preferred platforms* instead of *Netdata sends alarm notifications to your preferred platforms*. |
+| **Netdata Agent** or **Open-source Netdata Agent** | The free and open source [monitoring agent](https://github.com/netdata/netdata) that you can install on all of your distributed systems, whether they're physical, virtual, containerized, ephemeral, and more. The Agent monitors systems running Linux, Docker, Kubernetes, macOS, FreeBSD, and more, and collects metrics from hundreds of popular services and applications. <br /><br /> **Note:** You should avoid referencing the Netdata Agent or Open-source Netdata agent in any scenario that does not specifically require the distinction for clear instructions. |
+| **Netdata Cloud** | The web application hosted at [https://app.netdata.cloud](https://app.netdata.cloud) that helps you monitor an entire infrastructure of distributed systems in real time. <br /><br />**Notes:** Never use *Cloud* without the preceding *Netdata* to avoid ambiguity. You should avoid referencing Netdata Cloud in any scenario that does not specifically require the distinction for clear instructions. | |
+| **Netdata community** | Contributors to any of Netdata's [open-source projects](https://github.com/netdata/learn/blob/master/contribute/projects.mdx), members of the [community forum](https://community.netdata.cloud/). |
+| **Netdata community forum** | The Discourse-powered forum for feature requests, Netdata Cloud technical support, and conversations about Netdata's monitoring and troubleshooting products. |
+| **node** | A system on which the Netdata Agent is installed. The system can be physical, virtual, in a Docker container, and more. Depending on your infrastructure, you may have one, dozens, or hundreds of nodes. Some nodes are *ephemeral*, in that they're created/destroyed automatically by an orchestrator service. |
+| **Space** | The highest level container within Netdata Cloud for a user to organize their team members and nodes within their infrastructure. A Space likely represents an entire organization or a large team. <br /><br />*Space* is always capitalized. |
+| **unreachable node** | A connected node with a disrupted [Agent-Cloud link](https://github.com/netdata/netdata/blob/master/aclk/README.md). Unreachable could mean the node no longer exists or is experiencing network connectivity issues with Cloud. |
+| **visited node** | A node which has had its Agent dashboard directly visited by a user. A list of these is maintained on a per-user basis. |
+| **War Room** | A smaller grouping of nodes where users can view key metrics in real-time and monitor the health of many nodes with their alarm status. War Rooms can be used to organize nodes in any way that makes sense for your infrastructure, such as by a service, purpose, physical location, and more. <br /><br />*War Room* is always capitalized. |
+
+### Other technical terms
+
+| Term | Definition |
+|-----------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| **filesystem** | Use instead of *file system*. |
+| **preconfigured** | The concept that many of Netdata's features come with sane defaults that users don't need to configure to find [immediate value](/docs/overview/why-netdata#simple-to-deploy). |
+| **real time**/**real-time** | Use *real time* as a noun phrase, most often with *in*: *Netdata collects metrics in real time*. Use *real-time* as an adjective: _Netdata collects real-time metrics from hundreds of supported applications and services. |
diff --git a/daemon/README.md b/daemon/README.md
index c5951c69..7a17506b 100644
--- a/daemon/README.md
+++ b/daemon/README.md
@@ -1,7 +1,11 @@
<!--
title: "Netdata daemon"
-date: 2020-04-29
-custom_edit_url: https://github.com/netdata/netdata/edit/master/daemon/README.md
+date: "2020-04-29"
+custom_edit_url: "https://github.com/netdata/netdata/edit/master/daemon/README.md"
+sidebar_label: "Netdata daemon"
+learn_status: "Published"
+learn_topic_type: "References"
+learn_rel_path: "References/Configuration"
-->
# Netdata daemon
@@ -11,7 +15,7 @@ custom_edit_url: https://github.com/netdata/netdata/edit/master/daemon/README.md
- You can start Netdata by executing it with `/usr/sbin/netdata` (the installer will also start it).
- You can stop Netdata by killing it with `killall netdata`. You can stop and start Netdata at any point. When
- exiting, the [database engine](/database/engine/README.md) saves metrics to `/var/cache/netdata/dbengine/` so that
+ exiting, the [database engine](https://github.com/netdata/netdata/blob/master/database/engine/README.md) saves metrics to `/var/cache/netdata/dbengine/` so that
it can continue when started again.
Access to the web site, for all graphs, is by default on port `19999`, so go to:
@@ -202,29 +206,26 @@ The command line options of the Netdata 1.10.0 version are the following:
- USR2 Reload health configuration.
```
-You can send commands during runtime via [netdatacli](/cli/README.md).
+You can send commands during runtime via [netdatacli](https://github.com/netdata/netdata/blob/master/cli/README.md).
## Log files
-Netdata uses 3 log files:
+Netdata uses 4 log files:
1. `error.log`
-2. `access.log`
-3. `debug.log`
+2. `collector.log`
+3. `access.log`
+4. `debug.log`
-Any of them can be disabled by setting it to `/dev/null` or `none` in `netdata.conf`. By default `error.log` and
-`access.log` are enabled. `debug.log` is only enabled if debugging/tracing is also enabled (Netdata needs to be compiled
-with debugging enabled).
+Any of them can be disabled by setting it to `/dev/null` or `none` in `netdata.conf`. By default `error.log`,
+`collector.log`, and `access.log` are enabled. `debug.log` is only enabled if debugging/tracing is also enabled
+(Netdata needs to be compiled with debugging enabled).
Log files are stored in `/var/log/netdata/` by default.
### error.log
-The `error.log` is the `stderr` of the `netdata` daemon and all external plugins
-run by `netdata`.
-
-So if any process, in the Netdata process tree, writes anything to its standard error,
-it will appear in `error.log`.
+The `error.log` is the `stderr` of the `netdata` daemon .
For most Netdata programs (including standard external plugins shipped by netdata), the following lines may appear:
@@ -239,6 +240,16 @@ program continues to run.
When a Netdata program cannot run at all, a `FATAL` line is logged.
+### collector.log
+
+The `collector.log` is the `stderr` of all [collectors](https://github.com/netdata/netdata/blob/master/collectors/COLLECTORS.md)
+ run by `netdata`.
+
+So if any process, in the Netdata process tree, writes anything to its standard error,
+it will appear in `collector.log`.
+
+Data stored inside this file follows pattern already described for `error.log`.
+
### access.log
The `access.log` logs web requests. The format is:
@@ -361,7 +372,7 @@ all programs), edit `netdata.conf` and set:
process nice level = -1
```
-then execute this to [restart Netdata](/docs/configure/start-stop-restart.md):
+then execute this to [restart Netdata](https://github.com/netdata/netdata/blob/master/docs/configure/start-stop-restart.md):
```sh
sudo systemctl restart netdata
diff --git a/daemon/analytics.c b/daemon/analytics.c
index 3d0e514d..a2f52bc8 100644
--- a/daemon/analytics.c
+++ b/daemon/analytics.c
@@ -223,9 +223,7 @@ void analytics_mirrored_hosts(void)
if (rrdhost_flag_check(host, RRDHOST_FLAG_ARCHIVED))
continue;
- netdata_mutex_lock(&host->receiver_lock);
- ((host->receiver || host == localhost) ? reachable++ : unreachable++);
- netdata_mutex_unlock(&host->receiver_lock);
+ ((host == localhost || !rrdhost_flag_check(host, RRDHOST_FLAG_ORPHAN)) ? reachable++ : unreachable++);
count++;
}
@@ -243,7 +241,7 @@ void analytics_exporters(void)
{
//when no exporters are available, an empty string will be sent
//decide if something else is more suitable (but probably not null)
- BUFFER *bi = buffer_create(1000);
+ BUFFER *bi = buffer_create(1000, NULL);
analytics_exporting_connectors(bi);
analytics_set_data_str(&analytics_data.netdata_exporting_connectors, (char *)buffer_tostring(bi));
buffer_free(bi);
@@ -280,7 +278,7 @@ void analytics_collectors(void)
RRDSET *st;
DICTIONARY *dict = dictionary_create(DICT_OPTION_SINGLE_THREADED);
char name[500];
- BUFFER *bt = buffer_create(1000);
+ BUFFER *bt = buffer_create(1000, NULL);
rrdset_foreach_read(st, localhost) {
if(!rrdset_is_available_for_viewers(st))
@@ -335,7 +333,7 @@ void analytics_alarms_notifications(void)
debug(D_ANALYTICS, "Executing %s", script);
- BUFFER *b = buffer_create(1000);
+ BUFFER *b = buffer_create(1000, NULL);
int cnt = 0;
FILE *fp_child_input;
FILE *fp_child_output = netdata_popen(script, &command_pid, &fp_child_input);
@@ -382,7 +380,7 @@ void analytics_get_install_type(void)
*/
void analytics_https(void)
{
- BUFFER *b = buffer_create(30);
+ BUFFER *b = buffer_create(30, NULL);
#ifdef ENABLE_HTTPS
analytics_exporting_connectors_ssl(b);
buffer_strcat(b, netdata_ssl_client_ctx && rrdhost_flag_check(localhost, RRDHOST_FLAG_RRDPUSH_SENDER_CONNECTED) && localhost->sender->ssl.flags == NETDATA_SSL_HANDSHAKE_COMPLETE ? "streaming|" : "|");
@@ -554,7 +552,7 @@ void analytics_gather_mutable_meta_data(void)
snprintfz(b, 6, "%d", analytics_data.dashboard_hits);
analytics_set_data(&analytics_data.netdata_dashboard_used, b);
- snprintfz(b, 6, "%zu", rrd_hosts_available);
+ snprintfz(b, 6, "%zu", rrdhost_hosts_available());
analytics_set_data(&analytics_data.netdata_config_hosts_available, b);
}
}
@@ -587,12 +585,12 @@ void *analytics_main(void *ptr)
debug(D_ANALYTICS, "Analytics thread starts");
//first delay after agent start
- while (!netdata_exit && likely(sec <= ANALYTICS_INIT_SLEEP_SEC)) {
+ while (service_running(SERVICE_ANALYTICS) && likely(sec <= ANALYTICS_INIT_SLEEP_SEC)) {
heartbeat_next(&hb, step_ut);
sec++;
}
- if (unlikely(netdata_exit))
+ if (unlikely(!service_running(SERVICE_ANALYTICS)))
goto cleanup;
analytics_gather_immutable_meta_data();
@@ -605,7 +603,7 @@ void *analytics_main(void *ptr)
heartbeat_next(&hb, step_ut * 2);
sec += 2;
- if (unlikely(netdata_exit))
+ if (unlikely(!service_running(SERVICE_ANALYTICS)))
break;
if (likely(sec < ANALYTICS_HEARTBEAT))
@@ -677,7 +675,7 @@ void set_late_global_environment()
analytics_set_data_str(&analytics_data.netdata_config_release_channel, (char *)get_release_channel());
{
- BUFFER *bi = buffer_create(1000);
+ BUFFER *bi = buffer_create(1000, NULL);
analytics_build_info(bi);
analytics_set_data_str(&analytics_data.netdata_buildinfo, (char *)buffer_tostring(bi));
buffer_free(bi);
@@ -838,7 +836,7 @@ void set_global_environment()
setenv("NETDATA_HOST_PREFIX", netdata_configured_host_prefix, 1);
{
- BUFFER *user_plugins_dirs = buffer_create(FILENAME_MAX);
+ BUFFER *user_plugins_dirs = buffer_create(FILENAME_MAX, NULL);
for (size_t i = 1; i < PLUGINSD_MAX_DIRECTORIES && plugin_directories[i]; i++) {
if (i > 1)
diff --git a/daemon/commands.c b/daemon/commands.c
index 6288ee59..377a4002 100644
--- a/daemon/commands.c
+++ b/daemon/commands.c
@@ -46,6 +46,7 @@ static cmd_status_t cmd_read_config_execute(char *args, char **message);
static cmd_status_t cmd_write_config_execute(char *args, char **message);
static cmd_status_t cmd_ping_execute(char *args, char **message);
static cmd_status_t cmd_aclk_state(char *args, char **message);
+static cmd_status_t cmd_version(char *args, char **message);
static command_info_t command_info_array[] = {
{"help", cmd_help_execute, CMD_TYPE_HIGH_PRIORITY}, // show help menu
@@ -59,7 +60,8 @@ static command_info_t command_info_array[] = {
{"read-config", cmd_read_config_execute, CMD_TYPE_CONCURRENT},
{"write-config", cmd_write_config_execute, CMD_TYPE_ORTHOGONAL},
{"ping", cmd_ping_execute, CMD_TYPE_ORTHOGONAL},
- {"aclk-state", cmd_aclk_state, CMD_TYPE_ORTHOGONAL}
+ {"aclk-state", cmd_aclk_state, CMD_TYPE_ORTHOGONAL},
+ {"version", cmd_version, CMD_TYPE_ORTHOGONAL}
};
/* Mutexes for commands of type CMD_TYPE_ORTHOGONAL */
@@ -124,7 +126,9 @@ static cmd_status_t cmd_help_execute(char *args, char **message)
"ping\n"
" Return with 'pong' if agent is alive.\n"
"aclk-state [json]\n"
- " Returns current state of ACLK and Cloud connection. (optionally in json)\n",
+ " Returns current state of ACLK and Cloud connection. (optionally in json).\n"
+ "version\n"
+ " Returns the netdata version.\n",
MAX_COMMAND_LENGTH - 1);
return CMD_STATUS_SUCCESS;
}
@@ -216,7 +220,7 @@ static cmd_status_t cmd_reload_labels_execute(char *args, char **message)
info("COMMAND: reloading host labels.");
reload_host_labels();
- BUFFER *wb = buffer_create(10);
+ BUFFER *wb = buffer_create(10, NULL);
rrdlabels_log_to_buffer(localhost->rrdlabels, wb);
(*message)=strdupz(buffer_tostring(wb));
buffer_free(wb);
@@ -314,6 +318,18 @@ static cmd_status_t cmd_aclk_state(char *args, char **message)
return CMD_STATUS_SUCCESS;
}
+static cmd_status_t cmd_version(char *args, char **message)
+{
+ (void)args;
+
+ char version[MAX_COMMAND_LENGTH];
+ snprintfz(version, MAX_COMMAND_LENGTH -1, "%s %s", program_name, program_version);
+
+ *message = strdupz(version);
+
+ return CMD_STATUS_SUCCESS;
+}
+
static void cmd_lock_exclusive(unsigned index)
{
(void)index;
@@ -454,9 +470,13 @@ static void after_schedule_command(uv_work_t *req, int status)
static void schedule_command(uv_work_t *req)
{
- struct command_context *cmd_ctx = req->data;
+ register_libuv_worker_jobs();
+ worker_is_busy(UV_EVENT_SCHEDULE_CMD);
+ struct command_context *cmd_ctx = req->data;
cmd_ctx->status = execute_command(cmd_ctx->idx, cmd_ctx->args, &cmd_ctx->message);
+
+ worker_is_idle();
}
/* This will alter the state of the command_info_array.cmd_str
diff --git a/daemon/commands.h b/daemon/commands.h
index f0e38ce9..78bdcc77 100644
--- a/daemon/commands.h
+++ b/daemon/commands.h
@@ -25,6 +25,7 @@ typedef enum cmd {
CMD_WRITE_CONFIG,
CMD_PING,
CMD_ACLK_STATE,
+ CMD_VERSION,
CMD_TOTAL_COMMANDS
} cmd_t;
diff --git a/daemon/common.c b/daemon/common.c
index 85d63863..6eae07cf 100644
--- a/daemon/common.c
+++ b/daemon/common.c
@@ -19,3 +19,38 @@ int32_t netdata_configured_utc_offset = 0;
int netdata_ready;
int netdata_cloud_setting;
+long get_netdata_cpus(void) {
+ static long processors = 0;
+
+ if(processors)
+ return processors;
+
+ long cores_proc_stat = get_system_cpus_with_cache(false, true);
+ long cores_cpuset_v1 = (long)read_cpuset_cpus("/sys/fs/cgroup/cpuset/cpuset.cpus", cores_proc_stat);
+ long cores_cpuset_v2 = (long)read_cpuset_cpus("/sys/fs/cgroup/cpuset.cpus", cores_proc_stat);
+
+ if(cores_cpuset_v2)
+ processors = cores_cpuset_v2;
+ else if(cores_cpuset_v1)
+ processors = cores_cpuset_v1;
+ else
+ processors = cores_proc_stat;
+
+ long cores_user_configured = config_get_number(CONFIG_SECTION_GLOBAL, "cpu cores", processors);
+
+ errno = 0;
+ internal_error(true,
+ "System CPUs: %ld, ("
+ "system: %ld, cgroups cpuset v1: %ld, cgroups cpuset v2: %ld, netdata.conf: %ld"
+ ")"
+ , processors
+ , cores_proc_stat
+ , cores_cpuset_v1
+ , cores_cpuset_v2
+ , cores_user_configured
+ );
+
+ processors = cores_user_configured;
+
+ return processors;
+}
diff --git a/daemon/common.h b/daemon/common.h
index f3d86866..ca4d5c95 100644
--- a/daemon/common.h
+++ b/daemon/common.h
@@ -4,6 +4,7 @@
#define NETDATA_COMMON_H 1
#include "libnetdata/libnetdata.h"
+#include "event_loop.h"
// ----------------------------------------------------------------------------
// shortcuts for the default netdata configuration
@@ -105,4 +106,6 @@ extern int netdata_anonymous_statistics_enabled;
extern int netdata_ready;
extern int netdata_cloud_setting;
+long get_netdata_cpus(void);
+
#endif /* NETDATA_COMMON_H */
diff --git a/daemon/config/README.md b/daemon/config/README.md
index 7b4d27ec..4a6d0bb8 100644
--- a/daemon/config/README.md
+++ b/daemon/config/README.md
@@ -1,7 +1,12 @@
<!--
title: "Daemon configuration"
description: "The Netdata Agent's daemon is installed preconfigured to collect thousands of metrics every second, but is highly configurable for real-world workloads."
-custom_edit_url: https://github.com/netdata/netdata/edit/master/daemon/config/README.md
+custom_edit_url: "https://github.com/netdata/netdata/edit/master/daemon/config/README.md"
+sidebar_label: "Daemon"
+learn_status: "Published"
+learn_topic_type: "References"
+learn_rel_path: "References/Configuration"
+learn_doc_purpose: "Explain the daemon options, the log files, the process scheduling, virtual memory, explain how the netdata.conf is used and backlink to the netdata.conf file reference"
-->
# Daemon configuration
@@ -21,24 +26,24 @@ adapt the general behavior of Netdata, in great detail. You can find all these s
accessing the URL `https://netdata.server.hostname:19999/netdata.conf`. For example check the configuration file
of [netdata.firehol.org](http://netdata.firehol.org/netdata.conf). HTTP access to this file is limited by default to
[private IPs](https://en.wikipedia.org/wiki/Private_network), via
-the [web server access lists](/web/server/README.md#access-lists).
+the [web server access lists](https://github.com/netdata/netdata/blob/master/web/server/README.md#access-lists).
`netdata.conf` has sections stated with `[section]`. You will see the following sections:
-1. `[global]` to [configure](#global-section-options) the [Netdata daemon](/daemon/README.md).
+1. `[global]` to [configure](#global-section-options) the [Netdata daemon](https://github.com/netdata/netdata/blob/master/daemon/README.md).
2. `[db]` to [configure](#db-section-options) the database of Netdata.
3. `[directories]` to [configure](#directories-section-options) the directories used by Netdata.
4. `[logs]` to [configure](#logs-section-options) the Netdata logging.
5. `[environment variables]` to [configure](#environment-variables-section-options) the environment variables used
Netdata.
-6. `[sqlite]` to [configure](#sqlite-section-options) the [Netdata daemon](/daemon/README.md) SQLite settings.
-7. `[ml]` to configure settings for [machine learning](/ml/README.md).
-8. `[health]` to [configure](#health-section-options) general settings for [health monitoring](/health/README.md).
-9. `[web]` to [configure the web server](/web/server/README.md).
-10. `[registry]` for the [Netdata registry](/registry/README.md).
-11. `[global statistics]` for the [Netdata registry](/registry/README.md).
-12. `[statsd]` for the general settings of the [stats.d.plugin](/collectors/statsd.plugin/README.md).
-13. `[plugins]` to [configure](#plugins-section-options) which [collectors](/collectors/README.md) to use and PATH
+6. `[sqlite]` to [configure](#sqlite-section-options) the [Netdata daemon](https://github.com/netdata/netdata/blob/master/daemon/README.md) SQLite settings.
+7. `[ml]` to configure settings for [machine learning](https://github.com/netdata/netdata/blob/master/ml/README.md).
+8. `[health]` to [configure](#health-section-options) general settings for [health monitoring](https://github.com/netdata/netdata/blob/master/health/README.md).
+9. `[web]` to [configure the web server](https://github.com/netdata/netdata/blob/master/web/server/README.md).
+10. `[registry]` for the [Netdata registry](https://github.com/netdata/netdata/blob/master/registry/README.md).
+11. `[global statistics]` for the [Netdata registry](https://github.com/netdata/netdata/blob/master/registry/README.md).
+12. `[statsd]` for the general settings of the [stats.d.plugin](https://github.com/netdata/netdata/blob/master/collectors/statsd.plugin/README.md).
+13. `[plugins]` to [configure](#plugins-section-options) which [collectors](https://github.com/netdata/netdata/blob/master/collectors/README.md) to use and PATH
settings.
14. `[plugin:NAME]` sections for each collector plugin, under the
comment [Per plugin configuration](#per-plugin-configuration).
@@ -49,7 +54,7 @@ comment on settings it does not currently use.
## Applying changes
-After `netdata.conf` has been modified, Netdata needs to be [restarted](/docs/configure/start-stop-restart.md) for
+After `netdata.conf` has been modified, Netdata needs to be [restarted](https://github.com/netdata/netdata/blob/master/docs/configure/start-stop-restart.md) for
changes to apply:
```bash
@@ -70,10 +75,10 @@ Please note that your data history will be lost if you have modified `history` p
| setting | default | info |
|:-------------------------------------:|:-------------:|:---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
-| process scheduling policy | `keep` | See [Netdata process scheduling policy](/daemon/README.md#netdata-process-scheduling-policy) |
+| process scheduling policy | `keep` | See [Netdata process scheduling policy](https://github.com/netdata/netdata/blob/master/daemon/README.md#netdata-process-scheduling-policy) |
| OOM score | `0` | |
-| glibc malloc arena max for plugins | `1` | See [Virtual memory](/daemon/README.md#virtual-memory). |
-| glibc malloc arena max for Netdata | `1` | See [Virtual memory](/daemon/README.md#virtual-memory). |
+| glibc malloc arena max for plugins | `1` | See [Virtual memory](https://github.com/netdata/netdata/blob/master/daemon/README.md#virtual-memory). |
+| glibc malloc arena max for Netdata | `1` | See [Virtual memory](https://github.com/netdata/netdata/blob/master/daemon/README.md#virtual-memory). |
| hostname | auto-detected | The hostname of the computer running Netdata. |
| host access prefix | empty | This is used in docker environments where /proc, /sys, etc have to be accessed via another path. You may also have to set SYS_PTRACE capability on the docker for this work. Check [issue 43](https://github.com/netdata/netdata/issues/43). |
| timezone | auto-detected | The timezone retrieved from the environment variable |
@@ -84,22 +89,22 @@ Please note that your data history will be lost if you have modified `history` p
| setting | default | info |
|:---------------------------------------------:|:----------:|:------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
-| mode | `dbengine` | `dbengine`: The default for long-term metrics storage with efficient RAM and disk usage. Can be extended with `dbengine page cache size MB` and `dbengine disk space MB`. <br />`save`: Netdata will save its round robin database on exit and load it on startup. <br />`map`: Cache files will be updated in real-time. Not ideal for systems with high load or slow disks (check `man mmap`). <br />`ram`: The round-robin database will be temporary and it will be lost when Netdata exits. <br />`none`: Disables the database at this host, and disables health monitoring entirely, as that requires a database of metrics. |
-| retention | `3600` | Used with `mode = save/map/ram/alloc`, not the default `mode = dbengine`. This number reflects the number of entries the `netdata` daemon will by default keep in memory for each chart dimension. Check [Memory Requirements](/database/README.md) for more information. |
-| storage tiers | `1` | The number of storage tiers you want to have in your dbengine. Check the tiering mechanism in the [dbengine's reference](/database/engine/README.md#tiering). You can have up to 5 tiers of data (including the _Tier 0_). This number ranges between 1 and 5. |
+| mode | `dbengine` | `dbengine`: The default for long-term metrics storage with efficient RAM and disk usage. Can be extended with `dbengine page cache size MB` and `dbengine disk space MB`. <br />`save`: Netdata will save its round robin database on exit and load it on startup. <br />`map`: Cache files will be updated in real-time. Not ideal for systems with high load or slow disks (check `man mmap`). <br />`ram`: The round-robin database will be temporary and it will be lost when Netdata exits. <br />`alloc`: Similar to `ram`, but can significantly reduce memory usage, when combined with a low retention and does not support KSM. <br />`none`: Disables the database at this host, and disables health monitoring entirely, as that requires a database of metrics. Not to be used together with streaming. |
+| retention | `3600` | Used with `mode = save/map/ram/alloc`, not the default `mode = dbengine`. This number reflects the number of entries the `netdata` daemon will by default keep in memory for each chart dimension. Check [Memory Requirements](https://github.com/netdata/netdata/blob/master/database/README.md) for more information. |
+| storage tiers | `1` | The number of storage tiers you want to have in your dbengine. Check the tiering mechanism in the [dbengine's reference](https://github.com/netdata/netdata/blob/master/database/engine/README.md#tiering). You can have up to 5 tiers of data (including the _Tier 0_). This number ranges between 1 and 5. |
| dbengine page cache size MB | `32` | Determines the amount of RAM in MiB that is dedicated to caching for _Tier 0_ Netdata metric values. |
| dbengine tier **`N`** page cache size MB | `32` | Determines the amount of RAM in MiB that is dedicated for caching Netdata metric values of the **`N`** tier. <br /> `N belongs to [1..4]` ||
| dbengine disk space MB | `256` | Determines the amount of disk space in MiB that is dedicated to storing _Tier 0_ Netdata metric values and all related metadata describing them. This option is available **only for legacy configuration** (`Agent v1.23.2 and prior`). |
| dbengine multihost disk space MB | `256` | Same functionality as `dbengine disk space MB`, but includes support for storing metrics streamed to a parent node by its children. Can be used in single-node environments as well. This setting is only for _Tier 0_ metrics. |
| dbengine tier **`N`** multihost disk space MB | `256` | Same functionality as `dbengine multihost disk space MB`, but stores metrics of the **`N`** tier (both parent node and its children). Can be used in single-node environments as well. <br /> `N belongs to [1..4]` |
-| update every | `1` | The frequency in seconds, for data collection. For more information see the [performance guide](/docs/guides/configure/performance.md). These metrics stored as _Tier 0_ data. Explore the tiering mechanism in the [dbengine's reference](/database/engine/README.md#tiering). |
+| update every | `1` | The frequency in seconds, for data collection. For more information see the [performance guide](https://github.com/netdata/netdata/blob/master/docs/guides/configure/performance.md). These metrics stored as _Tier 0_ data. Explore the tiering mechanism in the [dbengine's reference](https://github.com/netdata/netdata/blob/master/database/engine/README.md#tiering). |
| dbengine tier **`N`** update every iterations | `60` | The down sampling value of each tier from the previous one. For each Tier, the greater by one Tier has N (equal to 60 by default) less data points of any metric it collects. This setting can take values from `2` up to `255`. <br /> `N belongs to [1..4]` |
| dbengine tier **`N`** back fill | `New` | Specifies the strategy of recreating missing data on each Tier from the exact lower Tier. <br /> `New`: Sees the latest point on each Tier and save new points to it only if the exact lower Tier has available points for it's observation window (`dbengine tier N update every iterations` window). <br /> `none`: No back filling is applied. <br /> `N belongs to [1..4]` |
-| memory deduplication (ksm) | `yes` | When set to `yes`, Netdata will offer its in-memory round robin database and the dbengine page cache to kernel same page merging (KSM) for deduplication. For more information check [Memory Deduplication - Kernel Same Page Merging - KSM](/database/README.md#ksm) |
-| cleanup obsolete charts after secs | `3600` | See [monitoring ephemeral containers](/collectors/cgroups.plugin/README.md#monitoring-ephemeral-containers), also sets the timeout for cleaning up obsolete dimensions |
+| memory deduplication (ksm) | `yes` | When set to `yes`, Netdata will offer its in-memory round robin database and the dbengine page cache to kernel same page merging (KSM) for deduplication. For more information check [Memory Deduplication - Kernel Same Page Merging - KSM](https://github.com/netdata/netdata/blob/master/database/README.md#ksm) |
+| cleanup obsolete charts after secs | `3600` | See [monitoring ephemeral containers](https://github.com/netdata/netdata/blob/master/collectors/cgroups.plugin/README.md#monitoring-ephemeral-containers), also sets the timeout for cleaning up obsolete dimensions |
| gap when lost iterations above | `1` | |
| cleanup orphan hosts after secs | `3600` | How long to wait until automatically removing from the DB a remote Netdata host (child) that is no longer sending data. |
-| delete obsolete charts files | `yes` | See [monitoring ephemeral containers](/collectors/cgroups.plugin/README.md#monitoring-ephemeral-containers), also affects the deletion of files for obsolete dimensions |
+| delete obsolete charts files | `yes` | See [monitoring ephemeral containers](https://github.com/netdata/netdata/blob/master/collectors/cgroups.plugin/README.md#monitoring-ephemeral-containers), also affects the deletion of files for obsolete dimensions |
| delete orphan hosts files | `yes` | Set to `no` to disable non-responsive host removal. |
| enable zero metrics | `no` | Set to `yes` to show charts when all their metrics are zero. |
@@ -116,7 +121,7 @@ The multiplication of all the **enabled** tiers `dbengine tier N update every i
|:-------------------:|:------------------------------------------------------------------:|:----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| config | `/etc/netdata` | The directory configuration files are kept. |
| stock config | `/usr/lib/netdata/conf.d` | |
-| log | `/var/log/netdata` | The directory in which the [log files](/daemon/README.md#log-files) are kept. |
+| log | `/var/log/netdata` | The directory in which the [log files](https://github.com/netdata/netdata/blob/master/daemon/README.md#log-files) are kept. |
| web | `/usr/share/netdata/web` | The directory the web static files are kept. |
| cache | `/var/cache/netdata` | The directory the memory database will be stored if and when Netdata exits. Netdata will re-read the database when it will start again, to continue from the same point. |
| lib | `/var/lib/netdata` | Contains the alarm log and the Netdata instance GUID. |
@@ -125,14 +130,14 @@ The multiplication of all the **enabled** tiers `dbengine tier N update every i
| plugins | `"/usr/libexec/netdata/plugins.d" "/etc/netdata/custom-plugins.d"` | The directory plugin programs are kept. This setting supports multiple directories, space separated. If any directory path contains spaces, enclose it in single or double quotes. |
| health config | `/etc/netdata/health.d` | The directory containing the user alarm configuration files, to override the stock configurations |
| stock health config | `/usr/lib/netdata/conf.d/health.d` | Contains the stock alarm configuration files for each collector |
-| registry | `/opt/netdata/var/lib/netdata/registry` | Contains the [registry](/registry/README.md) database and GUID that uniquely identifies each Netdata Agent |
+| registry | `/opt/netdata/var/lib/netdata/registry` | Contains the [registry](https://github.com/netdata/netdata/blob/master/registry/README.md) database and GUID that uniquely identifies each Netdata Agent |
### [logs] section options
| setting | default | info |
|:----------------------------------:|:-----------------------------:|:-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
-| debug flags | `0x0000000000000000` | Bitmap of debug options to enable. For more information check [Tracing Options](/daemon/README.md#debugging). |
-| debug | `/var/log/netdata/debug.log` | The filename to save debug information. This file will not be created if debugging is not enabled. You can also set it to `syslog` to send the debug messages to syslog, or `none` to disable this log. For more information check [Tracing Options](/daemon/README.md#debugging). |
+| debug flags | `0x0000000000000000` | Bitmap of debug options to enable. For more information check [Tracing Options](https://github.com/netdata/netdata/blob/master/daemon/README.md#debugging). |
+| debug | `/var/log/netdata/debug.log` | The filename to save debug information. This file will not be created if debugging is not enabled. You can also set it to `syslog` to send the debug messages to syslog, or `none` to disable this log. For more information check [Tracing Options](https://github.com/netdata/netdata/blob/master/daemon/README.md#debugging). |
| error | `/var/log/netdata/error.log` | The filename to save error messages for Netdata daemon and all plugins (`stderr` is sent here for all Netdata programs, including the plugins). You can also set it to `syslog` to send the errors to syslog, or `none` to disable this log. |
| access | `/var/log/netdata/access.log` | The filename to save the log of web clients accessing Netdata charts. You can also set it to `syslog` to send the access log to syslog, or `none` to disable this log. |
| facility | `daemon` | A facility keyword is used to specify the type of system that is logging the message. |
@@ -163,9 +168,9 @@ The multiplication of all the **enabled** tiers `dbengine tier N update every i
This section controls the general behavior of the health monitoring capabilities of Netdata.
Specific alarms are configured in per-collector config files under the `health.d` directory. For more info, see [health
-monitoring](/health/README.md).
+monitoring](https://github.com/netdata/netdata/blob/master/health/README.md).
-[Alarm notifications](/health/notifications/README.md) are configured in `health_alarm_notify.conf`.
+[Alarm notifications](https://github.com/netdata/netdata/blob/master/health/notifications/README.md) are configured in `health_alarm_notify.conf`.
| setting | default | info |
|:----------------------------------------------:|:------------------------------------------------:|:-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
@@ -175,10 +180,11 @@ monitoring](/health/README.md).
| run at least every seconds | `10` | Controls how often all alarm conditions should be evaluated. |
| postpone alarms during hibernation for seconds | `60` | Prevents false alarms. May need to be increased if you get alarms during hibernation. |
| rotate log every lines | 2000 | Controls the number of alarm log entries stored in `<lib directory>/health-log.db`, where `<lib directory>` is the one configured in the [\[global\] section](#global-section-options) |
+| enabled alarms | * | Defines which alarms to load from both user and stock directories. This is a [simple pattern](https://github.com/netdata/netdata/blob/master/libnetdata/simple_pattern/README.md) list of alarm or template names. Can be used to disable specific alarms. For example, `enabled alarms = !oom_kill *` will load all alarms except `oom_kill`. |
### [web] section options
-Refer to the [web server documentation](/web/server/README.md)
+Refer to the [web server documentation](https://github.com/netdata/netdata/blob/master/web/server/README.md)
### [plugins] section options
@@ -198,7 +204,7 @@ Additionally, there will be the following options:
### [registry] section options
To understand what this section is and how it should be configured, please refer to
-the [registry documentation](/registry/README.md).
+the [registry documentation](https://github.com/netdata/netdata/blob/master/registry/README.md).
## Per-plugin configuration
@@ -206,7 +212,7 @@ The configuration options for plugins appear in sections following the pattern `
### Internal plugins
-Most internal plugins will provide additional options. Check [Internal Plugins](/collectors/README.md) for more
+Most internal plugins will provide additional options. Check [Internal Plugins](https://github.com/netdata/netdata/blob/master/collectors/README.md) for more
information.
Please note, that by default Netdata will enable monitoring metrics for disks, memory, and network only when they are
@@ -222,7 +228,7 @@ External plugins will have only 2 options at `netdata.conf`:
| setting | default | info |
|:---------------:|:--------------------------------------------:|:------------------------------------------------------------------------------------------------------------------------------------------------------|
-| update every | the value of `[global].update every` setting | The frequency in seconds the plugin should collect values. For more information check the [performance guide](/docs/guides/configure/performance.md). |
+| update every | the value of `[global].update every` setting | The frequency in seconds the plugin should collect values. For more information check the [performance guide](https://github.com/netdata/netdata/blob/master/docs/guides/configure/performance.md). |
| command options | - | Additional command line options to pass to the plugin. | |
External plugins that need additional configuration may support a dedicated file in `/etc/netdata`. Check their
diff --git a/daemon/event_loop.c b/daemon/event_loop.c
new file mode 100644
index 00000000..6f09cd65
--- /dev/null
+++ b/daemon/event_loop.c
@@ -0,0 +1,59 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include <daemon/main.h>
+#include "event_loop.h"
+
+// Register workers
+void register_libuv_worker_jobs() {
+ static __thread bool registered = false;
+
+ if(likely(registered))
+ return;
+
+ registered = true;
+
+ worker_register("LIBUV");
+
+ // generic
+ worker_register_job_name(UV_EVENT_WORKER_INIT, "worker init");
+
+ // query related
+ worker_register_job_name(UV_EVENT_DBENGINE_QUERY, "query");
+ worker_register_job_name(UV_EVENT_DBENGINE_EXTENT_CACHE_LOOKUP, "extent cache");
+ worker_register_job_name(UV_EVENT_DBENGINE_EXTENT_MMAP, "extent mmap");
+ worker_register_job_name(UV_EVENT_DBENGINE_EXTENT_DECOMPRESSION, "extent decompression");
+ worker_register_job_name(UV_EVENT_DBENGINE_EXTENT_PAGE_LOOKUP, "page lookup");
+ worker_register_job_name(UV_EVENT_DBENGINE_EXTENT_PAGE_POPULATION, "page populate");
+ worker_register_job_name(UV_EVENT_DBENGINE_EXTENT_PAGE_ALLOCATION, "page allocate");
+
+ // flushing related
+ worker_register_job_name(UV_EVENT_DBENGINE_FLUSH_MAIN_CACHE, "flush main");
+ worker_register_job_name(UV_EVENT_DBENGINE_EXTENT_WRITE, "extent write");
+ worker_register_job_name(UV_EVENT_DBENGINE_FLUSHED_TO_OPEN, "flushed to open");
+
+ // datafile full
+ worker_register_job_name(UV_EVENT_DBENGINE_JOURNAL_INDEX_WAIT, "jv2 index wait");
+ worker_register_job_name(UV_EVENT_DBENGINE_JOURNAL_INDEX, "jv2 indexing");
+
+ // db rotation related
+ worker_register_job_name(UV_EVENT_DBENGINE_DATAFILE_DELETE_WAIT, "datafile delete wait");
+ worker_register_job_name(UV_EVENT_DBENGINE_DATAFILE_DELETE, "datafile deletion");
+ worker_register_job_name(UV_EVENT_DBENGINE_FIND_ROTATED_METRICS, "find rotated metrics");
+ worker_register_job_name(UV_EVENT_DBENGINE_FIND_REMAINING_RETENTION, "find remaining retention");
+ worker_register_job_name(UV_EVENT_DBENGINE_POPULATE_MRG, "update retention");
+
+ // other dbengine events
+ worker_register_job_name(UV_EVENT_DBENGINE_EVICT_MAIN_CACHE, "evict main");
+ worker_register_job_name(UV_EVENT_DBENGINE_BUFFERS_CLEANUP, "dbengine buffers cleanup");
+ worker_register_job_name(UV_EVENT_DBENGINE_QUIESCE, "dbengine quiesce");
+ worker_register_job_name(UV_EVENT_DBENGINE_SHUTDOWN, "dbengine shutdown");
+
+ // metadata
+ worker_register_job_name(UV_EVENT_METADATA_STORE, "metadata store host");
+ worker_register_job_name(UV_EVENT_METADATA_CLEANUP, "metadata cleanup");
+
+ // netdatacli
+ worker_register_job_name(UV_EVENT_SCHEDULE_CMD, "schedule command");
+
+ uv_thread_set_name_np(pthread_self(), "LIBUV_WORKER");
+}
diff --git a/daemon/event_loop.h b/daemon/event_loop.h
new file mode 100644
index 00000000..0d3cc0d0
--- /dev/null
+++ b/daemon/event_loop.h
@@ -0,0 +1,53 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#ifndef NETDATA_EVENT_LOOP_H
+#define NETDATA_EVENT_LOOP_H
+
+enum event_loop_job {
+ UV_EVENT_JOB_NONE = 0,
+
+ // generic
+ UV_EVENT_WORKER_INIT,
+
+ // query related
+ UV_EVENT_DBENGINE_QUERY,
+ UV_EVENT_DBENGINE_EXTENT_CACHE_LOOKUP,
+ UV_EVENT_DBENGINE_EXTENT_MMAP,
+ UV_EVENT_DBENGINE_EXTENT_DECOMPRESSION,
+ UV_EVENT_DBENGINE_EXTENT_PAGE_LOOKUP,
+ UV_EVENT_DBENGINE_EXTENT_PAGE_POPULATION,
+ UV_EVENT_DBENGINE_EXTENT_PAGE_ALLOCATION,
+
+ // flushing related
+ UV_EVENT_DBENGINE_FLUSH_MAIN_CACHE,
+ UV_EVENT_DBENGINE_EXTENT_WRITE,
+ UV_EVENT_DBENGINE_FLUSHED_TO_OPEN,
+
+ // datafile full
+ UV_EVENT_DBENGINE_JOURNAL_INDEX_WAIT,
+ UV_EVENT_DBENGINE_JOURNAL_INDEX,
+
+ // db rotation related
+ UV_EVENT_DBENGINE_DATAFILE_DELETE_WAIT,
+ UV_EVENT_DBENGINE_DATAFILE_DELETE,
+ UV_EVENT_DBENGINE_FIND_ROTATED_METRICS, // find the metrics that are rotated
+ UV_EVENT_DBENGINE_FIND_REMAINING_RETENTION, // find their remaining retention
+ UV_EVENT_DBENGINE_POPULATE_MRG, // update mrg
+
+ // other dbengine events
+ UV_EVENT_DBENGINE_EVICT_MAIN_CACHE,
+ UV_EVENT_DBENGINE_BUFFERS_CLEANUP,
+ UV_EVENT_DBENGINE_QUIESCE,
+ UV_EVENT_DBENGINE_SHUTDOWN,
+
+ // metadata
+ UV_EVENT_METADATA_STORE,
+ UV_EVENT_METADATA_CLEANUP,
+
+ // netdatacli
+ UV_EVENT_SCHEDULE_CMD,
+};
+
+void register_libuv_worker_jobs();
+
+#endif //NETDATA_EVENT_LOOP_H
diff --git a/daemon/global_statistics.c b/daemon/global_statistics.c
index a4e9d321..0dc3ee64 100644
--- a/daemon/global_statistics.c
+++ b/daemon/global_statistics.c
@@ -20,6 +20,11 @@
bool global_statistics_enabled = true;
+struct netdata_buffers_statistics netdata_buffers_statistics = {};
+
+static size_t dbengine_total_memory = 0;
+size_t rrddim_db_memory_size = 0;
+
static struct global_statistics {
uint16_t connected_clients;
@@ -52,6 +57,7 @@ static struct global_statistics {
uint64_t ml_queries_made;
uint64_t ml_db_points_read;
uint64_t ml_result_points_generated;
+ uint64_t ml_models_consulted;
uint64_t exporters_queries_made;
uint64_t exporters_db_points_read;
@@ -88,6 +94,10 @@ void global_statistics_ml_query_completed(size_t points_read) {
__atomic_fetch_add(&global_statistics.ml_db_points_read, points_read, __ATOMIC_RELAXED);
}
+void global_statistics_ml_models_consulted(size_t models_consulted) {
+ __atomic_fetch_add(&global_statistics.ml_models_consulted, models_consulted, __ATOMIC_RELAXED);
+}
+
void global_statistics_exporters_query_completed(size_t points_read) {
__atomic_fetch_add(&global_statistics.exporters_queries_made, 1, __ATOMIC_RELAXED);
__atomic_fetch_add(&global_statistics.exporters_db_points_read, points_read, __ATOMIC_RELAXED);
@@ -193,6 +203,7 @@ static inline void global_statistics_copy(struct global_statistics *gs, uint8_t
gs->ml_queries_made = __atomic_load_n(&global_statistics.ml_queries_made, __ATOMIC_RELAXED);
gs->ml_db_points_read = __atomic_load_n(&global_statistics.ml_db_points_read, __ATOMIC_RELAXED);
gs->ml_result_points_generated = __atomic_load_n(&global_statistics.ml_result_points_generated, __ATOMIC_RELAXED);
+ gs->ml_models_consulted = __atomic_load_n(&global_statistics.ml_models_consulted, __ATOMIC_RELAXED);
gs->exporters_queries_made = __atomic_load_n(&global_statistics.exporters_queries_made, __ATOMIC_RELAXED);
gs->exporters_db_points_read = __atomic_load_n(&global_statistics.exporters_db_points_read, __ATOMIC_RELAXED);
@@ -208,6 +219,9 @@ static inline void global_statistics_copy(struct global_statistics *gs, uint8_t
}
}
+#define dictionary_stats_memory_total(stats) \
+ ((stats).memory.dict + (stats).memory.values + (stats).memory.index)
+
static void global_statistics_charts(void) {
static unsigned long long old_web_requests = 0,
old_web_usec = 0,
@@ -264,23 +278,182 @@ static void global_statistics_charts(void) {
// ----------------------------------------------------------------
{
- static RRDSET *st_uptime = NULL;
- static RRDDIM *rd_uptime = NULL;
+ static RRDSET *st_memory = NULL;
+ static RRDDIM *rd_database = NULL;
+ static RRDDIM *rd_collectors = NULL;
+ static RRDDIM *rd_hosts = NULL;
+ static RRDDIM *rd_rrd = NULL;
+ static RRDDIM *rd_contexts = NULL;
+ static RRDDIM *rd_health = NULL;
+ static RRDDIM *rd_functions = NULL;
+ static RRDDIM *rd_labels = NULL;
+ static RRDDIM *rd_strings = NULL;
+ static RRDDIM *rd_streaming = NULL;
+ static RRDDIM *rd_replication = NULL;
+ static RRDDIM *rd_buffers = NULL;
+ static RRDDIM *rd_workers = NULL;
+ static RRDDIM *rd_aral = NULL;
+ static RRDDIM *rd_judy = NULL;
+ static RRDDIM *rd_other = NULL;
+
+ if (unlikely(!st_memory)) {
+ st_memory = rrdset_create_localhost(
+ "netdata",
+ "memory",
+ NULL,
+ "netdata",
+ NULL,
+ "Netdata Memory",
+ "bytes",
+ "netdata",
+ "stats",
+ 130100,
+ localhost->rrd_update_every,
+ RRDSET_TYPE_STACKED);
+
+ rd_database = rrddim_add(st_memory, "db", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
+ rd_collectors = rrddim_add(st_memory, "collectors", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
+ rd_hosts = rrddim_add(st_memory, "hosts", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
+ rd_rrd = rrddim_add(st_memory, "rrdset rrddim", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
+ rd_contexts = rrddim_add(st_memory, "contexts", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
+ rd_health = rrddim_add(st_memory, "health", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
+ rd_functions = rrddim_add(st_memory, "functions", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
+ rd_labels = rrddim_add(st_memory, "labels", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
+ rd_strings = rrddim_add(st_memory, "strings", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
+ rd_streaming = rrddim_add(st_memory, "streaming", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
+ rd_replication = rrddim_add(st_memory, "replication", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
+ rd_buffers = rrddim_add(st_memory, "buffers", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
+ rd_workers = rrddim_add(st_memory, "workers", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
+ rd_aral = rrddim_add(st_memory, "aral", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
+ rd_judy = rrddim_add(st_memory, "judy", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
+ rd_other = rrddim_add(st_memory, "other", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
+ }
- if (unlikely(!st_uptime)) {
- st_uptime = rrdset_create_localhost(
+ size_t buffers =
+ netdata_buffers_statistics.query_targets_size +
+ netdata_buffers_statistics.rrdset_done_rda_size +
+ netdata_buffers_statistics.buffers_aclk +
+ netdata_buffers_statistics.buffers_api +
+ netdata_buffers_statistics.buffers_functions +
+ netdata_buffers_statistics.buffers_sqlite +
+ netdata_buffers_statistics.buffers_exporters +
+ netdata_buffers_statistics.buffers_health +
+ netdata_buffers_statistics.buffers_streaming +
+ netdata_buffers_statistics.cbuffers_streaming +
+ netdata_buffers_statistics.buffers_web +
+ replication_allocated_buffers() +
+ aral_by_size_overhead() +
+ judy_aral_overhead();
+
+ size_t strings = 0;
+ string_statistics(NULL, NULL, NULL, NULL, NULL, &strings, NULL, NULL);
+
+ rrddim_set_by_pointer(st_memory, rd_database, (collected_number)dbengine_total_memory + (collected_number)rrddim_db_memory_size);
+ rrddim_set_by_pointer(st_memory, rd_collectors, (collected_number)dictionary_stats_memory_total(dictionary_stats_category_collectors));
+ rrddim_set_by_pointer(st_memory, rd_hosts, (collected_number)dictionary_stats_memory_total(dictionary_stats_category_rrdhost) + (collected_number)netdata_buffers_statistics.rrdhost_allocations_size);
+ rrddim_set_by_pointer(st_memory, rd_rrd, (collected_number)dictionary_stats_memory_total(dictionary_stats_category_rrdset_rrddim));
+ rrddim_set_by_pointer(st_memory, rd_contexts, (collected_number)dictionary_stats_memory_total(dictionary_stats_category_rrdcontext));
+ rrddim_set_by_pointer(st_memory, rd_health, (collected_number)dictionary_stats_memory_total(dictionary_stats_category_rrdhealth));
+ rrddim_set_by_pointer(st_memory, rd_functions, (collected_number)dictionary_stats_memory_total(dictionary_stats_category_functions));
+ rrddim_set_by_pointer(st_memory, rd_labels, (collected_number)dictionary_stats_memory_total(dictionary_stats_category_rrdlabels));
+ rrddim_set_by_pointer(st_memory, rd_strings, (collected_number)strings);
+ rrddim_set_by_pointer(st_memory, rd_streaming, (collected_number)netdata_buffers_statistics.rrdhost_senders + (collected_number)netdata_buffers_statistics.rrdhost_receivers);
+ rrddim_set_by_pointer(st_memory, rd_replication, (collected_number)dictionary_stats_memory_total(dictionary_stats_category_replication) + (collected_number)replication_allocated_memory());
+ rrddim_set_by_pointer(st_memory, rd_buffers, (collected_number)buffers);
+ rrddim_set_by_pointer(st_memory, rd_workers, (collected_number) workers_allocated_memory());
+ rrddim_set_by_pointer(st_memory, rd_aral, (collected_number) aral_by_size_structures());
+ rrddim_set_by_pointer(st_memory, rd_judy, (collected_number) judy_aral_structures());
+ rrddim_set_by_pointer(st_memory, rd_other, (collected_number)dictionary_stats_memory_total(dictionary_stats_category_other));
+
+ rrdset_done(st_memory);
+ }
+
+ {
+ static RRDSET *st_memory_buffers = NULL;
+ static RRDDIM *rd_queries = NULL;
+ static RRDDIM *rd_collectors = NULL;
+ static RRDDIM *rd_buffers_aclk = NULL;
+ static RRDDIM *rd_buffers_api = NULL;
+ static RRDDIM *rd_buffers_functions = NULL;
+ static RRDDIM *rd_buffers_sqlite = NULL;
+ static RRDDIM *rd_buffers_exporters = NULL;
+ static RRDDIM *rd_buffers_health = NULL;
+ static RRDDIM *rd_buffers_streaming = NULL;
+ static RRDDIM *rd_cbuffers_streaming = NULL;
+ static RRDDIM *rd_buffers_replication = NULL;
+ static RRDDIM *rd_buffers_web = NULL;
+ static RRDDIM *rd_buffers_aral = NULL;
+ static RRDDIM *rd_buffers_judy = NULL;
+
+ if (unlikely(!st_memory_buffers)) {
+ st_memory_buffers = rrdset_create_localhost(
"netdata",
- "uptime",
+ "memory_buffers",
NULL,
"netdata",
NULL,
- "Netdata uptime",
- "seconds",
+ "Netdata Memory Buffers",
+ "bytes",
"netdata",
"stats",
- 130100,
+ 130101,
localhost->rrd_update_every,
- RRDSET_TYPE_LINE);
+ RRDSET_TYPE_STACKED);
+
+ rd_queries = rrddim_add(st_memory_buffers, "queries", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
+ rd_collectors = rrddim_add(st_memory_buffers, "collection", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
+ rd_buffers_aclk = rrddim_add(st_memory_buffers, "aclk", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
+ rd_buffers_api = rrddim_add(st_memory_buffers, "api", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
+ rd_buffers_functions = rrddim_add(st_memory_buffers, "functions", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
+ rd_buffers_sqlite = rrddim_add(st_memory_buffers, "sqlite", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
+ rd_buffers_exporters = rrddim_add(st_memory_buffers, "exporters", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
+ rd_buffers_health = rrddim_add(st_memory_buffers, "health", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
+ rd_buffers_streaming = rrddim_add(st_memory_buffers, "streaming", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
+ rd_cbuffers_streaming = rrddim_add(st_memory_buffers, "streaming cbuf", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
+ rd_buffers_replication = rrddim_add(st_memory_buffers, "replication", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
+ rd_buffers_web = rrddim_add(st_memory_buffers, "web", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
+ rd_buffers_aral = rrddim_add(st_memory_buffers, "aral", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
+ rd_buffers_judy = rrddim_add(st_memory_buffers, "judy", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
+ }
+
+ rrddim_set_by_pointer(st_memory_buffers, rd_queries, (collected_number)netdata_buffers_statistics.query_targets_size + (collected_number) onewayalloc_allocated_memory());
+ rrddim_set_by_pointer(st_memory_buffers, rd_collectors, (collected_number)netdata_buffers_statistics.rrdset_done_rda_size);
+ rrddim_set_by_pointer(st_memory_buffers, rd_buffers_aclk, (collected_number)netdata_buffers_statistics.buffers_aclk);
+ rrddim_set_by_pointer(st_memory_buffers, rd_buffers_api, (collected_number)netdata_buffers_statistics.buffers_api);
+ rrddim_set_by_pointer(st_memory_buffers, rd_buffers_functions, (collected_number)netdata_buffers_statistics.buffers_functions);
+ rrddim_set_by_pointer(st_memory_buffers, rd_buffers_sqlite, (collected_number)netdata_buffers_statistics.buffers_sqlite);
+ rrddim_set_by_pointer(st_memory_buffers, rd_buffers_exporters, (collected_number)netdata_buffers_statistics.buffers_exporters);
+ rrddim_set_by_pointer(st_memory_buffers, rd_buffers_health, (collected_number)netdata_buffers_statistics.buffers_health);
+ rrddim_set_by_pointer(st_memory_buffers, rd_buffers_streaming, (collected_number)netdata_buffers_statistics.buffers_streaming);
+ rrddim_set_by_pointer(st_memory_buffers, rd_cbuffers_streaming, (collected_number)netdata_buffers_statistics.cbuffers_streaming);
+ rrddim_set_by_pointer(st_memory_buffers, rd_buffers_replication, (collected_number)replication_allocated_buffers());
+ rrddim_set_by_pointer(st_memory_buffers, rd_buffers_web, (collected_number)netdata_buffers_statistics.buffers_web);
+ rrddim_set_by_pointer(st_memory_buffers, rd_buffers_aral, (collected_number)aral_by_size_overhead());
+ rrddim_set_by_pointer(st_memory_buffers, rd_buffers_judy, (collected_number)judy_aral_overhead());
+
+ rrdset_done(st_memory_buffers);
+ }
+
+ // ----------------------------------------------------------------
+
+ {
+ static RRDSET *st_uptime = NULL;
+ static RRDDIM *rd_uptime = NULL;
+
+ if (unlikely(!st_uptime)) {
+ st_uptime = rrdset_create_localhost(
+ "netdata",
+ "uptime",
+ NULL,
+ "netdata",
+ NULL,
+ "Netdata uptime",
+ "seconds",
+ "netdata",
+ "stats",
+ 130150,
+ localhost->rrd_update_every,
+ RRDSET_TYPE_LINE);
rd_uptime = rrddim_add(st_uptime, "uptime", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
}
@@ -653,6 +826,34 @@ static void global_statistics_charts(void) {
rrdset_done(st_points_stored);
}
+
+ {
+ static RRDSET *st = NULL;
+ static RRDDIM *rd = NULL;
+
+ if (unlikely(!st)) {
+ st = rrdset_create_localhost(
+ "netdata" // type
+ , "ml_models_consulted" // id
+ , NULL // name
+ , NETDATA_ML_CHART_FAMILY // family
+ , NULL // context
+ , "KMeans models used for prediction" // title
+ , "models" // units
+ , NETDATA_ML_PLUGIN // plugin
+ , NETDATA_ML_MODULE_DETECTION // module
+ , NETDATA_ML_CHART_PRIO_MACHINE_LEARNING_STATUS // priority
+ , localhost->rrd_update_every // update_every
+ , RRDSET_TYPE_AREA // chart_type
+ );
+
+ rd = rrddim_add(st, "num_models_consulted", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ }
+
+ rrddim_set_by_pointer(st, rd, (collected_number) gs.ml_models_consulted);
+
+ rrdset_done(st);
+ }
}
// ----------------------------------------------------------------------------
@@ -962,9 +1163,1332 @@ static void sqlite3_statistics_charts(void) {
// ----------------------------------------------------------------
}
-static void dbengine_statistics_charts(void) {
#ifdef ENABLE_DBENGINE
+
+struct dbengine2_cache_pointers {
+ RRDSET *st_cache_hit_ratio;
+ RRDDIM *rd_hit_ratio_closest;
+ RRDDIM *rd_hit_ratio_exact;
+
+ RRDSET *st_operations;
+ RRDDIM *rd_searches_closest;
+ RRDDIM *rd_searches_exact;
+ RRDDIM *rd_add_hot;
+ RRDDIM *rd_add_clean;
+ RRDDIM *rd_evictions;
+ RRDDIM *rd_flushes;
+ RRDDIM *rd_acquires;
+ RRDDIM *rd_releases;
+ RRDDIM *rd_acquires_for_deletion;
+
+ RRDSET *st_pgc_memory;
+ RRDDIM *rd_pgc_memory_free;
+ RRDDIM *rd_pgc_memory_clean;
+ RRDDIM *rd_pgc_memory_hot;
+ RRDDIM *rd_pgc_memory_dirty;
+ RRDDIM *rd_pgc_memory_index;
+ RRDDIM *rd_pgc_memory_evicting;
+ RRDDIM *rd_pgc_memory_flushing;
+
+ RRDSET *st_pgc_tm;
+ RRDDIM *rd_pgc_tm_current;
+ RRDDIM *rd_pgc_tm_wanted;
+ RRDDIM *rd_pgc_tm_hot_max;
+ RRDDIM *rd_pgc_tm_dirty_max;
+ RRDDIM *rd_pgc_tm_hot;
+ RRDDIM *rd_pgc_tm_dirty;
+ RRDDIM *rd_pgc_tm_referenced;
+
+ RRDSET *st_pgc_pages;
+ RRDDIM *rd_pgc_pages_clean;
+ RRDDIM *rd_pgc_pages_hot;
+ RRDDIM *rd_pgc_pages_dirty;
+ RRDDIM *rd_pgc_pages_referenced;
+
+ RRDSET *st_pgc_memory_changes;
+ RRDDIM *rd_pgc_memory_new_hot;
+ RRDDIM *rd_pgc_memory_new_clean;
+ RRDDIM *rd_pgc_memory_clean_evictions;
+
+ RRDSET *st_pgc_memory_migrations;
+ RRDDIM *rd_pgc_memory_hot_to_dirty;
+ RRDDIM *rd_pgc_memory_dirty_to_clean;
+
+ RRDSET *st_pgc_workers;
+ RRDDIM *rd_pgc_workers_evictors;
+ RRDDIM *rd_pgc_workers_flushers;
+ RRDDIM *rd_pgc_workers_adders;
+ RRDDIM *rd_pgc_workers_searchers;
+ RRDDIM *rd_pgc_workers_jv2_flushers;
+ RRDDIM *rd_pgc_workers_hot2dirty;
+
+ RRDSET *st_pgc_memory_events;
+ RRDDIM *rd_pgc_memory_evictions_critical;
+ RRDDIM *rd_pgc_memory_evictions_aggressive;
+ RRDDIM *rd_pgc_memory_flushes_critical;
+
+ RRDSET *st_pgc_waste;
+ RRDDIM *rd_pgc_waste_evictions_skipped;
+ RRDDIM *rd_pgc_waste_flushes_cancelled;
+ RRDDIM *rd_pgc_waste_insert_spins;
+ RRDDIM *rd_pgc_waste_evict_spins;
+ RRDDIM *rd_pgc_waste_release_spins;
+ RRDDIM *rd_pgc_waste_acquire_spins;
+ RRDDIM *rd_pgc_waste_delete_spins;
+ RRDDIM *rd_pgc_waste_flush_spins;
+
+};
+
+static void dbengine2_cache_statistics_charts(struct dbengine2_cache_pointers *ptrs, struct pgc_statistics *pgc_stats, struct pgc_statistics *pgc_stats_old __maybe_unused, const char *name, int priority) {
+
+ {
+ if (unlikely(!ptrs->st_cache_hit_ratio)) {
+ BUFFER *id = buffer_create(100, NULL);
+ buffer_sprintf(id, "dbengine_%s_cache_hit_ratio", name);
+
+ BUFFER *family = buffer_create(100, NULL);
+ buffer_sprintf(family, "dbengine %s cache", name);
+
+ BUFFER *title = buffer_create(100, NULL);
+ buffer_sprintf(title, "Netdata %s Cache Hit Ratio", name);
+
+ ptrs->st_cache_hit_ratio = rrdset_create_localhost(
+ "netdata",
+ buffer_tostring(id),
+ NULL,
+ buffer_tostring(family),
+ NULL,
+ buffer_tostring(title),
+ "%",
+ "netdata",
+ "stats",
+ priority,
+ localhost->rrd_update_every,
+ RRDSET_TYPE_LINE);
+
+ ptrs->rd_hit_ratio_closest = rrddim_add(ptrs->st_cache_hit_ratio, "closest", NULL, 1, 10000, RRD_ALGORITHM_ABSOLUTE);
+ ptrs->rd_hit_ratio_exact = rrddim_add(ptrs->st_cache_hit_ratio, "exact", NULL, 1, 10000, RRD_ALGORITHM_ABSOLUTE);
+
+ buffer_free(id);
+ buffer_free(family);
+ buffer_free(title);
+ priority++;
+ }
+
+ size_t closest_percent = 100 * 10000;
+ if(pgc_stats->searches_closest > pgc_stats_old->searches_closest)
+ closest_percent = (pgc_stats->searches_closest_hits - pgc_stats_old->searches_closest_hits) * 100 * 10000 / (pgc_stats->searches_closest - pgc_stats_old->searches_closest);
+
+ size_t exact_percent = 100 * 10000;
+ if(pgc_stats->searches_exact > pgc_stats_old->searches_exact)
+ exact_percent = (pgc_stats->searches_exact_hits - pgc_stats_old->searches_exact_hits) * 100 * 10000 / (pgc_stats->searches_exact - pgc_stats_old->searches_exact);
+
+ rrddim_set_by_pointer(ptrs->st_cache_hit_ratio, ptrs->rd_hit_ratio_closest, (collected_number)closest_percent);
+ rrddim_set_by_pointer(ptrs->st_cache_hit_ratio, ptrs->rd_hit_ratio_exact, (collected_number)exact_percent);
+
+ rrdset_done(ptrs->st_cache_hit_ratio);
+ }
+
+ {
+ if (unlikely(!ptrs->st_operations)) {
+ BUFFER *id = buffer_create(100, NULL);
+ buffer_sprintf(id, "dbengine_%s_cache_operations", name);
+
+ BUFFER *family = buffer_create(100, NULL);
+ buffer_sprintf(family, "dbengine %s cache", name);
+
+ BUFFER *title = buffer_create(100, NULL);
+ buffer_sprintf(title, "Netdata %s Cache Operations", name);
+
+ ptrs->st_operations = rrdset_create_localhost(
+ "netdata",
+ buffer_tostring(id),
+ NULL,
+ buffer_tostring(family),
+ NULL,
+ buffer_tostring(title),
+ "ops/s",
+ "netdata",
+ "stats",
+ priority,
+ localhost->rrd_update_every,
+ RRDSET_TYPE_LINE);
+
+ ptrs->rd_searches_closest = rrddim_add(ptrs->st_operations, "search closest", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ ptrs->rd_searches_exact = rrddim_add(ptrs->st_operations, "search exact", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ ptrs->rd_add_hot = rrddim_add(ptrs->st_operations, "add hot", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ ptrs->rd_add_clean = rrddim_add(ptrs->st_operations, "add clean", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ ptrs->rd_evictions = rrddim_add(ptrs->st_operations, "evictions", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ ptrs->rd_flushes = rrddim_add(ptrs->st_operations, "flushes", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ ptrs->rd_acquires = rrddim_add(ptrs->st_operations, "acquires", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ ptrs->rd_releases = rrddim_add(ptrs->st_operations, "releases", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ ptrs->rd_acquires_for_deletion = rrddim_add(ptrs->st_operations, "del acquires", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+
+ buffer_free(id);
+ buffer_free(family);
+ buffer_free(title);
+ priority++;
+ }
+
+ rrddim_set_by_pointer(ptrs->st_operations, ptrs->rd_searches_closest, (collected_number)pgc_stats->searches_closest);
+ rrddim_set_by_pointer(ptrs->st_operations, ptrs->rd_searches_exact, (collected_number)pgc_stats->searches_exact);
+ rrddim_set_by_pointer(ptrs->st_operations, ptrs->rd_add_hot, (collected_number)pgc_stats->queues.hot.added_entries);
+ rrddim_set_by_pointer(ptrs->st_operations, ptrs->rd_add_clean, (collected_number)(pgc_stats->added_entries - pgc_stats->queues.hot.added_entries));
+ rrddim_set_by_pointer(ptrs->st_operations, ptrs->rd_evictions, (collected_number)pgc_stats->queues.clean.removed_entries);
+ rrddim_set_by_pointer(ptrs->st_operations, ptrs->rd_flushes, (collected_number)pgc_stats->flushes_completed);
+ rrddim_set_by_pointer(ptrs->st_operations, ptrs->rd_acquires, (collected_number)pgc_stats->acquires);
+ rrddim_set_by_pointer(ptrs->st_operations, ptrs->rd_releases, (collected_number)pgc_stats->releases);
+ rrddim_set_by_pointer(ptrs->st_operations, ptrs->rd_acquires_for_deletion, (collected_number)pgc_stats->acquires_for_deletion);
+
+ rrdset_done(ptrs->st_operations);
+ }
+
+ {
+ if (unlikely(!ptrs->st_pgc_memory)) {
+ BUFFER *id = buffer_create(100, NULL);
+ buffer_sprintf(id, "dbengine_%s_cache_memory", name);
+
+ BUFFER *family = buffer_create(100, NULL);
+ buffer_sprintf(family, "dbengine %s cache", name);
+
+ BUFFER *title = buffer_create(100, NULL);
+ buffer_sprintf(title, "Netdata %s Cache Memory", name);
+
+ ptrs->st_pgc_memory = rrdset_create_localhost(
+ "netdata",
+ buffer_tostring(id),
+ NULL,
+ buffer_tostring(family),
+ NULL,
+ buffer_tostring(title),
+ "bytes",
+ "netdata",
+ "stats",
+ priority,
+ localhost->rrd_update_every,
+ RRDSET_TYPE_STACKED);
+
+ ptrs->rd_pgc_memory_free = rrddim_add(ptrs->st_pgc_memory, "free", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
+ ptrs->rd_pgc_memory_hot = rrddim_add(ptrs->st_pgc_memory, "hot", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
+ ptrs->rd_pgc_memory_dirty = rrddim_add(ptrs->st_pgc_memory, "dirty", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
+ ptrs->rd_pgc_memory_clean = rrddim_add(ptrs->st_pgc_memory, "clean", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
+ ptrs->rd_pgc_memory_index = rrddim_add(ptrs->st_pgc_memory, "index", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
+ ptrs->rd_pgc_memory_evicting = rrddim_add(ptrs->st_pgc_memory, "evicting", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
+ ptrs->rd_pgc_memory_flushing = rrddim_add(ptrs->st_pgc_memory, "flushing", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
+
+ buffer_free(id);
+ buffer_free(family);
+ buffer_free(title);
+ priority++;
+ }
+
+ collected_number free = (pgc_stats->current_cache_size > pgc_stats->wanted_cache_size) ? 0 :
+ (collected_number)(pgc_stats->wanted_cache_size - pgc_stats->current_cache_size);
+
+ rrddim_set_by_pointer(ptrs->st_pgc_memory, ptrs->rd_pgc_memory_free, free);
+ rrddim_set_by_pointer(ptrs->st_pgc_memory, ptrs->rd_pgc_memory_hot, (collected_number)pgc_stats->queues.hot.size);
+ rrddim_set_by_pointer(ptrs->st_pgc_memory, ptrs->rd_pgc_memory_dirty, (collected_number)pgc_stats->queues.dirty.size);
+ rrddim_set_by_pointer(ptrs->st_pgc_memory, ptrs->rd_pgc_memory_clean, (collected_number)pgc_stats->queues.clean.size);
+ rrddim_set_by_pointer(ptrs->st_pgc_memory, ptrs->rd_pgc_memory_evicting, (collected_number)pgc_stats->evicting_size);
+ rrddim_set_by_pointer(ptrs->st_pgc_memory, ptrs->rd_pgc_memory_flushing, (collected_number)pgc_stats->flushing_size);
+ rrddim_set_by_pointer(ptrs->st_pgc_memory, ptrs->rd_pgc_memory_index,
+ (collected_number)(pgc_stats->size - pgc_stats->queues.clean.size - pgc_stats->queues.hot.size - pgc_stats->queues.dirty.size - pgc_stats->evicting_size - pgc_stats->flushing_size));
+
+ rrdset_done(ptrs->st_pgc_memory);
+ }
+
+ {
+ if (unlikely(!ptrs->st_pgc_tm)) {
+ BUFFER *id = buffer_create(100, NULL);
+ buffer_sprintf(id, "dbengine_%s_target_memory", name);
+
+ BUFFER *family = buffer_create(100, NULL);
+ buffer_sprintf(family, "dbengine %s cache", name);
+
+ BUFFER *title = buffer_create(100, NULL);
+ buffer_sprintf(title, "Netdata %s Target Cache Memory", name);
+
+ ptrs->st_pgc_tm = rrdset_create_localhost(
+ "netdata",
+ buffer_tostring(id),
+ NULL,
+ buffer_tostring(family),
+ NULL,
+ buffer_tostring(title),
+ "bytes",
+ "netdata",
+ "stats",
+ priority,
+ localhost->rrd_update_every,
+ RRDSET_TYPE_LINE);
+
+ ptrs->rd_pgc_tm_current = rrddim_add(ptrs->st_pgc_tm, "current", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
+ ptrs->rd_pgc_tm_wanted = rrddim_add(ptrs->st_pgc_tm, "wanted", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
+ ptrs->rd_pgc_tm_referenced = rrddim_add(ptrs->st_pgc_tm, "referenced", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
+ ptrs->rd_pgc_tm_hot_max = rrddim_add(ptrs->st_pgc_tm, "hot max", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
+ ptrs->rd_pgc_tm_dirty_max = rrddim_add(ptrs->st_pgc_tm, "dirty max", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
+ ptrs->rd_pgc_tm_hot = rrddim_add(ptrs->st_pgc_tm, "hot", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
+ ptrs->rd_pgc_tm_dirty = rrddim_add(ptrs->st_pgc_tm, "dirty", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
+
+ buffer_free(id);
+ buffer_free(family);
+ buffer_free(title);
+ priority++;
+ }
+
+ rrddim_set_by_pointer(ptrs->st_pgc_tm, ptrs->rd_pgc_tm_current, (collected_number)pgc_stats->current_cache_size);
+ rrddim_set_by_pointer(ptrs->st_pgc_tm, ptrs->rd_pgc_tm_wanted, (collected_number)pgc_stats->wanted_cache_size);
+ rrddim_set_by_pointer(ptrs->st_pgc_tm, ptrs->rd_pgc_tm_referenced, (collected_number)pgc_stats->referenced_size);
+ rrddim_set_by_pointer(ptrs->st_pgc_tm, ptrs->rd_pgc_tm_hot_max, (collected_number)pgc_stats->queues.hot.max_size);
+ rrddim_set_by_pointer(ptrs->st_pgc_tm, ptrs->rd_pgc_tm_dirty_max, (collected_number)pgc_stats->queues.dirty.max_size);
+ rrddim_set_by_pointer(ptrs->st_pgc_tm, ptrs->rd_pgc_tm_hot, (collected_number)pgc_stats->queues.hot.size);
+ rrddim_set_by_pointer(ptrs->st_pgc_tm, ptrs->rd_pgc_tm_dirty, (collected_number)pgc_stats->queues.dirty.size);
+
+ rrdset_done(ptrs->st_pgc_tm);
+ }
+
+ {
+ if (unlikely(!ptrs->st_pgc_pages)) {
+ BUFFER *id = buffer_create(100, NULL);
+ buffer_sprintf(id, "dbengine_%s_cache_pages", name);
+
+ BUFFER *family = buffer_create(100, NULL);
+ buffer_sprintf(family, "dbengine %s cache", name);
+
+ BUFFER *title = buffer_create(100, NULL);
+ buffer_sprintf(title, "Netdata %s Cache Pages", name);
+
+ ptrs->st_pgc_pages = rrdset_create_localhost(
+ "netdata",
+ buffer_tostring(id),
+ NULL,
+ buffer_tostring(family),
+ NULL,
+ buffer_tostring(title),
+ "pages",
+ "netdata",
+ "stats",
+ priority,
+ localhost->rrd_update_every,
+ RRDSET_TYPE_LINE);
+
+ ptrs->rd_pgc_pages_clean = rrddim_add(ptrs->st_pgc_pages, "clean", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
+ ptrs->rd_pgc_pages_hot = rrddim_add(ptrs->st_pgc_pages, "hot", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
+ ptrs->rd_pgc_pages_dirty = rrddim_add(ptrs->st_pgc_pages, "dirty", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
+ ptrs->rd_pgc_pages_referenced = rrddim_add(ptrs->st_pgc_pages, "referenced", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
+
+ buffer_free(id);
+ buffer_free(family);
+ buffer_free(title);
+ priority++;
+ }
+
+ rrddim_set_by_pointer(ptrs->st_pgc_pages, ptrs->rd_pgc_pages_clean, (collected_number)pgc_stats->queues.clean.entries);
+ rrddim_set_by_pointer(ptrs->st_pgc_pages, ptrs->rd_pgc_pages_hot, (collected_number)pgc_stats->queues.hot.entries);
+ rrddim_set_by_pointer(ptrs->st_pgc_pages, ptrs->rd_pgc_pages_dirty, (collected_number)pgc_stats->queues.dirty.entries);
+ rrddim_set_by_pointer(ptrs->st_pgc_pages, ptrs->rd_pgc_pages_referenced, (collected_number)pgc_stats->referenced_entries);
+
+ rrdset_done(ptrs->st_pgc_pages);
+ }
+
+ {
+ if (unlikely(!ptrs->st_pgc_memory_changes)) {
+ BUFFER *id = buffer_create(100, NULL);
+ buffer_sprintf(id, "dbengine_%s_cache_memory_changes", name);
+
+ BUFFER *family = buffer_create(100, NULL);
+ buffer_sprintf(family, "dbengine %s cache", name);
+
+ BUFFER *title = buffer_create(100, NULL);
+ buffer_sprintf(title, "Netdata %s Cache Memory Changes", name);
+
+ ptrs->st_pgc_memory_changes = rrdset_create_localhost(
+ "netdata",
+ buffer_tostring(id),
+ NULL,
+ buffer_tostring(family),
+ NULL,
+ buffer_tostring(title),
+ "bytes/s",
+ "netdata",
+ "stats",
+ priority,
+ localhost->rrd_update_every,
+ RRDSET_TYPE_AREA);
+
+ ptrs->rd_pgc_memory_new_clean = rrddim_add(ptrs->st_pgc_memory_changes, "new clean", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ ptrs->rd_pgc_memory_clean_evictions = rrddim_add(ptrs->st_pgc_memory_changes, "evictions", NULL, -1, 1, RRD_ALGORITHM_INCREMENTAL);
+ ptrs->rd_pgc_memory_new_hot = rrddim_add(ptrs->st_pgc_memory_changes, "new hot", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+
+ buffer_free(id);
+ buffer_free(family);
+ buffer_free(title);
+ priority++;
+ }
+
+ rrddim_set_by_pointer(ptrs->st_pgc_memory_changes, ptrs->rd_pgc_memory_new_clean, (collected_number)(pgc_stats->added_size - pgc_stats->queues.hot.added_size));
+ rrddim_set_by_pointer(ptrs->st_pgc_memory_changes, ptrs->rd_pgc_memory_clean_evictions, (collected_number)pgc_stats->queues.clean.removed_size);
+ rrddim_set_by_pointer(ptrs->st_pgc_memory_changes, ptrs->rd_pgc_memory_new_hot, (collected_number)pgc_stats->queues.hot.added_size);
+
+ rrdset_done(ptrs->st_pgc_memory_changes);
+ }
+
+ {
+ if (unlikely(!ptrs->st_pgc_memory_migrations)) {
+ BUFFER *id = buffer_create(100, NULL);
+ buffer_sprintf(id, "dbengine_%s_cache_memory_migrations", name);
+
+ BUFFER *family = buffer_create(100, NULL);
+ buffer_sprintf(family, "dbengine %s cache", name);
+
+ BUFFER *title = buffer_create(100, NULL);
+ buffer_sprintf(title, "Netdata %s Cache Memory Migrations", name);
+
+ ptrs->st_pgc_memory_migrations = rrdset_create_localhost(
+ "netdata",
+ buffer_tostring(id),
+ NULL,
+ buffer_tostring(family),
+ NULL,
+ buffer_tostring(title),
+ "bytes/s",
+ "netdata",
+ "stats",
+ priority,
+ localhost->rrd_update_every,
+ RRDSET_TYPE_AREA);
+
+ ptrs->rd_pgc_memory_dirty_to_clean = rrddim_add(ptrs->st_pgc_memory_migrations, "dirty to clean", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ ptrs->rd_pgc_memory_hot_to_dirty = rrddim_add(ptrs->st_pgc_memory_migrations, "hot to dirty", NULL, -1, 1, RRD_ALGORITHM_INCREMENTAL);
+
+ buffer_free(id);
+ buffer_free(family);
+ buffer_free(title);
+ priority++;
+ }
+
+ rrddim_set_by_pointer(ptrs->st_pgc_memory_migrations, ptrs->rd_pgc_memory_dirty_to_clean, (collected_number)pgc_stats->queues.dirty.removed_size);
+ rrddim_set_by_pointer(ptrs->st_pgc_memory_migrations, ptrs->rd_pgc_memory_hot_to_dirty, (collected_number)pgc_stats->queues.dirty.added_size);
+
+ rrdset_done(ptrs->st_pgc_memory_migrations);
+ }
+
+ {
+ if (unlikely(!ptrs->st_pgc_memory_events)) {
+ BUFFER *id = buffer_create(100, NULL);
+ buffer_sprintf(id, "dbengine_%s_cache_events", name);
+
+ BUFFER *family = buffer_create(100, NULL);
+ buffer_sprintf(family, "dbengine %s cache", name);
+
+ BUFFER *title = buffer_create(100, NULL);
+ buffer_sprintf(title, "Netdata %s Cache Events", name);
+
+ ptrs->st_pgc_memory_events = rrdset_create_localhost(
+ "netdata",
+ buffer_tostring(id),
+ NULL,
+ buffer_tostring(family),
+ NULL,
+ buffer_tostring(title),
+ "events/s",
+ "netdata",
+ "stats",
+ priority,
+ localhost->rrd_update_every,
+ RRDSET_TYPE_AREA);
+
+ ptrs->rd_pgc_memory_evictions_aggressive = rrddim_add(ptrs->st_pgc_memory_events, "evictions aggressive", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ ptrs->rd_pgc_memory_evictions_critical = rrddim_add(ptrs->st_pgc_memory_events, "evictions critical", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ ptrs->rd_pgc_memory_flushes_critical = rrddim_add(ptrs->st_pgc_memory_events, "flushes critical", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+
+ buffer_free(id);
+ buffer_free(family);
+ buffer_free(title);
+ priority++;
+ }
+
+ rrddim_set_by_pointer(ptrs->st_pgc_memory_events, ptrs->rd_pgc_memory_evictions_aggressive, (collected_number)pgc_stats->events_cache_needs_space_aggressively);
+ rrddim_set_by_pointer(ptrs->st_pgc_memory_events, ptrs->rd_pgc_memory_evictions_critical, (collected_number)pgc_stats->events_cache_under_severe_pressure);
+ rrddim_set_by_pointer(ptrs->st_pgc_memory_events, ptrs->rd_pgc_memory_flushes_critical, (collected_number)pgc_stats->events_flush_critical);
+
+ rrdset_done(ptrs->st_pgc_memory_events);
+ }
+
+ {
+ if (unlikely(!ptrs->st_pgc_waste)) {
+ BUFFER *id = buffer_create(100, NULL);
+ buffer_sprintf(id, "dbengine_%s_waste_events", name);
+
+ BUFFER *family = buffer_create(100, NULL);
+ buffer_sprintf(family, "dbengine %s cache", name);
+
+ BUFFER *title = buffer_create(100, NULL);
+ buffer_sprintf(title, "Netdata %s Waste Events", name);
+
+ ptrs->st_pgc_waste = rrdset_create_localhost(
+ "netdata",
+ buffer_tostring(id),
+ NULL,
+ buffer_tostring(family),
+ NULL,
+ buffer_tostring(title),
+ "events/s",
+ "netdata",
+ "stats",
+ priority,
+ localhost->rrd_update_every,
+ RRDSET_TYPE_LINE);
+
+ ptrs->rd_pgc_waste_evictions_skipped = rrddim_add(ptrs->st_pgc_waste, "evictions skipped", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ ptrs->rd_pgc_waste_flushes_cancelled = rrddim_add(ptrs->st_pgc_waste, "flushes cancelled", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ ptrs->rd_pgc_waste_acquire_spins = rrddim_add(ptrs->st_pgc_waste, "acquire spins", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ ptrs->rd_pgc_waste_release_spins = rrddim_add(ptrs->st_pgc_waste, "release spins", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ ptrs->rd_pgc_waste_insert_spins = rrddim_add(ptrs->st_pgc_waste, "insert spins", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ ptrs->rd_pgc_waste_delete_spins = rrddim_add(ptrs->st_pgc_waste, "delete spins", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ ptrs->rd_pgc_waste_evict_spins = rrddim_add(ptrs->st_pgc_waste, "evict spins", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ ptrs->rd_pgc_waste_flush_spins = rrddim_add(ptrs->st_pgc_waste, "flush spins", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+
+ buffer_free(id);
+ buffer_free(family);
+ buffer_free(title);
+ priority++;
+ }
+
+ rrddim_set_by_pointer(ptrs->st_pgc_waste, ptrs->rd_pgc_waste_evictions_skipped, (collected_number)pgc_stats->evict_skipped);
+ rrddim_set_by_pointer(ptrs->st_pgc_waste, ptrs->rd_pgc_waste_flushes_cancelled, (collected_number)pgc_stats->flushes_cancelled);
+ rrddim_set_by_pointer(ptrs->st_pgc_waste, ptrs->rd_pgc_waste_acquire_spins, (collected_number)pgc_stats->acquire_spins);
+ rrddim_set_by_pointer(ptrs->st_pgc_waste, ptrs->rd_pgc_waste_release_spins, (collected_number)pgc_stats->release_spins);
+ rrddim_set_by_pointer(ptrs->st_pgc_waste, ptrs->rd_pgc_waste_insert_spins, (collected_number)pgc_stats->insert_spins);
+ rrddim_set_by_pointer(ptrs->st_pgc_waste, ptrs->rd_pgc_waste_delete_spins, (collected_number)pgc_stats->delete_spins);
+ rrddim_set_by_pointer(ptrs->st_pgc_waste, ptrs->rd_pgc_waste_evict_spins, (collected_number)pgc_stats->evict_spins);
+ rrddim_set_by_pointer(ptrs->st_pgc_waste, ptrs->rd_pgc_waste_flush_spins, (collected_number)pgc_stats->flush_spins);
+
+ rrdset_done(ptrs->st_pgc_waste);
+ }
+
+ {
+ if (unlikely(!ptrs->st_pgc_workers)) {
+ BUFFER *id = buffer_create(100, NULL);
+ buffer_sprintf(id, "dbengine_%s_cache_workers", name);
+
+ BUFFER *family = buffer_create(100, NULL);
+ buffer_sprintf(family, "dbengine %s cache", name);
+
+ BUFFER *title = buffer_create(100, NULL);
+ buffer_sprintf(title, "Netdata %s Cache Workers", name);
+
+ ptrs->st_pgc_workers = rrdset_create_localhost(
+ "netdata",
+ buffer_tostring(id),
+ NULL,
+ buffer_tostring(family),
+ NULL,
+ buffer_tostring(title),
+ "workers",
+ "netdata",
+ "stats",
+ priority,
+ localhost->rrd_update_every,
+ RRDSET_TYPE_LINE);
+
+ ptrs->rd_pgc_workers_searchers = rrddim_add(ptrs->st_pgc_workers, "searchers", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
+ ptrs->rd_pgc_workers_adders = rrddim_add(ptrs->st_pgc_workers, "adders", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
+ ptrs->rd_pgc_workers_evictors = rrddim_add(ptrs->st_pgc_workers, "evictors", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
+ ptrs->rd_pgc_workers_flushers = rrddim_add(ptrs->st_pgc_workers, "flushers", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
+ ptrs->rd_pgc_workers_hot2dirty = rrddim_add(ptrs->st_pgc_workers, "hot2dirty", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
+ ptrs->rd_pgc_workers_jv2_flushers = rrddim_add(ptrs->st_pgc_workers, "jv2 flushers", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
+
+ buffer_free(id);
+ buffer_free(family);
+ buffer_free(title);
+ priority++;
+ }
+
+ rrddim_set_by_pointer(ptrs->st_pgc_workers, ptrs->rd_pgc_workers_searchers, (collected_number)pgc_stats->workers_search);
+ rrddim_set_by_pointer(ptrs->st_pgc_workers, ptrs->rd_pgc_workers_adders, (collected_number)pgc_stats->workers_add);
+ rrddim_set_by_pointer(ptrs->st_pgc_workers, ptrs->rd_pgc_workers_evictors, (collected_number)pgc_stats->workers_evict);
+ rrddim_set_by_pointer(ptrs->st_pgc_workers, ptrs->rd_pgc_workers_flushers, (collected_number)pgc_stats->workers_flush);
+ rrddim_set_by_pointer(ptrs->st_pgc_workers, ptrs->rd_pgc_workers_hot2dirty, (collected_number)pgc_stats->workers_hot2dirty);
+ rrddim_set_by_pointer(ptrs->st_pgc_workers, ptrs->rd_pgc_workers_jv2_flushers, (collected_number)pgc_stats->workers_jv2_flush);
+
+ rrdset_done(ptrs->st_pgc_workers);
+ }
+}
+
+
+static void dbengine2_statistics_charts(void) {
+ if(!main_cache || !main_mrg)
+ return;
+
+ static struct dbengine2_cache_pointers main_cache_ptrs = {}, open_cache_ptrs = {}, extent_cache_ptrs = {};
+ static struct rrdeng_cache_efficiency_stats cache_efficiency_stats = {}, cache_efficiency_stats_old = {};
+ static struct pgc_statistics pgc_main_stats = {}, pgc_main_stats_old = {}; (void)pgc_main_stats_old;
+ static struct pgc_statistics pgc_open_stats = {}, pgc_open_stats_old = {}; (void)pgc_open_stats_old;
+ static struct pgc_statistics pgc_extent_stats = {}, pgc_extent_stats_old = {}; (void)pgc_extent_stats_old;
+ static struct mrg_statistics mrg_stats = {}, mrg_stats_old = {}; (void)mrg_stats_old;
+
+ pgc_main_stats_old = pgc_main_stats;
+ pgc_main_stats = pgc_get_statistics(main_cache);
+ dbengine2_cache_statistics_charts(&main_cache_ptrs, &pgc_main_stats, &pgc_main_stats_old, "main", 135100);
+
+ pgc_open_stats_old = pgc_open_stats;
+ pgc_open_stats = pgc_get_statistics(open_cache);
+ dbengine2_cache_statistics_charts(&open_cache_ptrs, &pgc_open_stats, &pgc_open_stats_old, "open", 135200);
+
+ pgc_extent_stats_old = pgc_extent_stats;
+ pgc_extent_stats = pgc_get_statistics(extent_cache);
+ dbengine2_cache_statistics_charts(&extent_cache_ptrs, &pgc_extent_stats, &pgc_extent_stats_old, "extent", 135300);
+
+ cache_efficiency_stats_old = cache_efficiency_stats;
+ cache_efficiency_stats = rrdeng_get_cache_efficiency_stats();
+
+ mrg_stats_old = mrg_stats;
+ mrg_stats = mrg_get_statistics(main_mrg);
+
+ struct rrdeng_buffer_sizes buffers = rrdeng_get_buffer_sizes();
+ size_t buffers_total_size = buffers.handles + buffers.xt_buf + buffers.xt_io + buffers.pdc + buffers.descriptors +
+ buffers.opcodes + buffers.wal + buffers.workers + buffers.epdl + buffers.deol + buffers.pd + buffers.pgc + buffers.mrg;
+
+#ifdef PDC_USE_JULYL
+ buffers_total_size += buffers.julyl;
+#endif
+
+ dbengine_total_memory = pgc_main_stats.size + pgc_open_stats.size + pgc_extent_stats.size + mrg_stats.size + buffers_total_size;
+
+ size_t priority = 135000;
+
+ {
+ static RRDSET *st_pgc_memory = NULL;
+ static RRDDIM *rd_pgc_memory_main = NULL;
+ static RRDDIM *rd_pgc_memory_open = NULL; // open journal memory
+ static RRDDIM *rd_pgc_memory_extent = NULL; // extent compresses cache memory
+ static RRDDIM *rd_pgc_memory_metrics = NULL; // metric registry memory
+ static RRDDIM *rd_pgc_memory_buffers = NULL;
+
+ if (unlikely(!st_pgc_memory)) {
+ st_pgc_memory = rrdset_create_localhost(
+ "netdata",
+ "dbengine_memory",
+ NULL,
+ "dbengine memory",
+ NULL,
+ "Netdata DB Memory",
+ "bytes",
+ "netdata",
+ "stats",
+ priority,
+ localhost->rrd_update_every,
+ RRDSET_TYPE_STACKED);
+
+ rd_pgc_memory_main = rrddim_add(st_pgc_memory, "main cache", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
+ rd_pgc_memory_open = rrddim_add(st_pgc_memory, "open cache", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
+ rd_pgc_memory_extent = rrddim_add(st_pgc_memory, "extent cache", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
+ rd_pgc_memory_metrics = rrddim_add(st_pgc_memory, "metrics registry", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
+ rd_pgc_memory_buffers = rrddim_add(st_pgc_memory, "buffers", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
+ }
+ priority++;
+
+
+ rrddim_set_by_pointer(st_pgc_memory, rd_pgc_memory_main, (collected_number)pgc_main_stats.size);
+ rrddim_set_by_pointer(st_pgc_memory, rd_pgc_memory_open, (collected_number)pgc_open_stats.size);
+ rrddim_set_by_pointer(st_pgc_memory, rd_pgc_memory_extent, (collected_number)pgc_extent_stats.size);
+ rrddim_set_by_pointer(st_pgc_memory, rd_pgc_memory_metrics, (collected_number)mrg_stats.size);
+ rrddim_set_by_pointer(st_pgc_memory, rd_pgc_memory_buffers, (collected_number)buffers_total_size);
+
+ rrdset_done(st_pgc_memory);
+ }
+
+ {
+ static RRDSET *st_pgc_buffers = NULL;
+ static RRDDIM *rd_pgc_buffers_pgc = NULL;
+ static RRDDIM *rd_pgc_buffers_mrg = NULL;
+ static RRDDIM *rd_pgc_buffers_opcodes = NULL;
+ static RRDDIM *rd_pgc_buffers_handles = NULL;
+ static RRDDIM *rd_pgc_buffers_descriptors = NULL;
+ static RRDDIM *rd_pgc_buffers_wal = NULL;
+ static RRDDIM *rd_pgc_buffers_workers = NULL;
+ static RRDDIM *rd_pgc_buffers_pdc = NULL;
+ static RRDDIM *rd_pgc_buffers_xt_io = NULL;
+ static RRDDIM *rd_pgc_buffers_xt_buf = NULL;
+ static RRDDIM *rd_pgc_buffers_epdl = NULL;
+ static RRDDIM *rd_pgc_buffers_deol = NULL;
+ static RRDDIM *rd_pgc_buffers_pd = NULL;
+#ifdef PDC_USE_JULYL
+ static RRDDIM *rd_pgc_buffers_julyl = NULL;
+#endif
+
+ if (unlikely(!st_pgc_buffers)) {
+ st_pgc_buffers = rrdset_create_localhost(
+ "netdata",
+ "dbengine_buffers",
+ NULL,
+ "dbengine memory",
+ NULL,
+ "Netdata DB Buffers",
+ "bytes",
+ "netdata",
+ "stats",
+ priority,
+ localhost->rrd_update_every,
+ RRDSET_TYPE_STACKED);
+
+ rd_pgc_buffers_pgc = rrddim_add(st_pgc_buffers, "pgc", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
+ rd_pgc_buffers_mrg = rrddim_add(st_pgc_buffers, "mrg", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
+ rd_pgc_buffers_opcodes = rrddim_add(st_pgc_buffers, "opcodes", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
+ rd_pgc_buffers_handles = rrddim_add(st_pgc_buffers, "query handles", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
+ rd_pgc_buffers_descriptors = rrddim_add(st_pgc_buffers, "descriptors", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
+ rd_pgc_buffers_wal = rrddim_add(st_pgc_buffers, "wal", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
+ rd_pgc_buffers_workers = rrddim_add(st_pgc_buffers, "workers", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
+ rd_pgc_buffers_pdc = rrddim_add(st_pgc_buffers, "pdc", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
+ rd_pgc_buffers_pd = rrddim_add(st_pgc_buffers, "pd", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
+ rd_pgc_buffers_xt_io = rrddim_add(st_pgc_buffers, "extent io", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
+ rd_pgc_buffers_xt_buf = rrddim_add(st_pgc_buffers, "extent buffers", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
+ rd_pgc_buffers_epdl = rrddim_add(st_pgc_buffers, "epdl", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
+ rd_pgc_buffers_deol = rrddim_add(st_pgc_buffers, "deol", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
+#ifdef PDC_USE_JULYL
+ rd_pgc_buffers_julyl = rrddim_add(st_pgc_buffers, "julyl", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
+#endif
+ }
+ priority++;
+
+ rrddim_set_by_pointer(st_pgc_buffers, rd_pgc_buffers_pgc, (collected_number)buffers.pgc);
+ rrddim_set_by_pointer(st_pgc_buffers, rd_pgc_buffers_mrg, (collected_number)buffers.mrg);
+ rrddim_set_by_pointer(st_pgc_buffers, rd_pgc_buffers_opcodes, (collected_number)buffers.opcodes);
+ rrddim_set_by_pointer(st_pgc_buffers, rd_pgc_buffers_handles, (collected_number)buffers.handles);
+ rrddim_set_by_pointer(st_pgc_buffers, rd_pgc_buffers_descriptors, (collected_number)buffers.descriptors);
+ rrddim_set_by_pointer(st_pgc_buffers, rd_pgc_buffers_wal, (collected_number)buffers.wal);
+ rrddim_set_by_pointer(st_pgc_buffers, rd_pgc_buffers_workers, (collected_number)buffers.workers);
+ rrddim_set_by_pointer(st_pgc_buffers, rd_pgc_buffers_pdc, (collected_number)buffers.pdc);
+ rrddim_set_by_pointer(st_pgc_buffers, rd_pgc_buffers_pd, (collected_number)buffers.pd);
+ rrddim_set_by_pointer(st_pgc_buffers, rd_pgc_buffers_xt_io, (collected_number)buffers.xt_io);
+ rrddim_set_by_pointer(st_pgc_buffers, rd_pgc_buffers_xt_buf, (collected_number)buffers.xt_buf);
+ rrddim_set_by_pointer(st_pgc_buffers, rd_pgc_buffers_epdl, (collected_number)buffers.epdl);
+ rrddim_set_by_pointer(st_pgc_buffers, rd_pgc_buffers_deol, (collected_number)buffers.deol);
+#ifdef PDC_USE_JULYL
+ rrddim_set_by_pointer(st_pgc_buffers, rd_pgc_buffers_julyl, (collected_number)buffers.julyl);
+#endif
+
+ rrdset_done(st_pgc_buffers);
+ }
+
+#ifdef PDC_USE_JULYL
+ {
+ static RRDSET *st_julyl_moved = NULL;
+ static RRDDIM *rd_julyl_moved = NULL;
+
+ if (unlikely(!st_julyl_moved)) {
+ st_julyl_moved = rrdset_create_localhost(
+ "netdata",
+ "dbengine_julyl_moved",
+ NULL,
+ "dbengine memory",
+ NULL,
+ "Netdata JulyL Memory Moved",
+ "bytes/s",
+ "netdata",
+ "stats",
+ priority,
+ localhost->rrd_update_every,
+ RRDSET_TYPE_AREA);
+
+ rd_julyl_moved = rrddim_add(st_julyl_moved, "moved", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ }
+ priority++;
+
+ rrddim_set_by_pointer(st_julyl_moved, rd_julyl_moved, (collected_number)julyl_bytes_moved());
+
+ rrdset_done(st_julyl_moved);
+ }
+#endif
+
+ {
+ static RRDSET *st_mrg_metrics = NULL;
+ static RRDDIM *rd_mrg_metrics = NULL;
+ static RRDDIM *rd_mrg_acquired = NULL;
+ static RRDDIM *rd_mrg_collected = NULL;
+ static RRDDIM *rd_mrg_with_retention = NULL;
+ static RRDDIM *rd_mrg_without_retention = NULL;
+ static RRDDIM *rd_mrg_multiple_writers = NULL;
+
+ if (unlikely(!st_mrg_metrics)) {
+ st_mrg_metrics = rrdset_create_localhost(
+ "netdata",
+ "dbengine_metrics",
+ NULL,
+ "dbengine metrics",
+ NULL,
+ "Netdata Metrics in Metrics Registry",
+ "metrics",
+ "netdata",
+ "stats",
+ priority,
+ localhost->rrd_update_every,
+ RRDSET_TYPE_LINE);
+
+ rd_mrg_metrics = rrddim_add(st_mrg_metrics, "all", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
+ rd_mrg_acquired = rrddim_add(st_mrg_metrics, "acquired", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
+ rd_mrg_collected = rrddim_add(st_mrg_metrics, "collected", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
+ rd_mrg_with_retention = rrddim_add(st_mrg_metrics, "with retention", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
+ rd_mrg_without_retention = rrddim_add(st_mrg_metrics, "without retention", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
+ rd_mrg_multiple_writers = rrddim_add(st_mrg_metrics, "multi-collected", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
+ }
+ priority++;
+
+ rrddim_set_by_pointer(st_mrg_metrics, rd_mrg_metrics, (collected_number)mrg_stats.entries);
+ rrddim_set_by_pointer(st_mrg_metrics, rd_mrg_acquired, (collected_number)mrg_stats.entries_referenced);
+ rrddim_set_by_pointer(st_mrg_metrics, rd_mrg_collected, (collected_number)mrg_stats.writers);
+ rrddim_set_by_pointer(st_mrg_metrics, rd_mrg_with_retention, (collected_number)mrg_stats.entries_with_retention);
+ rrddim_set_by_pointer(st_mrg_metrics, rd_mrg_without_retention, (collected_number)mrg_stats.entries - (collected_number)mrg_stats.entries_with_retention);
+ rrddim_set_by_pointer(st_mrg_metrics, rd_mrg_multiple_writers, (collected_number)mrg_stats.writers_conflicts);
+
+ rrdset_done(st_mrg_metrics);
+ }
+
+ {
+ static RRDSET *st_mrg_ops = NULL;
+ static RRDDIM *rd_mrg_add = NULL;
+ static RRDDIM *rd_mrg_del = NULL;
+ static RRDDIM *rd_mrg_search = NULL;
+
+ if (unlikely(!st_mrg_ops)) {
+ st_mrg_ops = rrdset_create_localhost(
+ "netdata",
+ "dbengine_metrics_registry_operations",
+ NULL,
+ "dbengine metrics",
+ NULL,
+ "Netdata Metrics Registry Operations",
+ "metrics",
+ "netdata",
+ "stats",
+ priority,
+ localhost->rrd_update_every,
+ RRDSET_TYPE_LINE);
+
+ rd_mrg_add = rrddim_add(st_mrg_ops, "add", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_mrg_del = rrddim_add(st_mrg_ops, "delete", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_mrg_search = rrddim_add(st_mrg_ops, "search", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ }
+ priority++;
+
+ rrddim_set_by_pointer(st_mrg_ops, rd_mrg_add, (collected_number)mrg_stats.additions);
+ rrddim_set_by_pointer(st_mrg_ops, rd_mrg_del, (collected_number)mrg_stats.deletions);
+ rrddim_set_by_pointer(st_mrg_ops, rd_mrg_search, (collected_number)mrg_stats.search_hits + (collected_number)mrg_stats.search_misses);
+
+ rrdset_done(st_mrg_ops);
+ }
+
+ {
+ static RRDSET *st_mrg_references = NULL;
+ static RRDDIM *rd_mrg_references = NULL;
+
+ if (unlikely(!st_mrg_references)) {
+ st_mrg_references = rrdset_create_localhost(
+ "netdata",
+ "dbengine_metrics_registry_references",
+ NULL,
+ "dbengine metrics",
+ NULL,
+ "Netdata Metrics Registry References",
+ "references",
+ "netdata",
+ "stats",
+ priority,
+ localhost->rrd_update_every,
+ RRDSET_TYPE_LINE);
+
+ rd_mrg_references = rrddim_add(st_mrg_references, "references", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
+ }
+ priority++;
+
+ rrddim_set_by_pointer(st_mrg_references, rd_mrg_references, (collected_number)mrg_stats.current_references);
+
+ rrdset_done(st_mrg_references);
+ }
+
+ {
+ static RRDSET *st_cache_hit_ratio = NULL;
+ static RRDDIM *rd_hit_ratio = NULL;
+ static RRDDIM *rd_main_cache_hit_ratio = NULL;
+ static RRDDIM *rd_extent_cache_hit_ratio = NULL;
+ static RRDDIM *rd_extent_merge_hit_ratio = NULL;
+
+ if (unlikely(!st_cache_hit_ratio)) {
+ st_cache_hit_ratio = rrdset_create_localhost(
+ "netdata",
+ "dbengine_cache_hit_ratio",
+ NULL,
+ "dbengine query router",
+ NULL,
+ "Netdata Queries Cache Hit Ratio",
+ "%",
+ "netdata",
+ "stats",
+ priority,
+ localhost->rrd_update_every,
+ RRDSET_TYPE_LINE);
+
+ rd_hit_ratio = rrddim_add(st_cache_hit_ratio, "overall", NULL, 1, 10000, RRD_ALGORITHM_ABSOLUTE);
+ rd_main_cache_hit_ratio = rrddim_add(st_cache_hit_ratio, "main cache", NULL, 1, 10000, RRD_ALGORITHM_ABSOLUTE);
+ rd_extent_cache_hit_ratio = rrddim_add(st_cache_hit_ratio, "extent cache", NULL, 1, 10000, RRD_ALGORITHM_ABSOLUTE);
+ rd_extent_merge_hit_ratio = rrddim_add(st_cache_hit_ratio, "extent merge", NULL, 1, 10000, RRD_ALGORITHM_ABSOLUTE);
+ }
+ priority++;
+
+ size_t delta_pages_total = cache_efficiency_stats.pages_total - cache_efficiency_stats_old.pages_total;
+ size_t delta_pages_to_load_from_disk = cache_efficiency_stats.pages_to_load_from_disk - cache_efficiency_stats_old.pages_to_load_from_disk;
+ size_t delta_extents_loaded_from_disk = cache_efficiency_stats.extents_loaded_from_disk - cache_efficiency_stats_old.extents_loaded_from_disk;
+
+ size_t delta_pages_data_source_main_cache = cache_efficiency_stats.pages_data_source_main_cache - cache_efficiency_stats_old.pages_data_source_main_cache;
+ size_t delta_pages_pending_found_in_cache_at_pass4 = cache_efficiency_stats.pages_data_source_main_cache_at_pass4 - cache_efficiency_stats_old.pages_data_source_main_cache_at_pass4;
+
+ size_t delta_pages_data_source_extent_cache = cache_efficiency_stats.pages_data_source_extent_cache - cache_efficiency_stats_old.pages_data_source_extent_cache;
+ size_t delta_pages_load_extent_merged = cache_efficiency_stats.pages_load_extent_merged - cache_efficiency_stats_old.pages_load_extent_merged;
+
+ size_t pages_total_hit = delta_pages_total - delta_extents_loaded_from_disk;
+
+ static size_t overall_hit_ratio = 100;
+ size_t main_cache_hit_ratio = 0, extent_cache_hit_ratio = 0, extent_merge_hit_ratio = 0;
+ if(delta_pages_total) {
+ if(pages_total_hit > delta_pages_total)
+ pages_total_hit = delta_pages_total;
+
+ overall_hit_ratio = pages_total_hit * 100 * 10000 / delta_pages_total;
+
+ size_t delta_pages_main_cache = delta_pages_data_source_main_cache + delta_pages_pending_found_in_cache_at_pass4;
+ if(delta_pages_main_cache > delta_pages_total)
+ delta_pages_main_cache = delta_pages_total;
+
+ main_cache_hit_ratio = delta_pages_main_cache * 100 * 10000 / delta_pages_total;
+ }
+
+ if(delta_pages_to_load_from_disk) {
+ if(delta_pages_data_source_extent_cache > delta_pages_to_load_from_disk)
+ delta_pages_data_source_extent_cache = delta_pages_to_load_from_disk;
+
+ extent_cache_hit_ratio = delta_pages_data_source_extent_cache * 100 * 10000 / delta_pages_to_load_from_disk;
+
+ if(delta_pages_load_extent_merged > delta_pages_to_load_from_disk)
+ delta_pages_load_extent_merged = delta_pages_to_load_from_disk;
+
+ extent_merge_hit_ratio = delta_pages_load_extent_merged * 100 * 10000 / delta_pages_to_load_from_disk;
+ }
+
+ rrddim_set_by_pointer(st_cache_hit_ratio, rd_hit_ratio, (collected_number)overall_hit_ratio);
+ rrddim_set_by_pointer(st_cache_hit_ratio, rd_main_cache_hit_ratio, (collected_number)main_cache_hit_ratio);
+ rrddim_set_by_pointer(st_cache_hit_ratio, rd_extent_cache_hit_ratio, (collected_number)extent_cache_hit_ratio);
+ rrddim_set_by_pointer(st_cache_hit_ratio, rd_extent_merge_hit_ratio, (collected_number)extent_merge_hit_ratio);
+
+ rrdset_done(st_cache_hit_ratio);
+ }
+
+ {
+ static RRDSET *st_queries = NULL;
+ static RRDDIM *rd_total = NULL;
+ static RRDDIM *rd_open = NULL;
+ static RRDDIM *rd_jv2 = NULL;
+ static RRDDIM *rd_planned_with_gaps = NULL;
+ static RRDDIM *rd_executed_with_gaps = NULL;
+
+ if (unlikely(!st_queries)) {
+ st_queries = rrdset_create_localhost(
+ "netdata",
+ "dbengine_queries",
+ NULL,
+ "dbengine query router",
+ NULL,
+ "Netdata Queries",
+ "queries/s",
+ "netdata",
+ "stats",
+ priority,
+ localhost->rrd_update_every,
+ RRDSET_TYPE_LINE);
+
+ rd_total = rrddim_add(st_queries, "total", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_open = rrddim_add(st_queries, "open cache", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_jv2 = rrddim_add(st_queries, "journal v2", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_planned_with_gaps = rrddim_add(st_queries, "planned with gaps", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_executed_with_gaps = rrddim_add(st_queries, "executed with gaps", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ }
+ priority++;
+
+ rrddim_set_by_pointer(st_queries, rd_total, (collected_number)cache_efficiency_stats.queries);
+ rrddim_set_by_pointer(st_queries, rd_open, (collected_number)cache_efficiency_stats.queries_open);
+ rrddim_set_by_pointer(st_queries, rd_jv2, (collected_number)cache_efficiency_stats.queries_journal_v2);
+ rrddim_set_by_pointer(st_queries, rd_planned_with_gaps, (collected_number)cache_efficiency_stats.queries_planned_with_gaps);
+ rrddim_set_by_pointer(st_queries, rd_executed_with_gaps, (collected_number)cache_efficiency_stats.queries_executed_with_gaps);
+
+ rrdset_done(st_queries);
+ }
+
+ {
+ static RRDSET *st_queries_running = NULL;
+ static RRDDIM *rd_queries = NULL;
+
+ if (unlikely(!st_queries_running)) {
+ st_queries_running = rrdset_create_localhost(
+ "netdata",
+ "dbengine_queries_running",
+ NULL,
+ "dbengine query router",
+ NULL,
+ "Netdata Queries Running",
+ "queries",
+ "netdata",
+ "stats",
+ priority,
+ localhost->rrd_update_every,
+ RRDSET_TYPE_LINE);
+
+ rd_queries = rrddim_add(st_queries_running, "queries", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
+ }
+ priority++;
+
+ rrddim_set_by_pointer(st_queries_running, rd_queries, (collected_number)cache_efficiency_stats.currently_running_queries);
+
+ rrdset_done(st_queries_running);
+ }
+
+ {
+ static RRDSET *st_query_pages_metadata_source = NULL;
+ static RRDDIM *rd_cache = NULL;
+ static RRDDIM *rd_open = NULL;
+ static RRDDIM *rd_jv2 = NULL;
+
+ if (unlikely(!st_query_pages_metadata_source)) {
+ st_query_pages_metadata_source = rrdset_create_localhost(
+ "netdata",
+ "dbengine_query_pages_metadata_source",
+ NULL,
+ "dbengine query router",
+ NULL,
+ "Netdata Query Pages Metadata Source",
+ "pages/s",
+ "netdata",
+ "stats",
+ priority,
+ localhost->rrd_update_every,
+ RRDSET_TYPE_STACKED);
+
+ rd_cache = rrddim_add(st_query_pages_metadata_source, "cache hit", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_jv2 = rrddim_add(st_query_pages_metadata_source, "journal v2 scan", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_open = rrddim_add(st_query_pages_metadata_source, "open journal", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ }
+ priority++;
+
+ rrddim_set_by_pointer(st_query_pages_metadata_source, rd_cache, (collected_number)cache_efficiency_stats.pages_meta_source_main_cache);
+ rrddim_set_by_pointer(st_query_pages_metadata_source, rd_jv2, (collected_number)cache_efficiency_stats.pages_meta_source_journal_v2);
+ rrddim_set_by_pointer(st_query_pages_metadata_source, rd_open, (collected_number)cache_efficiency_stats.pages_meta_source_open_cache);
+
+ rrdset_done(st_query_pages_metadata_source);
+ }
+
+ {
+ static RRDSET *st_query_pages_data_source = NULL;
+ static RRDDIM *rd_pages_main_cache = NULL;
+ static RRDDIM *rd_pages_disk = NULL;
+ static RRDDIM *rd_pages_extent_cache = NULL;
+
+ if (unlikely(!st_query_pages_data_source)) {
+ st_query_pages_data_source = rrdset_create_localhost(
+ "netdata",
+ "dbengine_query_pages_data_source",
+ NULL,
+ "dbengine query router",
+ NULL,
+ "Netdata Query Pages to Data Source",
+ "pages/s",
+ "netdata",
+ "stats",
+ priority,
+ localhost->rrd_update_every,
+ RRDSET_TYPE_STACKED);
+
+ rd_pages_main_cache = rrddim_add(st_query_pages_data_source, "main cache", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_pages_disk = rrddim_add(st_query_pages_data_source, "disk", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_pages_extent_cache = rrddim_add(st_query_pages_data_source, "extent cache", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ }
+ priority++;
+
+ rrddim_set_by_pointer(st_query_pages_data_source, rd_pages_main_cache, (collected_number)cache_efficiency_stats.pages_data_source_main_cache + (collected_number)cache_efficiency_stats.pages_data_source_main_cache_at_pass4);
+ rrddim_set_by_pointer(st_query_pages_data_source, rd_pages_disk, (collected_number)cache_efficiency_stats.pages_to_load_from_disk);
+ rrddim_set_by_pointer(st_query_pages_data_source, rd_pages_extent_cache, (collected_number)cache_efficiency_stats.pages_data_source_extent_cache);
+
+ rrdset_done(st_query_pages_data_source);
+ }
+
+ {
+ static RRDSET *st_query_next_page = NULL;
+ static RRDDIM *rd_pass4 = NULL;
+ static RRDDIM *rd_nowait_failed = NULL;
+ static RRDDIM *rd_wait_failed = NULL;
+ static RRDDIM *rd_wait_loaded = NULL;
+ static RRDDIM *rd_nowait_loaded = NULL;
+
+ if (unlikely(!st_query_next_page)) {
+ st_query_next_page = rrdset_create_localhost(
+ "netdata",
+ "dbengine_query_next_page",
+ NULL,
+ "dbengine query router",
+ NULL,
+ "Netdata Query Next Page",
+ "pages/s",
+ "netdata",
+ "stats",
+ priority,
+ localhost->rrd_update_every,
+ RRDSET_TYPE_STACKED);
+
+ rd_pass4 = rrddim_add(st_query_next_page, "pass4", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_wait_failed = rrddim_add(st_query_next_page, "failed slow", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_nowait_failed = rrddim_add(st_query_next_page, "failed fast", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_wait_loaded = rrddim_add(st_query_next_page, "loaded slow", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_nowait_loaded = rrddim_add(st_query_next_page, "loaded fast", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ }
+ priority++;
+
+ rrddim_set_by_pointer(st_query_next_page, rd_pass4, (collected_number)cache_efficiency_stats.pages_data_source_main_cache_at_pass4);
+ rrddim_set_by_pointer(st_query_next_page, rd_wait_failed, (collected_number)cache_efficiency_stats.page_next_wait_failed);
+ rrddim_set_by_pointer(st_query_next_page, rd_nowait_failed, (collected_number)cache_efficiency_stats.page_next_nowait_failed);
+ rrddim_set_by_pointer(st_query_next_page, rd_wait_loaded, (collected_number)cache_efficiency_stats.page_next_wait_loaded);
+ rrddim_set_by_pointer(st_query_next_page, rd_nowait_loaded, (collected_number)cache_efficiency_stats.page_next_nowait_loaded);
+
+ rrdset_done(st_query_next_page);
+ }
+
+ {
+ static RRDSET *st_query_page_issues = NULL;
+ static RRDDIM *rd_pages_zero_time = NULL;
+ static RRDDIM *rd_pages_past_time = NULL;
+ static RRDDIM *rd_pages_invalid_size = NULL;
+ static RRDDIM *rd_pages_fixed_update_every = NULL;
+ static RRDDIM *rd_pages_fixed_entries = NULL;
+ static RRDDIM *rd_pages_overlapping = NULL;
+
+ if (unlikely(!st_query_page_issues)) {
+ st_query_page_issues = rrdset_create_localhost(
+ "netdata",
+ "dbengine_query_next_page_issues",
+ NULL,
+ "dbengine query router",
+ NULL,
+ "Netdata Query Next Page Issues",
+ "pages/s",
+ "netdata",
+ "stats",
+ priority,
+ localhost->rrd_update_every,
+ RRDSET_TYPE_STACKED);
+
+ rd_pages_zero_time = rrddim_add(st_query_page_issues, "zero timestamp", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_pages_invalid_size = rrddim_add(st_query_page_issues, "invalid size", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_pages_past_time = rrddim_add(st_query_page_issues, "past time", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_pages_overlapping = rrddim_add(st_query_page_issues, "overlapping", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_pages_fixed_update_every = rrddim_add(st_query_page_issues, "update every fixed", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_pages_fixed_entries = rrddim_add(st_query_page_issues, "entries fixed", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ }
+ priority++;
+
+ rrddim_set_by_pointer(st_query_page_issues, rd_pages_zero_time, (collected_number)cache_efficiency_stats.pages_zero_time_skipped);
+ rrddim_set_by_pointer(st_query_page_issues, rd_pages_invalid_size, (collected_number)cache_efficiency_stats.pages_invalid_size_skipped);
+ rrddim_set_by_pointer(st_query_page_issues, rd_pages_past_time, (collected_number)cache_efficiency_stats.pages_past_time_skipped);
+ rrddim_set_by_pointer(st_query_page_issues, rd_pages_overlapping, (collected_number)cache_efficiency_stats.pages_overlapping_skipped);
+ rrddim_set_by_pointer(st_query_page_issues, rd_pages_fixed_update_every, (collected_number)cache_efficiency_stats.pages_invalid_update_every_fixed);
+ rrddim_set_by_pointer(st_query_page_issues, rd_pages_fixed_entries, (collected_number)cache_efficiency_stats.pages_invalid_entries_fixed);
+
+ rrdset_done(st_query_page_issues);
+ }
+
+ {
+ static RRDSET *st_query_pages_from_disk = NULL;
+ static RRDDIM *rd_compressed = NULL;
+ static RRDDIM *rd_invalid = NULL;
+ static RRDDIM *rd_uncompressed = NULL;
+ static RRDDIM *rd_mmap_failed = NULL;
+ static RRDDIM *rd_unavailable = NULL;
+ static RRDDIM *rd_unroutable = NULL;
+ static RRDDIM *rd_not_found = NULL;
+ static RRDDIM *rd_cancelled = NULL;
+ static RRDDIM *rd_invalid_extent = NULL;
+ static RRDDIM *rd_extent_merged = NULL;
+
+ if (unlikely(!st_query_pages_from_disk)) {
+ st_query_pages_from_disk = rrdset_create_localhost(
+ "netdata",
+ "dbengine_query_pages_disk_load",
+ NULL,
+ "dbengine query router",
+ NULL,
+ "Netdata Query Pages Loaded from Disk",
+ "pages/s",
+ "netdata",
+ "stats",
+ priority,
+ localhost->rrd_update_every,
+ RRDSET_TYPE_LINE);
+
+ rd_compressed = rrddim_add(st_query_pages_from_disk, "ok compressed", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_invalid = rrddim_add(st_query_pages_from_disk, "fail invalid page", NULL, -1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_uncompressed = rrddim_add(st_query_pages_from_disk, "ok uncompressed", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_mmap_failed = rrddim_add(st_query_pages_from_disk, "fail cant mmap", NULL, -1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_unavailable = rrddim_add(st_query_pages_from_disk, "fail unavailable", NULL, -1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_unroutable = rrddim_add(st_query_pages_from_disk, "fail unroutable", NULL, -1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_not_found = rrddim_add(st_query_pages_from_disk, "fail not found", NULL, -1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_invalid_extent = rrddim_add(st_query_pages_from_disk, "fail invalid extent", NULL, -1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_extent_merged = rrddim_add(st_query_pages_from_disk, "extent merged", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_cancelled = rrddim_add(st_query_pages_from_disk, "cancelled", NULL, -1, 1, RRD_ALGORITHM_INCREMENTAL);
+ }
+ priority++;
+
+ rrddim_set_by_pointer(st_query_pages_from_disk, rd_compressed, (collected_number)cache_efficiency_stats.pages_load_ok_compressed);
+ rrddim_set_by_pointer(st_query_pages_from_disk, rd_invalid, (collected_number)cache_efficiency_stats.pages_load_fail_invalid_page_in_extent);
+ rrddim_set_by_pointer(st_query_pages_from_disk, rd_uncompressed, (collected_number)cache_efficiency_stats.pages_load_ok_uncompressed);
+ rrddim_set_by_pointer(st_query_pages_from_disk, rd_mmap_failed, (collected_number)cache_efficiency_stats.pages_load_fail_cant_mmap_extent);
+ rrddim_set_by_pointer(st_query_pages_from_disk, rd_unavailable, (collected_number)cache_efficiency_stats.pages_load_fail_datafile_not_available);
+ rrddim_set_by_pointer(st_query_pages_from_disk, rd_unroutable, (collected_number)cache_efficiency_stats.pages_load_fail_unroutable);
+ rrddim_set_by_pointer(st_query_pages_from_disk, rd_not_found, (collected_number)cache_efficiency_stats.pages_load_fail_not_found);
+ rrddim_set_by_pointer(st_query_pages_from_disk, rd_cancelled, (collected_number)cache_efficiency_stats.pages_load_fail_cancelled);
+ rrddim_set_by_pointer(st_query_pages_from_disk, rd_invalid_extent, (collected_number)cache_efficiency_stats.pages_load_fail_invalid_extent);
+ rrddim_set_by_pointer(st_query_pages_from_disk, rd_extent_merged, (collected_number)cache_efficiency_stats.pages_load_extent_merged);
+
+ rrdset_done(st_query_pages_from_disk);
+ }
+
+ {
+ static RRDSET *st_events = NULL;
+ static RRDDIM *rd_journal_v2_mapped = NULL;
+ static RRDDIM *rd_journal_v2_unmapped = NULL;
+ static RRDDIM *rd_datafile_creation = NULL;
+ static RRDDIM *rd_datafile_deletion = NULL;
+ static RRDDIM *rd_datafile_deletion_spin = NULL;
+ static RRDDIM *rd_jv2_indexing = NULL;
+ static RRDDIM *rd_retention = NULL;
+
+ if (unlikely(!st_events)) {
+ st_events = rrdset_create_localhost(
+ "netdata",
+ "dbengine_events",
+ NULL,
+ "dbengine query router",
+ NULL,
+ "Netdata Database Events",
+ "events/s",
+ "netdata",
+ "stats",
+ priority,
+ localhost->rrd_update_every,
+ RRDSET_TYPE_LINE);
+
+ rd_journal_v2_mapped = rrddim_add(st_events, "journal v2 mapped", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_journal_v2_unmapped = rrddim_add(st_events, "journal v2 unmapped", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_datafile_creation = rrddim_add(st_events, "datafile creation", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_datafile_deletion = rrddim_add(st_events, "datafile deletion", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_datafile_deletion_spin = rrddim_add(st_events, "datafile deletion spin", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_jv2_indexing = rrddim_add(st_events, "journal v2 indexing", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_retention = rrddim_add(st_events, "retention", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ }
+ priority++;
+
+ rrddim_set_by_pointer(st_events, rd_journal_v2_mapped, (collected_number)cache_efficiency_stats.journal_v2_mapped);
+ rrddim_set_by_pointer(st_events, rd_journal_v2_unmapped, (collected_number)cache_efficiency_stats.journal_v2_unmapped);
+ rrddim_set_by_pointer(st_events, rd_datafile_creation, (collected_number)cache_efficiency_stats.datafile_creation_started);
+ rrddim_set_by_pointer(st_events, rd_datafile_deletion, (collected_number)cache_efficiency_stats.datafile_deletion_started);
+ rrddim_set_by_pointer(st_events, rd_datafile_deletion_spin, (collected_number)cache_efficiency_stats.datafile_deletion_spin);
+ rrddim_set_by_pointer(st_events, rd_jv2_indexing, (collected_number)cache_efficiency_stats.journal_v2_indexing_started);
+ rrddim_set_by_pointer(st_events, rd_retention, (collected_number)cache_efficiency_stats.metrics_retention_started);
+
+ rrdset_done(st_events);
+ }
+
+ {
+ static RRDSET *st_prep_timings = NULL;
+ static RRDDIM *rd_routing = NULL;
+ static RRDDIM *rd_main_cache = NULL;
+ static RRDDIM *rd_open_cache = NULL;
+ static RRDDIM *rd_journal_v2 = NULL;
+ static RRDDIM *rd_pass4 = NULL;
+
+ if (unlikely(!st_prep_timings)) {
+ st_prep_timings = rrdset_create_localhost(
+ "netdata",
+ "dbengine_prep_timings",
+ NULL,
+ "dbengine query router",
+ NULL,
+ "Netdata Query Preparation Timings",
+ "usec/s",
+ "netdata",
+ "stats",
+ priority,
+ localhost->rrd_update_every,
+ RRDSET_TYPE_STACKED);
+
+ rd_routing = rrddim_add(st_prep_timings, "routing", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_main_cache = rrddim_add(st_prep_timings, "main cache", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_open_cache = rrddim_add(st_prep_timings, "open cache", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_journal_v2 = rrddim_add(st_prep_timings, "journal v2", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_pass4 = rrddim_add(st_prep_timings, "pass4", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ }
+ priority++;
+
+ rrddim_set_by_pointer(st_prep_timings, rd_routing, (collected_number)cache_efficiency_stats.prep_time_to_route);
+ rrddim_set_by_pointer(st_prep_timings, rd_main_cache, (collected_number)cache_efficiency_stats.prep_time_in_main_cache_lookup);
+ rrddim_set_by_pointer(st_prep_timings, rd_open_cache, (collected_number)cache_efficiency_stats.prep_time_in_open_cache_lookup);
+ rrddim_set_by_pointer(st_prep_timings, rd_journal_v2, (collected_number)cache_efficiency_stats.prep_time_in_journal_v2_lookup);
+ rrddim_set_by_pointer(st_prep_timings, rd_pass4, (collected_number)cache_efficiency_stats.prep_time_in_pass4_lookup);
+
+ rrdset_done(st_prep_timings);
+ }
+
+ {
+ static RRDSET *st_query_timings = NULL;
+ static RRDDIM *rd_init = NULL;
+ static RRDDIM *rd_prep_wait = NULL;
+ static RRDDIM *rd_next_page_disk_fast = NULL;
+ static RRDDIM *rd_next_page_disk_slow = NULL;
+ static RRDDIM *rd_next_page_preload_fast = NULL;
+ static RRDDIM *rd_next_page_preload_slow = NULL;
+
+ if (unlikely(!st_query_timings)) {
+ st_query_timings = rrdset_create_localhost(
+ "netdata",
+ "dbengine_query_timings",
+ NULL,
+ "dbengine query router",
+ NULL,
+ "Netdata Query Timings",
+ "usec/s",
+ "netdata",
+ "stats",
+ priority,
+ localhost->rrd_update_every,
+ RRDSET_TYPE_STACKED);
+
+ rd_init = rrddim_add(st_query_timings, "init", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_prep_wait = rrddim_add(st_query_timings, "prep wait", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_next_page_disk_fast = rrddim_add(st_query_timings, "next page disk fast", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_next_page_disk_slow = rrddim_add(st_query_timings, "next page disk slow", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_next_page_preload_fast = rrddim_add(st_query_timings, "next page preload fast", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_next_page_preload_slow = rrddim_add(st_query_timings, "next page preload slow", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ }
+ priority++;
+
+ rrddim_set_by_pointer(st_query_timings, rd_init, (collected_number)cache_efficiency_stats.query_time_init);
+ rrddim_set_by_pointer(st_query_timings, rd_prep_wait, (collected_number)cache_efficiency_stats.query_time_wait_for_prep);
+ rrddim_set_by_pointer(st_query_timings, rd_next_page_disk_fast, (collected_number)cache_efficiency_stats.query_time_to_fast_disk_next_page);
+ rrddim_set_by_pointer(st_query_timings, rd_next_page_disk_slow, (collected_number)cache_efficiency_stats.query_time_to_slow_disk_next_page);
+ rrddim_set_by_pointer(st_query_timings, rd_next_page_preload_fast, (collected_number)cache_efficiency_stats.query_time_to_fast_preload_next_page);
+ rrddim_set_by_pointer(st_query_timings, rd_next_page_preload_slow, (collected_number)cache_efficiency_stats.query_time_to_slow_preload_next_page);
+
+ rrdset_done(st_query_timings);
+ }
+
if(netdata_rwlock_tryrdlock(&rrd_rwlock) == 0) {
+ priority = 135400;
+
RRDHOST *host;
unsigned long long stats_array[RRDENG_NR_STATS] = {0};
unsigned long long local_stats_array[RRDENG_NR_STATS];
@@ -1012,21 +2536,22 @@ static void dbengine_statistics_charts(void) {
if (unlikely(!st_compression)) {
st_compression = rrdset_create_localhost(
- "netdata",
- "dbengine_compression_ratio",
- NULL,
- "dbengine",
- NULL,
- "Netdata DB engine data extents' compression savings ratio",
- "percentage",
- "netdata",
- "stats",
- 132000,
- localhost->rrd_update_every,
- RRDSET_TYPE_LINE);
+ "netdata",
+ "dbengine_compression_ratio",
+ NULL,
+ "dbengine io",
+ NULL,
+ "Netdata DB engine data extents' compression savings ratio",
+ "percentage",
+ "netdata",
+ "stats",
+ priority,
+ localhost->rrd_update_every,
+ RRDSET_TYPE_LINE);
rd_savings = rrddim_add(st_compression, "savings", NULL, 1, 1000, RRD_ALGORITHM_ABSOLUTE);
}
+ priority++;
unsigned long long ratio;
unsigned long long compressed_content_size = stats_array[12];
@@ -1046,158 +2571,29 @@ static void dbengine_statistics_charts(void) {
// ----------------------------------------------------------------
{
- static RRDSET *st_pg_cache_hit_ratio = NULL;
- static RRDDIM *rd_hit_ratio = NULL;
-
- if (unlikely(!st_pg_cache_hit_ratio)) {
- st_pg_cache_hit_ratio = rrdset_create_localhost(
- "netdata",
- "page_cache_hit_ratio",
- NULL,
- "dbengine",
- NULL,
- "Netdata DB engine page cache hit ratio",
- "percentage",
- "netdata",
- "stats",
- 132003,
- localhost->rrd_update_every,
- RRDSET_TYPE_LINE);
-
- rd_hit_ratio = rrddim_add(st_pg_cache_hit_ratio, "ratio", NULL, 1, 1000, RRD_ALGORITHM_ABSOLUTE);
- }
-
- static unsigned long long old_hits = 0;
- static unsigned long long old_misses = 0;
- unsigned long long hits = stats_array[7];
- unsigned long long misses = stats_array[8];
- unsigned long long hits_delta;
- unsigned long long misses_delta;
- unsigned long long ratio;
-
- hits_delta = hits - old_hits;
- misses_delta = misses - old_misses;
- old_hits = hits;
- old_misses = misses;
-
- if (hits_delta + misses_delta) {
- ratio = (hits_delta * 100 * 1000) / (hits_delta + misses_delta);
- } else {
- ratio = 0;
- }
- rrddim_set_by_pointer(st_pg_cache_hit_ratio, rd_hit_ratio, ratio);
-
- rrdset_done(st_pg_cache_hit_ratio);
- }
-
- // ----------------------------------------------------------------
-
- {
- static RRDSET *st_pg_cache_pages = NULL;
- static RRDDIM *rd_descriptors = NULL;
- static RRDDIM *rd_populated = NULL;
- static RRDDIM *rd_dirty = NULL;
- static RRDDIM *rd_backfills = NULL;
- static RRDDIM *rd_evictions = NULL;
- static RRDDIM *rd_used_by_collectors = NULL;
-
- if (unlikely(!st_pg_cache_pages)) {
- st_pg_cache_pages = rrdset_create_localhost(
- "netdata",
- "page_cache_stats",
- NULL,
- "dbengine",
- NULL,
- "Netdata dbengine page cache statistics",
- "pages",
- "netdata",
- "stats",
- 132004,
- localhost->rrd_update_every,
- RRDSET_TYPE_LINE);
-
- rd_descriptors = rrddim_add(st_pg_cache_pages, "descriptors", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
- rd_populated = rrddim_add(st_pg_cache_pages, "populated", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
- rd_dirty = rrddim_add(st_pg_cache_pages, "dirty", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
- rd_backfills = rrddim_add(st_pg_cache_pages, "backfills", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
- rd_evictions = rrddim_add(st_pg_cache_pages, "evictions", NULL, -1, 1, RRD_ALGORITHM_INCREMENTAL);
- rd_used_by_collectors =
- rrddim_add(st_pg_cache_pages, "used_by_collectors", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
- }
-
- rrddim_set_by_pointer(st_pg_cache_pages, rd_descriptors, (collected_number)stats_array[27]);
- rrddim_set_by_pointer(st_pg_cache_pages, rd_populated, (collected_number)stats_array[3]);
- rrddim_set_by_pointer(st_pg_cache_pages, rd_dirty, (collected_number)stats_array[0] + stats_array[4]);
- rrddim_set_by_pointer(st_pg_cache_pages, rd_backfills, (collected_number)stats_array[9]);
- rrddim_set_by_pointer(st_pg_cache_pages, rd_evictions, (collected_number)stats_array[10]);
- rrddim_set_by_pointer(st_pg_cache_pages, rd_used_by_collectors, (collected_number)stats_array[0]);
- rrdset_done(st_pg_cache_pages);
- }
-
- // ----------------------------------------------------------------
-
- {
- static RRDSET *st_long_term_pages = NULL;
- static RRDDIM *rd_total = NULL;
- static RRDDIM *rd_insertions = NULL;
- static RRDDIM *rd_deletions = NULL;
- static RRDDIM *rd_flushing_pressure_deletions = NULL;
-
- if (unlikely(!st_long_term_pages)) {
- st_long_term_pages = rrdset_create_localhost(
- "netdata",
- "dbengine_long_term_page_stats",
- NULL,
- "dbengine",
- NULL,
- "Netdata dbengine long-term page statistics",
- "pages",
- "netdata",
- "stats",
- 132005,
- localhost->rrd_update_every,
- RRDSET_TYPE_LINE);
-
- rd_total = rrddim_add(st_long_term_pages, "total", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
- rd_insertions = rrddim_add(st_long_term_pages, "insertions", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
- rd_deletions = rrddim_add(st_long_term_pages, "deletions", NULL, -1, 1, RRD_ALGORITHM_INCREMENTAL);
- rd_flushing_pressure_deletions = rrddim_add(
- st_long_term_pages, "flushing_pressure_deletions", NULL, -1, 1, RRD_ALGORITHM_INCREMENTAL);
- }
-
- rrddim_set_by_pointer(st_long_term_pages, rd_total, (collected_number)stats_array[2]);
- rrddim_set_by_pointer(st_long_term_pages, rd_insertions, (collected_number)stats_array[5]);
- rrddim_set_by_pointer(st_long_term_pages, rd_deletions, (collected_number)stats_array[6]);
- rrddim_set_by_pointer(
- st_long_term_pages, rd_flushing_pressure_deletions, (collected_number)stats_array[36]);
- rrdset_done(st_long_term_pages);
- }
-
- // ----------------------------------------------------------------
-
- {
static RRDSET *st_io_stats = NULL;
static RRDDIM *rd_reads = NULL;
static RRDDIM *rd_writes = NULL;
if (unlikely(!st_io_stats)) {
st_io_stats = rrdset_create_localhost(
- "netdata",
- "dbengine_io_throughput",
- NULL,
- "dbengine",
- NULL,
- "Netdata DB engine I/O throughput",
- "MiB/s",
- "netdata",
- "stats",
- 132006,
- localhost->rrd_update_every,
- RRDSET_TYPE_LINE);
+ "netdata",
+ "dbengine_io_throughput",
+ NULL,
+ "dbengine io",
+ NULL,
+ "Netdata DB engine I/O throughput",
+ "MiB/s",
+ "netdata",
+ "stats",
+ priority,
+ localhost->rrd_update_every,
+ RRDSET_TYPE_LINE);
rd_reads = rrddim_add(st_io_stats, "reads", NULL, 1, 1024 * 1024, RRD_ALGORITHM_INCREMENTAL);
rd_writes = rrddim_add(st_io_stats, "writes", NULL, -1, 1024 * 1024, RRD_ALGORITHM_INCREMENTAL);
}
+ priority++;
rrddim_set_by_pointer(st_io_stats, rd_reads, (collected_number)stats_array[17]);
rrddim_set_by_pointer(st_io_stats, rd_writes, (collected_number)stats_array[15]);
@@ -1213,22 +2609,23 @@ static void dbengine_statistics_charts(void) {
if (unlikely(!st_io_stats)) {
st_io_stats = rrdset_create_localhost(
- "netdata",
- "dbengine_io_operations",
- NULL,
- "dbengine",
- NULL,
- "Netdata DB engine I/O operations",
- "operations/s",
- "netdata",
- "stats",
- 132007,
- localhost->rrd_update_every,
- RRDSET_TYPE_LINE);
+ "netdata",
+ "dbengine_io_operations",
+ NULL,
+ "dbengine io",
+ NULL,
+ "Netdata DB engine I/O operations",
+ "operations/s",
+ "netdata",
+ "stats",
+ priority,
+ localhost->rrd_update_every,
+ RRDSET_TYPE_LINE);
rd_reads = rrddim_add(st_io_stats, "reads", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
rd_writes = rrddim_add(st_io_stats, "writes", NULL, -1, 1, RRD_ALGORITHM_INCREMENTAL);
}
+ priority++;
rrddim_set_by_pointer(st_io_stats, rd_reads, (collected_number)stats_array[18]);
rrddim_set_by_pointer(st_io_stats, rd_writes, (collected_number)stats_array[16]);
@@ -1245,24 +2642,25 @@ static void dbengine_statistics_charts(void) {
if (unlikely(!st_errors)) {
st_errors = rrdset_create_localhost(
- "netdata",
- "dbengine_global_errors",
- NULL,
- "dbengine",
- NULL,
- "Netdata DB engine errors",
- "errors/s",
- "netdata",
- "stats",
- 132008,
- localhost->rrd_update_every,
- RRDSET_TYPE_LINE);
+ "netdata",
+ "dbengine_global_errors",
+ NULL,
+ "dbengine io",
+ NULL,
+ "Netdata DB engine errors",
+ "errors/s",
+ "netdata",
+ "stats",
+ priority,
+ localhost->rrd_update_every,
+ RRDSET_TYPE_LINE);
rd_io_errors = rrddim_add(st_errors, "io_errors", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
rd_fs_errors = rrddim_add(st_errors, "fs_errors", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
pg_cache_over_half_dirty_events =
- rrddim_add(st_errors, "pg_cache_over_half_dirty_events", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rrddim_add(st_errors, "pg_cache_over_half_dirty_events", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
}
+ priority++;
rrddim_set_by_pointer(st_errors, rd_io_errors, (collected_number)stats_array[30]);
rrddim_set_by_pointer(st_errors, rd_fs_errors, (collected_number)stats_array[31]);
@@ -1279,87 +2677,33 @@ static void dbengine_statistics_charts(void) {
if (unlikely(!st_fd)) {
st_fd = rrdset_create_localhost(
- "netdata",
- "dbengine_global_file_descriptors",
- NULL,
- "dbengine",
- NULL,
- "Netdata DB engine File Descriptors",
- "descriptors",
- "netdata",
- "stats",
- 132009,
- localhost->rrd_update_every,
- RRDSET_TYPE_LINE);
+ "netdata",
+ "dbengine_global_file_descriptors",
+ NULL,
+ "dbengine io",
+ NULL,
+ "Netdata DB engine File Descriptors",
+ "descriptors",
+ "netdata",
+ "stats",
+ priority,
+ localhost->rrd_update_every,
+ RRDSET_TYPE_LINE);
rd_fd_current = rrddim_add(st_fd, "current", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
rd_fd_max = rrddim_add(st_fd, "max", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
}
+ priority++;
rrddim_set_by_pointer(st_fd, rd_fd_current, (collected_number)stats_array[32]);
/* Careful here, modify this accordingly if the File-Descriptor budget ever changes */
rrddim_set_by_pointer(st_fd, rd_fd_max, (collected_number)rlimit_nofile.rlim_cur / 4);
rrdset_done(st_fd);
}
-
- // ----------------------------------------------------------------
-
- {
- static RRDSET *st_ram_usage = NULL;
- static RRDDIM *rd_cached = NULL;
- static RRDDIM *rd_pinned = NULL;
- static RRDDIM *rd_cache_metadata = NULL;
- static RRDDIM *rd_index_metadata = NULL;
- static RRDDIM *rd_pages_metadata = NULL;
-
- collected_number API_producers, populated_pages, cache_metadata, pages_on_disk,
- page_cache_descriptors, index_metadata, pages_metadata;
-
- if (unlikely(!st_ram_usage)) {
- st_ram_usage = rrdset_create_localhost(
- "netdata",
- "dbengine_ram",
- NULL,
- "dbengine",
- NULL,
- "Netdata DB engine RAM usage",
- "MiB",
- "netdata",
- "stats",
- 132010,
- localhost->rrd_update_every,
- RRDSET_TYPE_STACKED);
-
- rd_cached = rrddim_add(st_ram_usage, "cache", NULL, RRDENG_BLOCK_SIZE, 1024*1024, RRD_ALGORITHM_ABSOLUTE);
- rd_pinned = rrddim_add(st_ram_usage, "collectors", NULL, RRDENG_BLOCK_SIZE, 1024*1024, RRD_ALGORITHM_ABSOLUTE);
- rd_cache_metadata = rrddim_add(st_ram_usage, "cache metadata", NULL, 1, 1024*1024, RRD_ALGORITHM_ABSOLUTE);
- rd_pages_metadata = rrddim_add(st_ram_usage, "pages metadata", NULL, 1, 1024*1024, RRD_ALGORITHM_ABSOLUTE);
- rd_index_metadata = rrddim_add(st_ram_usage, "index metadata", NULL, 1, 1024*1024, RRD_ALGORITHM_ABSOLUTE);
- }
-
- API_producers = (collected_number)stats_array[0];
- pages_on_disk = (collected_number)stats_array[2];
- populated_pages = (collected_number)stats_array[3];
- page_cache_descriptors = (collected_number)stats_array[27];
-
- cache_metadata = page_cache_descriptors * sizeof(struct page_cache_descr);
-
- pages_metadata = pages_on_disk * sizeof(struct rrdeng_page_descr);
-
- /* This is an empirical estimation for Judy array indexing and extent structures */
- index_metadata = pages_on_disk * 58;
-
- rrddim_set_by_pointer(st_ram_usage, rd_cached, populated_pages - API_producers);
- rrddim_set_by_pointer(st_ram_usage, rd_pinned, API_producers);
- rrddim_set_by_pointer(st_ram_usage, rd_cache_metadata, cache_metadata);
- rrddim_set_by_pointer(st_ram_usage, rd_pages_metadata, pages_metadata);
- rrddim_set_by_pointer(st_ram_usage, rd_index_metadata, index_metadata);
- rrdset_done(st_ram_usage);
- }
}
}
-#endif
}
+#endif // ENABLE_DBENGINE
static void update_strings_charts() {
static RRDSET *st_ops = NULL, *st_entries = NULL, *st_mem = NULL;
@@ -1486,6 +2830,15 @@ static void update_heartbeat_charts() {
// ---------------------------------------------------------------------------------------------------------------------
// dictionary statistics
+struct dictionary_stats dictionary_stats_category_collectors = { .name = "collectors" };
+struct dictionary_stats dictionary_stats_category_rrdhost = { .name = "rrdhost" };
+struct dictionary_stats dictionary_stats_category_rrdset_rrddim = { .name = "rrdset_rrddim" };
+struct dictionary_stats dictionary_stats_category_rrdcontext = { .name = "context" };
+struct dictionary_stats dictionary_stats_category_rrdlabels = { .name = "labels" };
+struct dictionary_stats dictionary_stats_category_rrdhealth = { .name = "health" };
+struct dictionary_stats dictionary_stats_category_functions = { .name = "functions" };
+struct dictionary_stats dictionary_stats_category_replication = { .name = "replication" };
+
struct dictionary_categories {
struct dictionary_stats *stats;
const char *family;
@@ -1531,7 +2884,15 @@ struct dictionary_categories {
RRDDIM *rd_spins_delete;
} dictionary_categories[] = {
- { .stats = &dictionary_stats_category_other, "dictionaries", "dictionaries", 900000 },
+ { .stats = &dictionary_stats_category_collectors, "dictionaries collectors", "dictionaries", 900000 },
+ { .stats = &dictionary_stats_category_rrdhost, "dictionaries hosts", "dictionaries", 900000 },
+ { .stats = &dictionary_stats_category_rrdset_rrddim, "dictionaries rrd", "dictionaries", 900000 },
+ { .stats = &dictionary_stats_category_rrdcontext, "dictionaries contexts", "dictionaries", 900000 },
+ { .stats = &dictionary_stats_category_rrdlabels, "dictionaries labels", "dictionaries", 900000 },
+ { .stats = &dictionary_stats_category_rrdhealth, "dictionaries health", "dictionaries", 900000 },
+ { .stats = &dictionary_stats_category_functions, "dictionaries functions", "dictionaries", 900000 },
+ { .stats = &dictionary_stats_category_replication, "dictionaries replication", "dictionaries", 900000 },
+ { .stats = &dictionary_stats_category_other, "dictionaries other", "dictionaries", 900000 },
// terminator
{ .stats = NULL, NULL, NULL, 0 },
@@ -1741,7 +3102,7 @@ static void update_dictionary_category_charts(struct dictionary_categories *c) {
// ------------------------------------------------------------------------
total = 0;
- load_dictionary_stats_entry(memory.indexed);
+ load_dictionary_stats_entry(memory.index);
load_dictionary_stats_entry(memory.values);
load_dictionary_stats_entry(memory.dict);
@@ -1775,7 +3136,7 @@ static void update_dictionary_category_charts(struct dictionary_categories *c) {
rrdlabels_add(c->st_memory->rrdlabels, "category", stats.name, RRDLABEL_SRC_AUTO);
}
- rrddim_set_by_pointer(c->st_memory, c->rd_memory_indexed, (collected_number)stats.memory.indexed);
+ rrddim_set_by_pointer(c->st_memory, c->rd_memory_indexed, (collected_number)stats.memory.index);
rrddim_set_by_pointer(c->st_memory, c->rd_memory_values, (collected_number)stats.memory.values);
rrddim_set_by_pointer(c->st_memory, c->rd_memory_dict, (collected_number)stats.memory.dict);
@@ -2079,6 +3440,7 @@ static struct worker_utilization all_workers_utilization[] = {
{ .name = "STREAMRCV", .family = "workers streaming receive", .priority = 1000000 },
{ .name = "STREAMSND", .family = "workers streaming send", .priority = 1000000 },
{ .name = "DBENGINE", .family = "workers dbengine instances", .priority = 1000000 },
+ { .name = "LIBUV", .family = "workers libuv threadpool", .priority = 1000000 },
{ .name = "WEB", .family = "workers web server", .priority = 1000000 },
{ .name = "ACLKQUERY", .family = "workers aclk query", .priority = 1000000 },
{ .name = "ACLKSYNC", .family = "workers aclk host sync", .priority = 1000000 },
@@ -2543,7 +3905,7 @@ static int read_thread_cpu_time_from_proc_stat(pid_t pid __maybe_unused, kernel_
// (re)open the procfile to the new filename
bool set_quotes = (ff == NULL) ? true : false;
- ff = procfile_reopen(ff, filename, NULL, PROCFILE_FLAG_DEFAULT);
+ ff = procfile_reopen(ff, filename, NULL, PROCFILE_FLAG_ERROR_ON_ERROR_LOG);
if(unlikely(!ff)) return -1;
if(set_quotes)
@@ -2577,7 +3939,7 @@ static void workers_threads_cleanup(struct worker_utilization *wu) {
if(!t->enabled) {
JudyLDel(&workers_by_pid_JudyL_array, t->pid, PJE0);
- DOUBLE_LINKED_LIST_REMOVE_UNSAFE(wu->threads, t, prev, next);
+ DOUBLE_LINKED_LIST_REMOVE_ITEM_UNSAFE(wu->threads, t, prev, next);
freez(t);
}
t = next;
@@ -2604,7 +3966,7 @@ static struct worker_thread *worker_thread_create(struct worker_utilization *wu,
*PValue = wt;
// link it
- DOUBLE_LINKED_LIST_APPEND_UNSAFE(wu->threads, wt, prev, next);
+ DOUBLE_LINKED_LIST_APPEND_ITEM_UNSAFE(wu->threads, wt, prev, next);
return wt;
}
@@ -2637,6 +3999,9 @@ static void worker_utilization_charts_callback(void *ptr
// find the worker_thread in the list
struct worker_thread *wt = worker_thread_find_or_create(wu, pid);
+ if(utilization_usec > duration_usec)
+ utilization_usec = duration_usec;
+
wt->enabled = true;
wt->busy_time = utilization_usec;
wt->jobs_started = jobs_started;
@@ -2786,8 +4151,6 @@ static void global_statistics_cleanup(void *ptr)
info("cleaning up...");
- worker_utilization_finish();
-
static_thread->enabled = NETDATA_MAIN_THREAD_EXITED;
}
@@ -2810,23 +4173,22 @@ void *global_statistics_main(void *ptr)
// to make sure we are not close to any other thread
hb.randomness = 0;
- while (!netdata_exit) {
+ while (service_running(SERVICE_COLLECTORS)) {
worker_is_idle();
heartbeat_next(&hb, step);
worker_is_busy(WORKER_JOB_GLOBAL);
global_statistics_charts();
- worker_is_busy(WORKER_JOB_SQLITE3);
- sqlite3_statistics_charts();
-
worker_is_busy(WORKER_JOB_REGISTRY);
registry_statistics();
+#ifdef ENABLE_DBENGINE
if(dbengine_enabled) {
worker_is_busy(WORKER_JOB_DBENGINE);
- dbengine_statistics_charts();
+ dbengine2_statistics_charts();
}
+#endif
worker_is_busy(WORKER_JOB_HEARTBEAT);
update_heartbeat_charts();
@@ -2880,7 +4242,7 @@ void *global_statistics_workers_main(void *ptr)
heartbeat_t hb;
heartbeat_init(&hb);
- while (!netdata_exit) {
+ while (service_running(SERVICE_COLLECTORS)) {
worker_is_idle();
heartbeat_next(&hb, step);
@@ -2892,3 +4254,45 @@ void *global_statistics_workers_main(void *ptr)
return NULL;
}
+// ---------------------------------------------------------------------------------------------------------------------
+// sqlite3 thread
+
+static void global_statistics_sqlite3_cleanup(void *ptr)
+{
+ worker_unregister();
+
+ struct netdata_static_thread *static_thread = (struct netdata_static_thread *)ptr;
+ static_thread->enabled = NETDATA_MAIN_THREAD_EXITING;
+
+ info("cleaning up...");
+
+ static_thread->enabled = NETDATA_MAIN_THREAD_EXITED;
+}
+
+void *global_statistics_sqlite3_main(void *ptr)
+{
+ global_statistics_register_workers();
+
+ netdata_thread_cleanup_push(global_statistics_sqlite3_cleanup, ptr);
+
+ int update_every =
+ (int)config_get_number(CONFIG_SECTION_GLOBAL_STATISTICS, "update every", localhost->rrd_update_every);
+ if (update_every < localhost->rrd_update_every)
+ update_every = localhost->rrd_update_every;
+
+ usec_t step = update_every * USEC_PER_SEC;
+ heartbeat_t hb;
+ heartbeat_init(&hb);
+
+ while (service_running(SERVICE_COLLECTORS)) {
+ worker_is_idle();
+ heartbeat_next(&hb, step);
+
+ worker_is_busy(WORKER_JOB_SQLITE3);
+ sqlite3_statistics_charts();
+ }
+
+ netdata_thread_cleanup_pop(1);
+ return NULL;
+}
+
diff --git a/daemon/global_statistics.h b/daemon/global_statistics.h
index f7d6775c..7bdb153d 100644
--- a/daemon/global_statistics.h
+++ b/daemon/global_statistics.h
@@ -5,10 +5,39 @@
#include "database/rrd.h"
+extern struct netdata_buffers_statistics {
+ size_t rrdhost_allocations_size;
+ size_t rrdhost_senders;
+ size_t rrdhost_receivers;
+ size_t query_targets_size;
+ size_t rrdset_done_rda_size;
+ size_t buffers_aclk;
+ size_t buffers_api;
+ size_t buffers_functions;
+ size_t buffers_sqlite;
+ size_t buffers_exporters;
+ size_t buffers_health;
+ size_t buffers_streaming;
+ size_t cbuffers_streaming;
+ size_t buffers_web;
+} netdata_buffers_statistics;
+
+extern struct dictionary_stats dictionary_stats_category_collectors;
+extern struct dictionary_stats dictionary_stats_category_rrdhost;
+extern struct dictionary_stats dictionary_stats_category_rrdset_rrddim;
+extern struct dictionary_stats dictionary_stats_category_rrdcontext;
+extern struct dictionary_stats dictionary_stats_category_rrdlabels;
+extern struct dictionary_stats dictionary_stats_category_rrdhealth;
+extern struct dictionary_stats dictionary_stats_category_functions;
+extern struct dictionary_stats dictionary_stats_category_replication;
+
+extern size_t rrddim_db_memory_size;
+
// ----------------------------------------------------------------------------
// global statistics
void global_statistics_ml_query_completed(size_t points_read);
+void global_statistics_ml_models_consulted(size_t models_consulted);
void global_statistics_exporters_query_completed(size_t points_read);
void global_statistics_backfill_query_completed(size_t points_read);
void global_statistics_rrdr_query_completed(size_t queries, uint64_t db_points_read, uint64_t result_points_generated, QUERY_SOURCE query_source);
diff --git a/daemon/main.c b/daemon/main.c
index 6b591385..7b2076f3 100644
--- a/daemon/main.c
+++ b/daemon/main.c
@@ -4,10 +4,16 @@
#include "buildinfo.h"
#include "static_threads.h"
+#if defined(ENV32BIT)
+#warning COMPILING 32BIT NETDATA
+#endif
+
bool unittest_running = false;
int netdata_zero_metrics_enabled;
int netdata_anonymous_statistics_enabled;
+int libuv_worker_threads = MIN_LIBUV_WORKER_THREADS;
+
struct netdata_static_thread *static_threads;
struct config netdata_config = {
@@ -23,69 +29,461 @@ struct config netdata_config = {
}
};
+typedef struct service_thread {
+ pid_t tid;
+ SERVICE_THREAD_TYPE type;
+ SERVICE_TYPE services;
+ char name[NETDATA_THREAD_NAME_MAX + 1];
+ bool cancelled;
+
+ union {
+ netdata_thread_t netdata_thread;
+ uv_thread_t uv_thread;
+ };
+
+ force_quit_t force_quit_callback;
+ request_quit_t request_quit_callback;
+ void *data;
+} SERVICE_THREAD;
+
+struct service_globals {
+ SERVICE_TYPE running;
+ SPINLOCK lock;
+ Pvoid_t pid_judy;
+} service_globals = {
+ .running = ~0,
+ .pid_judy = NULL,
+};
+
+SERVICE_THREAD *service_register(SERVICE_THREAD_TYPE thread_type, request_quit_t request_quit_callback, force_quit_t force_quit_callback, void *data, bool update __maybe_unused) {
+ SERVICE_THREAD *sth = NULL;
+ pid_t tid = gettid();
+
+ netdata_spinlock_lock(&service_globals.lock);
+ Pvoid_t *PValue = JudyLIns(&service_globals.pid_judy, tid, PJE0);
+ if(!*PValue) {
+ sth = callocz(1, sizeof(SERVICE_THREAD));
+ sth->tid = tid;
+ sth->type = thread_type;
+ sth->request_quit_callback = request_quit_callback;
+ sth->force_quit_callback = force_quit_callback;
+ sth->data = data;
+ os_thread_get_current_name_np(sth->name);
+ *PValue = sth;
+
+ switch(thread_type) {
+ default:
+ case SERVICE_THREAD_TYPE_NETDATA:
+ sth->netdata_thread = netdata_thread_self();
+ break;
+
+ case SERVICE_THREAD_TYPE_EVENT_LOOP:
+ case SERVICE_THREAD_TYPE_LIBUV:
+ sth->uv_thread = uv_thread_self();
+ break;
+ }
+ }
+ else {
+ sth = *PValue;
+ }
+ netdata_spinlock_unlock(&service_globals.lock);
+
+ return sth;
+}
+
+void service_exits(void) {
+ pid_t tid = gettid();
+
+ netdata_spinlock_lock(&service_globals.lock);
+ Pvoid_t *PValue = JudyLGet(service_globals.pid_judy, tid, PJE0);
+ if(PValue) {
+ freez(*PValue);
+ JudyLDel(&service_globals.pid_judy, tid, PJE0);
+ }
+ netdata_spinlock_unlock(&service_globals.lock);
+}
+
+bool service_running(SERVICE_TYPE service) {
+ static __thread SERVICE_THREAD *sth = NULL;
+
+ if(unlikely(!sth))
+ sth = service_register(SERVICE_THREAD_TYPE_NETDATA, NULL, NULL, NULL, false);
+
+ if(netdata_exit)
+ __atomic_store_n(&service_globals.running, 0, __ATOMIC_RELAXED);
+
+ if(service == 0)
+ service = sth->services;
+
+ sth->services |= service;
+
+ return ((__atomic_load_n(&service_globals.running, __ATOMIC_RELAXED) & service) == service);
+}
+
+void service_signal_exit(SERVICE_TYPE service) {
+ __atomic_and_fetch(&service_globals.running, ~(service), __ATOMIC_RELAXED);
+
+ netdata_spinlock_lock(&service_globals.lock);
+
+ Pvoid_t *PValue;
+ Word_t tid = 0;
+ bool first = true;
+ while((PValue = JudyLFirstThenNext(service_globals.pid_judy, &tid, &first))) {
+ SERVICE_THREAD *sth = *PValue;
+
+ if((sth->services & service) && sth->request_quit_callback) {
+ netdata_spinlock_unlock(&service_globals.lock);
+ sth->request_quit_callback(sth->data);
+ netdata_spinlock_lock(&service_globals.lock);
+ continue;
+ }
+ }
+
+ netdata_spinlock_unlock(&service_globals.lock);
+}
+
+static void service_to_buffer(BUFFER *wb, SERVICE_TYPE service) {
+ if(service & SERVICE_MAINTENANCE)
+ buffer_strcat(wb, "MAINTENANCE ");
+ if(service & SERVICE_COLLECTORS)
+ buffer_strcat(wb, "COLLECTORS ");
+ if(service & SERVICE_ML_TRAINING)
+ buffer_strcat(wb, "ML_TRAINING ");
+ if(service & SERVICE_ML_PREDICTION)
+ buffer_strcat(wb, "ML_PREDICTION ");
+ if(service & SERVICE_REPLICATION)
+ buffer_strcat(wb, "REPLICATION ");
+ if(service & ABILITY_DATA_QUERIES)
+ buffer_strcat(wb, "DATA_QUERIES ");
+ if(service & ABILITY_WEB_REQUESTS)
+ buffer_strcat(wb, "WEB_REQUESTS ");
+ if(service & SERVICE_WEB_SERVER)
+ buffer_strcat(wb, "WEB_SERVER ");
+ if(service & SERVICE_ACLK)
+ buffer_strcat(wb, "ACLK ");
+ if(service & SERVICE_HEALTH)
+ buffer_strcat(wb, "HEALTH ");
+ if(service & SERVICE_STREAMING)
+ buffer_strcat(wb, "STREAMING ");
+ if(service & ABILITY_STREAMING_CONNECTIONS)
+ buffer_strcat(wb, "STREAMING_CONNECTIONS ");
+ if(service & SERVICE_CONTEXT)
+ buffer_strcat(wb, "CONTEXT ");
+ if(service & SERVICE_ANALYTICS)
+ buffer_strcat(wb, "ANALYTICS ");
+ if(service & SERVICE_EXPORTERS)
+ buffer_strcat(wb, "EXPORTERS ");
+}
+
+static bool service_wait_exit(SERVICE_TYPE service, usec_t timeout_ut) {
+ BUFFER *service_list = buffer_create(1024, NULL);
+ BUFFER *thread_list = buffer_create(1024, NULL);
+ usec_t started_ut = now_monotonic_usec(), ended_ut;
+ size_t running;
+ SERVICE_TYPE running_services = 0;
+
+ // cancel the threads
+ running = 0;
+ running_services = 0;
+ {
+ buffer_flush(thread_list);
+
+ netdata_spinlock_lock(&service_globals.lock);
+
+ Pvoid_t *PValue;
+ Word_t tid = 0;
+ bool first = true;
+ while((PValue = JudyLFirstThenNext(service_globals.pid_judy, &tid, &first))) {
+ SERVICE_THREAD *sth = *PValue;
+ if(sth->services & service && sth->tid != gettid() && !sth->cancelled) {
+ sth->cancelled = true;
+
+ switch(sth->type) {
+ default:
+ case SERVICE_THREAD_TYPE_NETDATA:
+ netdata_thread_cancel(sth->netdata_thread);
+ break;
+
+ case SERVICE_THREAD_TYPE_EVENT_LOOP:
+ case SERVICE_THREAD_TYPE_LIBUV:
+ break;
+ }
+
+ if(running)
+ buffer_strcat(thread_list, ", ");
+
+ buffer_sprintf(thread_list, "'%s' (%d)", sth->name, sth->tid);
+
+ running++;
+ running_services |= sth->services & service;
+
+ if(sth->force_quit_callback) {
+ netdata_spinlock_unlock(&service_globals.lock);
+ sth->force_quit_callback(sth->data);
+ netdata_spinlock_lock(&service_globals.lock);
+ continue;
+ }
+ }
+ }
+
+ netdata_spinlock_unlock(&service_globals.lock);
+ }
+
+ service_signal_exit(service);
+
+ // signal them to stop
+ size_t last_running = 0;
+ size_t stale_time_ut = 0;
+ usec_t sleep_ut = 50 * USEC_PER_MS;
+ size_t log_countdown_ut = sleep_ut;
+ do {
+ if(running != last_running)
+ stale_time_ut = 0;
+
+ last_running = running;
+ running = 0;
+ running_services = 0;
+ buffer_flush(thread_list);
+
+ netdata_spinlock_lock(&service_globals.lock);
+
+ Pvoid_t *PValue;
+ Word_t tid = 0;
+ bool first = true;
+ while((PValue = JudyLFirstThenNext(service_globals.pid_judy, &tid, &first))) {
+ SERVICE_THREAD *sth = *PValue;
+ if(sth->services & service && sth->tid != gettid()) {
+ if(running)
+ buffer_strcat(thread_list, ", ");
+
+ buffer_sprintf(thread_list, "'%s' (%d)", sth->name, sth->tid);
+
+ running_services |= sth->services & service;
+ running++;
+ }
+ }
+
+ netdata_spinlock_unlock(&service_globals.lock);
+
+ if(running) {
+ log_countdown_ut -= (log_countdown_ut >= sleep_ut) ? sleep_ut : log_countdown_ut;
+ if(log_countdown_ut == 0 || running != last_running) {
+ log_countdown_ut = 20 * sleep_ut;
+
+ buffer_flush(service_list);
+ service_to_buffer(service_list, running_services);
+ info("SERVICE CONTROL: waiting for the following %zu services [ %s] to exit: %s",
+ running, buffer_tostring(service_list),
+ running <= 10 ? buffer_tostring(thread_list) : "");
+ }
+
+ sleep_usec(sleep_ut);
+ stale_time_ut += sleep_ut;
+ }
+
+ ended_ut = now_monotonic_usec();
+ } while(running && (ended_ut - started_ut < timeout_ut || stale_time_ut < timeout_ut));
+
+ if(running) {
+ buffer_flush(service_list);
+ service_to_buffer(service_list, running_services);
+ info("SERVICE CONTROL: "
+ "the following %zu service(s) [ %s] take too long to exit: %s; "
+ "giving up on them...",
+ running, buffer_tostring(service_list),
+ buffer_tostring(thread_list));
+ }
+
+ buffer_free(thread_list);
+ buffer_free(service_list);
+
+ return (running == 0);
+}
+
+#define delta_shutdown_time(msg) \
+ { \
+ usec_t now_ut = now_monotonic_usec(); \
+ if(prev_msg) \
+ info("NETDATA SHUTDOWN: in %7llu ms, %s%s - next: %s", (now_ut - last_ut) / USEC_PER_MS, (timeout)?"(TIMEOUT) ":"", prev_msg, msg); \
+ else \
+ info("NETDATA SHUTDOWN: next: %s", msg); \
+ last_ut = now_ut; \
+ prev_msg = msg; \
+ timeout = false; \
+ }
+
void netdata_cleanup_and_exit(int ret) {
- // enabling this, is wrong
- // because the threads will be cancelled while cleaning up
- // netdata_exit = 1;
+ usec_t started_ut = now_monotonic_usec();
+ usec_t last_ut = started_ut;
+ const char *prev_msg = NULL;
+ bool timeout = false;
error_log_limit_unlimited();
- info("EXIT: netdata prepares to exit with code %d...", ret);
+ info("NETDATA SHUTDOWN: initializing shutdown with code %d...", ret);
send_statistics("EXIT", ret?"ERROR":"OK","-");
+ delta_shutdown_time("create shutdown file");
+
char agent_crash_file[FILENAME_MAX + 1];
char agent_incomplete_shutdown_file[FILENAME_MAX + 1];
snprintfz(agent_crash_file, FILENAME_MAX, "%s/.agent_crash", netdata_configured_varlib_dir);
snprintfz(agent_incomplete_shutdown_file, FILENAME_MAX, "%s/.agent_incomplete_shutdown", netdata_configured_varlib_dir);
(void) rename(agent_crash_file, agent_incomplete_shutdown_file);
- // cleanup/save the database and exit
- info("EXIT: cleaning up the database...");
+#ifdef ENABLE_DBENGINE
+ if(dbengine_enabled) {
+ delta_shutdown_time("dbengine exit mode");
+ for (size_t tier = 0; tier < storage_tiers; tier++)
+ rrdeng_exit_mode(multidb_ctx[tier]);
+ }
+#endif
+
+ delta_shutdown_time("disable maintenance, new queries, new web requests, new streaming connections and aclk");
+
+ service_signal_exit(
+ SERVICE_MAINTENANCE
+ | ABILITY_DATA_QUERIES
+ | ABILITY_WEB_REQUESTS
+ | ABILITY_STREAMING_CONNECTIONS
+ | SERVICE_ACLK
+ );
+
+ delta_shutdown_time("stop replication, exporters, ML training, health and web servers threads");
+
+ timeout = !service_wait_exit(
+ SERVICE_REPLICATION
+ | SERVICE_EXPORTERS
+ | SERVICE_ML_TRAINING
+ | SERVICE_HEALTH
+ | SERVICE_WEB_SERVER
+ , 3 * USEC_PER_SEC);
+
+ delta_shutdown_time("stop collectors and streaming threads");
+
+ timeout = !service_wait_exit(
+ SERVICE_COLLECTORS
+ | SERVICE_STREAMING
+ , 3 * USEC_PER_SEC);
+
+ delta_shutdown_time("stop ML prediction and context threads");
+
+ timeout = !service_wait_exit(
+ SERVICE_ML_PREDICTION
+ | SERVICE_CONTEXT
+ , 3 * USEC_PER_SEC);
+
+ delta_shutdown_time("stop maintenance thread");
+
+ timeout = !service_wait_exit(
+ SERVICE_MAINTENANCE
+ , 3 * USEC_PER_SEC);
+
+ delta_shutdown_time("clean rrdhost database");
+
rrdhost_cleanup_all();
- if(!ret) {
- // exit cleanly
+ delta_shutdown_time("prepare metasync shutdown");
+
+ metadata_sync_shutdown_prepare();
- // stop everything
- info("EXIT: stopping static threads...");
#ifdef ENABLE_ACLK
- aclk_sync_exit_all();
+ delta_shutdown_time("signal aclk sync to stop");
+ aclk_sync_exit_all();
#endif
- cancel_main_threads();
- // free the database
- info("EXIT: freeing database memory...");
+ delta_shutdown_time("stop aclk threads");
+
+ timeout = !service_wait_exit(
+ SERVICE_ACLK
+ , 3 * USEC_PER_SEC);
+
+ delta_shutdown_time("stop all remaining worker threads");
+
+ timeout = !service_wait_exit(~0, 10 * USEC_PER_SEC);
+
+ delta_shutdown_time("cancel main threads");
+
+ cancel_main_threads();
+
+ if(!ret) {
+ // exit cleanly
+
#ifdef ENABLE_DBENGINE
if(dbengine_enabled) {
+ delta_shutdown_time("flush dbengine tiers");
for (size_t tier = 0; tier < storage_tiers; tier++)
rrdeng_prepare_exit(multidb_ctx[tier]);
}
#endif
- metadata_sync_shutdown_prepare();
- rrdhost_free_all();
+
+ // free the database
+ delta_shutdown_time("stop collection for all hosts");
+
+ // rrdhost_free_all();
+ rrd_finalize_collection_for_all_hosts();
+
+ delta_shutdown_time("stop metasync threads");
+
metadata_sync_shutdown();
+
#ifdef ENABLE_DBENGINE
if(dbengine_enabled) {
+ delta_shutdown_time("wait for dbengine collectors to finish");
+
+ size_t running = 1;
+ while(running) {
+ running = 0;
+ for (size_t tier = 0; tier < storage_tiers; tier++)
+ running += rrdeng_collectors_running(multidb_ctx[tier]);
+
+ if(running)
+ sleep_usec(100 * USEC_PER_MS);
+ }
+
+ delta_shutdown_time("wait for dbengine main cache to finish flushing");
+
+ while (pgc_hot_and_dirty_entries(main_cache)) {
+ pgc_flush_all_hot_and_dirty_pages(main_cache, PGC_SECTION_ALL);
+ sleep_usec(100 * USEC_PER_MS);
+ }
+
+ delta_shutdown_time("stop dbengine tiers");
for (size_t tier = 0; tier < storage_tiers; tier++)
rrdeng_exit(multidb_ctx[tier]);
}
#endif
}
+
+ delta_shutdown_time("close SQL context db");
+
sql_close_context_database();
+
+ delta_shutdown_time("closed SQL main db");
+
sql_close_database();
// unlink the pid
if(pidfile[0]) {
- info("EXIT: removing netdata PID file '%s'...", pidfile);
+ delta_shutdown_time("remove pid file");
+
if(unlink(pidfile) != 0)
error("EXIT: cannot unlink pidfile '%s'.", pidfile);
}
#ifdef ENABLE_HTTPS
+ delta_shutdown_time("free openssl structures");
security_clean_openssl();
#endif
- info("EXIT: all done - netdata is now exiting - bye bye...");
+
+ delta_shutdown_time("remove incomplete shutdown file");
+
(void) unlink(agent_incomplete_shutdown_file);
+
+ delta_shutdown_time("exit");
+
+ usec_t ended_ut = now_monotonic_usec();
+ info("NETDATA SHUTDOWN: completed in %llu ms - netdata is now exiting - bye bye...", (ended_ut - started_ut) / USEC_PER_MS);
exit(ret);
}
@@ -225,6 +623,32 @@ int killpid(pid_t pid) {
return ret;
}
+static void set_nofile_limit(struct rlimit *rl) {
+ // get the num files allowed
+ if(getrlimit(RLIMIT_NOFILE, rl) != 0) {
+ error("getrlimit(RLIMIT_NOFILE) failed");
+ return;
+ }
+
+ info("resources control: allowed file descriptors: soft = %zu, max = %zu",
+ (size_t) rl->rlim_cur, (size_t) rl->rlim_max);
+
+ // make the soft/hard limits equal
+ rl->rlim_cur = rl->rlim_max;
+ if (setrlimit(RLIMIT_NOFILE, rl) != 0) {
+ error("setrlimit(RLIMIT_NOFILE, { %zu, %zu }) failed", (size_t)rl->rlim_cur, (size_t)rl->rlim_max);
+ }
+
+ // sanity check to make sure we have enough file descriptors available to open
+ if (getrlimit(RLIMIT_NOFILE, rl) != 0) {
+ error("getrlimit(RLIMIT_NOFILE) failed");
+ return;
+ }
+
+ if (rl->rlim_cur < 1024)
+ error("Number of open file descriptors allowed for this process is too low (RLIMIT_NOFILE=%zu)", (size_t)rl->rlim_cur);
+}
+
void cancel_main_threads() {
error_log_limit_unlimited();
@@ -408,6 +832,9 @@ static void log_init(void) {
snprintfz(filename, FILENAME_MAX, "%s/error.log", netdata_configured_log_dir);
stderr_filename = config_get(CONFIG_SECTION_LOGS, "error", filename);
+ snprintfz(filename, FILENAME_MAX, "%s/collector.log", netdata_configured_log_dir);
+ stdcollector_filename = config_get(CONFIG_SECTION_LOGS, "collector", filename);
+
snprintfz(filename, FILENAME_MAX, "%s/access.log", netdata_configured_log_dir);
stdaccess_filename = config_get(CONFIG_SECTION_LOGS, "access", filename);
@@ -679,8 +1106,9 @@ static void get_netdata_configured_variables() {
// ------------------------------------------------------------------------
// get default Database Engine page cache size in MiB
- db_engine_use_malloc = config_get_boolean(CONFIG_SECTION_DB, "dbengine page cache with malloc", CONFIG_BOOLEAN_YES);
default_rrdeng_page_cache_mb = (int) config_get_number(CONFIG_SECTION_DB, "dbengine page cache size MB", default_rrdeng_page_cache_mb);
+ db_engine_journal_check = config_get_boolean(CONFIG_SECTION_DB, "dbengine enable journal integrity check", CONFIG_BOOLEAN_NO);
+
if(default_rrdeng_page_cache_mb < RRDENG_MIN_PAGE_CACHE_SIZE_MB) {
error("Invalid page cache size %d given. Defaulting to %d.", default_rrdeng_page_cache_mb, RRDENG_MIN_PAGE_CACHE_SIZE_MB);
default_rrdeng_page_cache_mb = RRDENG_MIN_PAGE_CACHE_SIZE_MB;
@@ -731,14 +1159,14 @@ static void get_netdata_configured_variables() {
// --------------------------------------------------------------------
- rrdset_free_obsolete_time = config_get_number(CONFIG_SECTION_DB, "cleanup obsolete charts after secs", rrdset_free_obsolete_time);
+ rrdset_free_obsolete_time_s = config_get_number(CONFIG_SECTION_DB, "cleanup obsolete charts after secs", rrdset_free_obsolete_time_s);
// Current chart locking and invalidation scheme doesn't prevent Netdata from segmentation faults if a short
// cleanup delay is set. Extensive stress tests showed that 10 seconds is quite a safe delay. Look at
// https://github.com/netdata/netdata/pull/11222#issuecomment-868367920 for more information.
- if (rrdset_free_obsolete_time < 10) {
- rrdset_free_obsolete_time = 10;
+ if (rrdset_free_obsolete_time_s < 10) {
+ rrdset_free_obsolete_time_s = 10;
info("The \"cleanup obsolete charts after seconds\" option was set to 10 seconds.");
- config_set_number(CONFIG_SECTION_DB, "cleanup obsolete charts after secs", rrdset_free_obsolete_time);
+ config_set_number(CONFIG_SECTION_DB, "cleanup obsolete charts after secs", rrdset_free_obsolete_time_s);
}
gap_when_lost_iterations_above = (int)config_get_number(CONFIG_SECTION_DB, "gap when lost iterations above", gap_when_lost_iterations_above);
@@ -746,12 +1174,13 @@ static void get_netdata_configured_variables() {
gap_when_lost_iterations_above = 1;
config_set_number(CONFIG_SECTION_DB, "gap when lost iterations above", gap_when_lost_iterations_above);
}
+ gap_when_lost_iterations_above += 2;
// --------------------------------------------------------------------
// get various system parameters
get_system_HZ();
- get_system_cpus();
+ get_system_cpus_uncached();
get_system_pid_max();
@@ -874,7 +1303,30 @@ void post_conf_load(char **user)
appconfig_get(&cloud_config, CONFIG_SECTION_GLOBAL, "cloud base url", DEFAULT_CLOUD_BASE_URL);
}
+#define delta_startup_time(msg) \
+ { \
+ usec_t now_ut = now_monotonic_usec(); \
+ if(prev_msg) \
+ info("NETDATA STARTUP: in %7llu ms, %s - next: %s", (now_ut - last_ut) / USEC_PER_MS, prev_msg, msg); \
+ else \
+ info("NETDATA STARTUP: next: %s", msg); \
+ last_ut = now_ut; \
+ prev_msg = msg; \
+ }
+
+int pgc_unittest(void);
+int mrg_unittest(void);
+int julytest(void);
+
int main(int argc, char **argv) {
+ // initialize the system clocks
+ clocks_init();
+ usec_t started_ut = now_monotonic_usec();
+ usec_t last_ut = started_ut;
+ const char *prev_msg = NULL;
+ // Initialize stderror avoiding coredump when info() or error() is called
+ stderror = stderr;
+
int i;
int config_loaded = 0;
int dont_fork = 0;
@@ -1001,7 +1453,7 @@ int main(int argc, char **argv) {
default_health_enabled = 0;
storage_tiers = 1;
registry_init();
- if(rrd_init("unittest", NULL)) {
+ if(rrd_init("unittest", NULL, true)) {
fprintf(stderr, "rrd_init failed for unittest\n");
return 1;
}
@@ -1027,11 +1479,6 @@ int main(int argc, char **argv) {
else if(strcmp(optarg, "escapetest") == 0) {
return command_argument_sanitization_tests();
}
-#ifdef ENABLE_ML_TESTS
- else if(strcmp(optarg, "mltest") == 0) {
- return test_ml(argc, argv);
- }
-#endif
#ifdef ENABLE_DBENGINE
else if(strcmp(optarg, "mctest") == 0) {
unittest_running = true;
@@ -1061,6 +1508,18 @@ int main(int argc, char **argv) {
unittest_running = true;
return metadata_unittest();
}
+ else if(strcmp(optarg, "pgctest") == 0) {
+ unittest_running = true;
+ return pgc_unittest();
+ }
+ else if(strcmp(optarg, "mrgtest") == 0) {
+ unittest_running = true;
+ return mrg_unittest();
+ }
+ else if(strcmp(optarg, "julytest") == 0) {
+ unittest_running = true;
+ return julytest();
+ }
else if(strncmp(optarg, createdataset_string, strlen(createdataset_string)) == 0) {
optarg += strlen(createdataset_string);
unsigned history_seconds = strtoul(optarg, NULL, 0);
@@ -1304,19 +1763,14 @@ int main(int argc, char **argv) {
}
}
-#ifdef _SC_OPEN_MAX
if (close_open_fds == true) {
// close all open file descriptors, except the standard ones
// the caller may have left open files (lxc-attach has this issue)
- for(int fd = (int) (sysconf(_SC_OPEN_MAX) - 1); fd > 2; fd--)
- if(fd_is_valid(fd))
- close(fd);
+ for_each_open_fd(OPEN_FD_ACTION_CLOSE, OPEN_FD_EXCLUDE_STDIN | OPEN_FD_EXCLUDE_STDOUT | OPEN_FD_EXCLUDE_STDERR);
}
-#endif
- if(!config_loaded)
- {
+ if(!config_loaded) {
load_netdata_conf(NULL, 0);
post_conf_load(&user);
load_cloud_conf(0);
@@ -1327,7 +1781,6 @@ int main(int argc, char **argv) {
appconfig_set(&cloud_config, CONFIG_SECTION_GLOBAL, "enabled", "false");
}
-
// ------------------------------------------------------------------------
// initialize netdata
{
@@ -1347,12 +1800,29 @@ int main(int argc, char **argv) {
#endif
#endif
- // initialize the system clocks
- clocks_init();
+ // set libuv worker threads
+ libuv_worker_threads = (int)get_netdata_cpus() * 2;
- // prepare configuration environment variables for the plugins
+ if(libuv_worker_threads < MIN_LIBUV_WORKER_THREADS)
+ libuv_worker_threads = MIN_LIBUV_WORKER_THREADS;
- setenv("UV_THREADPOOL_SIZE", config_get(CONFIG_SECTION_GLOBAL, "libuv worker threads", "16"), 1);
+ if(libuv_worker_threads > MAX_LIBUV_WORKER_THREADS)
+ libuv_worker_threads = MAX_LIBUV_WORKER_THREADS;
+
+
+ libuv_worker_threads = config_get_number(CONFIG_SECTION_GLOBAL, "libuv worker threads", libuv_worker_threads);
+ if(libuv_worker_threads < MIN_LIBUV_WORKER_THREADS) {
+ libuv_worker_threads = MIN_LIBUV_WORKER_THREADS;
+ config_set_number(CONFIG_SECTION_GLOBAL, "libuv worker threads", libuv_worker_threads);
+ }
+
+ {
+ char buf[20 + 1];
+ snprintfz(buf, 20, "%d", libuv_worker_threads);
+ setenv("UV_THREADPOOL_SIZE", buf, 1);
+ }
+
+ // prepare configuration environment variables for the plugins
get_netdata_configured_variables();
set_global_environment();
@@ -1396,6 +1866,8 @@ int main(int argc, char **argv) {
// initialize the log files
open_all_log_files();
+ aral_judy_init();
+
get_system_timezone();
// --------------------------------------------------------------------
@@ -1414,6 +1886,7 @@ int main(int argc, char **argv) {
// --------------------------------------------------------------------
// Initialize ML configuration
+ delta_startup_time("initialize ML");
ml_init();
// --------------------------------------------------------------------
@@ -1422,19 +1895,18 @@ int main(int argc, char **argv) {
// block signals while initializing threads.
// this causes the threads to block signals.
+ delta_startup_time("initialize signals");
signals_block();
+ signals_init(); // setup the signals we want to use
- // setup the signals we want to use
+ // --------------------------------------------------------------------
+ // check which threads are enabled and initialize them
- signals_init();
+ delta_startup_time("initialize static threads");
// setup threads configs
default_stacksize = netdata_threads_init();
-
- // --------------------------------------------------------------------
- // check which threads are enabled and initialize them
-
for (i = 0; static_threads[i].name != NULL ; i++) {
struct netdata_static_thread *st = &static_threads[i];
@@ -1454,14 +1926,17 @@ int main(int argc, char **argv) {
// --------------------------------------------------------------------
// create the listening sockets
+ delta_startup_time("initialize web server");
+
web_client_api_v1_init();
web_server_threading_selection();
if(web_server_mode != WEB_SERVER_MODE_NONE)
api_listen_sockets_setup();
-
}
+ delta_startup_time("set resource limits");
+
#ifdef NETDATA_INTERNAL_CHECKS
if(debug_flags != 0) {
struct rlimit rl = { RLIM_INFINITY, RLIM_INFINITY };
@@ -1473,11 +1948,9 @@ int main(int argc, char **argv) {
}
#endif /* NETDATA_INTERNAL_CHECKS */
- // get the max file limit
- if(getrlimit(RLIMIT_NOFILE, &rlimit_nofile) != 0)
- error("getrlimit(RLIMIT_NOFILE) failed");
- else
- info("resources control: allowed file descriptors: soft = %zu, max = %zu", (size_t)rlimit_nofile.rlim_cur, (size_t)rlimit_nofile.rlim_max);
+ set_nofile_limit(&rlimit_nofile);
+
+ delta_startup_time("become daemon");
// fork, switch user, create pid file, set process priority
if(become_daemon(dont_fork, user) == -1)
@@ -1485,12 +1958,18 @@ int main(int argc, char **argv) {
info("netdata started on pid %d.", getpid());
+ delta_startup_time("initialize threads after fork");
+
netdata_threads_init_after_fork((size_t)config_get_number(CONFIG_SECTION_GLOBAL, "pthread stack size", (long)default_stacksize));
// initialize internal registry
+ delta_startup_time("initialize registry");
registry_init();
+
// fork the spawn server
+ delta_startup_time("fork the spawn server");
spawn_init();
+
/*
* Libuv uv_spawn() uses SIGCHLD internally:
* https://github.com/libuv/libuv/blob/cc51217a317e96510fbb284721d5e6bc2af31e33/src/unix/process.c#L485
@@ -1503,15 +1982,22 @@ int main(int argc, char **argv) {
// ------------------------------------------------------------------------
// initialize rrd, registry, health, rrdpush, etc.
+ delta_startup_time("collecting system info");
+
netdata_anonymous_statistics_enabled=-1;
struct rrdhost_system_info *system_info = callocz(1, sizeof(struct rrdhost_system_info));
+ __atomic_sub_fetch(&netdata_buffers_statistics.rrdhost_allocations_size, sizeof(struct rrdhost_system_info), __ATOMIC_RELAXED);
get_system_info(system_info);
system_info->hops = 0;
get_install_type(&system_info->install_type, &system_info->prebuilt_arch, &system_info->prebuilt_dist);
- if(rrd_init(netdata_configured_hostname, system_info))
+ delta_startup_time("initialize RRD structures");
+
+ if(rrd_init(netdata_configured_hostname, system_info, false))
fatal("Cannot initialize localhost instance with name '%s'.", netdata_configured_hostname);
+ delta_startup_time("check for incomplete shutdown");
+
char agent_crash_file[FILENAME_MAX + 1];
char agent_incomplete_shutdown_file[FILENAME_MAX + 1];
snprintfz(agent_incomplete_shutdown_file, FILENAME_MAX, "%s/.agent_incomplete_shutdown", netdata_configured_varlib_dir);
@@ -1526,6 +2012,8 @@ int main(int argc, char **argv) {
// ------------------------------------------------------------------------
// Claim netdata agent to a cloud endpoint
+ delta_startup_time("collect claiming info");
+
if (claiming_pending_arguments)
claim_agent(claiming_pending_arguments);
load_claiming_state();
@@ -1536,11 +2024,14 @@ int main(int argc, char **argv) {
error_log_limit_reset();
// Load host labels
+ delta_startup_time("collect host labels");
reload_host_labels();
// ------------------------------------------------------------------------
// spawn the threads
+ delta_startup_time("start the static threads");
+
web_server_config_options();
netdata_zero_metrics_enabled = config_get_boolean_ondemand(CONFIG_SECTION_DB, "enable zero metrics", CONFIG_BOOLEAN_NO);
@@ -1561,9 +2052,14 @@ int main(int argc, char **argv) {
// ------------------------------------------------------------------------
// Initialize netdata agent command serving from cli and signals
+ delta_startup_time("initialize commands API");
+
commands_init();
- info("netdata initialization completed. Enjoy real-time performance monitoring!");
+ delta_startup_time("ready");
+
+ usec_t ready_ut = now_monotonic_usec();
+ info("NETDATA STARTUP: completed in %llu ms. Enjoy real-time performance monitoring!", (ready_ut - started_ut) / USEC_PER_MS);
netdata_ready = 1;
send_statistics("START", "-", "-");
diff --git a/daemon/main.h b/daemon/main.h
index a4e2b3aa..8704d609 100644
--- a/daemon/main.h
+++ b/daemon/main.h
@@ -27,4 +27,35 @@ int killpid(pid_t pid);
void netdata_cleanup_and_exit(int ret) NORETURN;
void send_statistics(const char *action, const char *action_result, const char *action_data);
+typedef enum {
+ ABILITY_DATA_QUERIES = (1 << 0),
+ ABILITY_WEB_REQUESTS = (1 << 1),
+ ABILITY_STREAMING_CONNECTIONS = (1 << 2),
+ SERVICE_MAINTENANCE = (1 << 3),
+ SERVICE_COLLECTORS = (1 << 4),
+ SERVICE_ML_TRAINING = (1 << 5),
+ SERVICE_ML_PREDICTION = (1 << 6),
+ SERVICE_REPLICATION = (1 << 7),
+ SERVICE_WEB_SERVER = (1 << 8),
+ SERVICE_ACLK = (1 << 9),
+ SERVICE_HEALTH = (1 << 10),
+ SERVICE_STREAMING = (1 << 11),
+ SERVICE_CONTEXT = (1 << 12),
+ SERVICE_ANALYTICS = (1 << 13),
+ SERVICE_EXPORTERS = (1 << 14),
+} SERVICE_TYPE;
+
+typedef enum {
+ SERVICE_THREAD_TYPE_NETDATA,
+ SERVICE_THREAD_TYPE_LIBUV,
+ SERVICE_THREAD_TYPE_EVENT_LOOP,
+} SERVICE_THREAD_TYPE;
+
+typedef void (*force_quit_t)(void *data);
+typedef void (*request_quit_t)(void *data);
+
+void service_exits(void);
+bool service_running(SERVICE_TYPE service);
+struct service_thread *service_register(SERVICE_THREAD_TYPE thread_type, request_quit_t request_quit_callback, force_quit_t force_quit_callback, void *data, bool update __maybe_unused);
+
#endif /* NETDATA_MAIN_H */
diff --git a/daemon/service.c b/daemon/service.c
index 6db2ef69..9761abd0 100644
--- a/daemon/service.c
+++ b/daemon/service.c
@@ -22,6 +22,10 @@
#define WORKER_JOB_SAVE_CHART 13
#define WORKER_JOB_DELETE_CHART 14
#define WORKER_JOB_FREE_DIMENSION 15
+#define WORKER_JOB_PGC_MAIN_EVICT 16
+#define WORKER_JOB_PGC_MAIN_FLUSH 17
+#define WORKER_JOB_PGC_OPEN_EVICT 18
+#define WORKER_JOB_PGC_OPEN_FLUSH 19
static void svc_rrddim_obsolete_to_archive(RRDDIM *rd) {
RRDSET *st = rd->rrdset;
@@ -48,13 +52,13 @@ static void svc_rrddim_obsolete_to_archive(RRDDIM *rd) {
size_t tiers_available = 0, tiers_said_no_retention = 0;
for(size_t tier = 0; tier < storage_tiers ;tier++) {
- if(rd->tiers[tier]) {
+ if(rd->tiers[tier].db_collection_handle) {
tiers_available++;
- if(rd->tiers[tier]->collect_ops->finalize(rd->tiers[tier]->db_collection_handle))
+ if(rd->tiers[tier].collect_ops->finalize(rd->tiers[tier].db_collection_handle))
tiers_said_no_retention++;
- rd->tiers[tier]->db_collection_handle = NULL;
+ rd->tiers[tier].db_collection_handle = NULL;
}
}
@@ -83,7 +87,7 @@ static bool svc_rrdset_archive_obsolete_dimensions(RRDSET *st, bool all_dimensio
dfe_start_write(st->rrddim_root_index, rd) {
if(unlikely(
all_dimensions ||
- (rrddim_flag_check(rd, RRDDIM_FLAG_OBSOLETE) && (rd->last_collected_time.tv_sec + rrdset_free_obsolete_time < now))
+ (rrddim_flag_check(rd, RRDDIM_FLAG_OBSOLETE) && (rd->last_collected_time.tv_sec + rrdset_free_obsolete_time_s < now))
)) {
if(dictionary_acquired_item_references(rd_dfe.item) == 1) {
@@ -142,9 +146,9 @@ static void svc_rrdhost_cleanup_obsolete_charts(RRDHOST *host) {
continue;
if(unlikely(rrdset_flag_check(st, RRDSET_FLAG_OBSOLETE)
- && st->last_accessed_time + rrdset_free_obsolete_time < now
- && st->last_updated.tv_sec + rrdset_free_obsolete_time < now
- && st->last_collected_time.tv_sec + rrdset_free_obsolete_time < now
+ && st->last_accessed_time_s + rrdset_free_obsolete_time_s < now
+ && st->last_updated.tv_sec + rrdset_free_obsolete_time_s < now
+ && st->last_collected_time.tv_sec + rrdset_free_obsolete_time_s < now
)) {
svc_rrdset_obsolete_to_archive(st);
}
@@ -166,10 +170,10 @@ static void svc_rrdset_check_obsoletion(RRDHOST *host) {
if(rrdset_is_replicating(st))
continue;
- last_entry_t = rrdset_last_entry_t(st);
+ last_entry_t = rrdset_last_entry_s(st);
- if(last_entry_t && last_entry_t < host->senders_connect_time &&
- host->senders_connect_time + TIME_TO_RUN_OBSOLETIONS_ON_CHILD_CONNECT + ITERATIONS_TO_RUN_OBSOLETIONS_ON_CHILD_CONNECT * st->update_every
+ if(last_entry_t && last_entry_t < host->child_connect_time &&
+ host->child_connect_time + TIME_TO_RUN_OBSOLETIONS_ON_CHILD_CONNECT + ITERATIONS_TO_RUN_OBSOLETIONS_ON_CHILD_CONNECT * st->update_every
< now)
rrdset_is_obsolete(st);
@@ -196,10 +200,10 @@ static void svc_rrd_cleanup_obsolete_charts_from_all_hosts() {
&& host->trigger_chart_obsoletion_check
&& (
(
- host->senders_last_chart_command
- && host->senders_last_chart_command + host->health_delay_up_to < now_realtime_sec()
+ host->child_last_chart_command
+ && host->child_last_chart_command + host->health.health_delay_up_to < now_realtime_sec()
)
- || (host->senders_connect_time + TIME_TO_RUN_OBSOLETIONS_ON_CHILD_CONNECT < now_realtime_sec())
+ || (host->child_connect_time + TIME_TO_RUN_OBSOLETIONS_ON_CHILD_CONNECT < now_realtime_sec())
)
) {
svc_rrdset_check_obsoletion(host);
@@ -238,7 +242,7 @@ restart_after_removal:
}
worker_is_busy(WORKER_JOB_FREE_HOST);
- rrdhost_free(host, 0);
+ rrdhost_free___while_having_rrd_wrlock(host, false);
goto restart_after_removal;
}
@@ -276,6 +280,10 @@ void *service_main(void *ptr)
worker_register_job_name(WORKER_JOB_SAVE_CHART, "save chart");
worker_register_job_name(WORKER_JOB_DELETE_CHART, "delete chart");
worker_register_job_name(WORKER_JOB_FREE_DIMENSION, "free dimension");
+ worker_register_job_name(WORKER_JOB_PGC_MAIN_EVICT, "main cache evictions");
+ worker_register_job_name(WORKER_JOB_PGC_MAIN_FLUSH, "main cache flushes");
+ worker_register_job_name(WORKER_JOB_PGC_OPEN_EVICT, "open cache evictions");
+ worker_register_job_name(WORKER_JOB_PGC_OPEN_FLUSH, "open cache flushes");
netdata_thread_cleanup_push(service_main_cleanup, ptr);
heartbeat_t hb;
@@ -284,7 +292,7 @@ void *service_main(void *ptr)
debug(D_SYSTEM, "Service thread starts");
- while (!netdata_exit) {
+ while (service_running(SERVICE_MAINTENANCE)) {
worker_is_idle();
heartbeat_next(&hb, step);
diff --git a/daemon/static_threads.c b/daemon/static_threads.c
index b7730bc3..d93cfe9d 100644
--- a/daemon/static_threads.c
+++ b/daemon/static_threads.c
@@ -7,6 +7,7 @@ void *analytics_main(void *ptr);
void *cpuidlejitter_main(void *ptr);
void *global_statistics_main(void *ptr);
void *global_statistics_workers_main(void *ptr);
+void *global_statistics_sqlite3_main(void *ptr);
void *health_main(void *ptr);
void *pluginsd_main(void *ptr);
void *service_main(void *ptr);
@@ -18,7 +19,7 @@ extern bool global_statistics_enabled;
const struct netdata_static_thread static_threads_common[] = {
{
- .name = "PLUGIN[timex]",
+ .name = "P[timex]",
.config_section = CONFIG_SECTION_PLUGINS,
.config_name = "timex",
.enabled = 1,
@@ -27,7 +28,7 @@ const struct netdata_static_thread static_threads_common[] = {
.start_routine = timex_main
},
{
- .name = "PLUGIN[idlejitter]",
+ .name = "P[idlejitter]",
.config_section = CONFIG_SECTION_PLUGINS,
.config_name = "idlejitter",
.enabled = 1,
@@ -36,6 +37,15 @@ const struct netdata_static_thread static_threads_common[] = {
.start_routine = cpuidlejitter_main
},
{
+ .name = "HEALTH",
+ .config_section = NULL,
+ .config_name = NULL,
+ .enabled = 1,
+ .thread = NULL,
+ .init_routine = NULL,
+ .start_routine = health_main
+ },
+ {
.name = "ANALYTICS",
.config_section = NULL,
.config_name = NULL,
@@ -45,7 +55,7 @@ const struct netdata_static_thread static_threads_common[] = {
.start_routine = analytics_main
},
{
- .name = "GLOBAL_STATS",
+ .name = "STATS_GLOBAL",
.config_section = CONFIG_SECTION_PLUGINS,
.config_name = "netdata monitoring",
.env_name = "NETDATA_INTERNALS_MONITORING",
@@ -56,7 +66,7 @@ const struct netdata_static_thread static_threads_common[] = {
.start_routine = global_statistics_main
},
{
- .name = "WORKERS_STATS",
+ .name = "STATS_WORKERS",
.config_section = CONFIG_SECTION_PLUGINS,
.config_name = "netdata monitoring",
.env_name = "NETDATA_INTERNALS_MONITORING",
@@ -67,6 +77,17 @@ const struct netdata_static_thread static_threads_common[] = {
.start_routine = global_statistics_workers_main
},
{
+ .name = "STATS_SQLITE3",
+ .config_section = CONFIG_SECTION_PLUGINS,
+ .config_name = "netdata monitoring",
+ .env_name = "NETDATA_INTERNALS_MONITORING",
+ .global_variable = &global_statistics_enabled,
+ .enabled = 1,
+ .thread = NULL,
+ .init_routine = NULL,
+ .start_routine = global_statistics_sqlite3_main
+ },
+ {
.name = "PLUGINSD",
.config_section = NULL,
.config_name = NULL,
@@ -85,7 +106,7 @@ const struct netdata_static_thread static_threads_common[] = {
.start_routine = service_main
},
{
- .name = "STATSD",
+ .name = "STATSD_FLUSH",
.config_section = NULL,
.config_name = NULL,
.enabled = 1,
@@ -103,7 +124,7 @@ const struct netdata_static_thread static_threads_common[] = {
.start_routine = exporting_main
},
{
- .name = "STREAM",
+ .name = "SNDR[localhost]",
.config_section = NULL,
.config_name = NULL,
.enabled = 0,
@@ -112,7 +133,7 @@ const struct netdata_static_thread static_threads_common[] = {
.start_routine = rrdpush_sender_thread
},
{
- .name = "WEB_SERVER[static1]",
+ .name = "WEB[1]",
.config_section = NULL,
.config_name = NULL,
.enabled = 0,
@@ -123,7 +144,7 @@ const struct netdata_static_thread static_threads_common[] = {
#ifdef ENABLE_ACLK
{
- .name = "ACLK_Main",
+ .name = "ACLK_MAIN",
.config_section = NULL,
.config_name = NULL,
.enabled = 1,
@@ -144,13 +165,13 @@ const struct netdata_static_thread static_threads_common[] = {
},
{
- .name = "REPLICATION",
- .config_section = NULL,
- .config_name = NULL,
- .enabled = 1,
- .thread = NULL,
- .init_routine = NULL,
- .start_routine = replication_thread_main
+ .name = "REPLAY[1]",
+ .config_section = NULL,
+ .config_name = NULL,
+ .enabled = 1,
+ .thread = NULL,
+ .init_routine = NULL,
+ .start_routine = replication_thread_main
},
// terminator
diff --git a/daemon/static_threads_freebsd.c b/daemon/static_threads_freebsd.c
index 48066bff..cc150faf 100644
--- a/daemon/static_threads_freebsd.c
+++ b/daemon/static_threads_freebsd.c
@@ -6,7 +6,7 @@ extern void *freebsd_main(void *ptr);
const struct netdata_static_thread static_threads_freebsd[] = {
{
- .name = "PLUGIN[freebsd]",
+ .name = "P[freebsd]",
.config_section = CONFIG_SECTION_PLUGINS,
.config_name = "freebsd",
.enabled = 1,
diff --git a/daemon/static_threads_linux.c b/daemon/static_threads_linux.c
index 260b2c17..54307ecc 100644
--- a/daemon/static_threads_linux.c
+++ b/daemon/static_threads_linux.c
@@ -10,7 +10,7 @@ extern void *timex_main(void *ptr);
const struct netdata_static_thread static_threads_linux[] = {
{
- .name = "PLUGIN[tc]",
+ .name = "P[tc]",
.config_section = CONFIG_SECTION_PLUGINS,
.config_name = "tc",
.enabled = 1,
@@ -19,7 +19,7 @@ const struct netdata_static_thread static_threads_linux[] = {
.start_routine = tc_main
},
{
- .name = "PLUGIN[diskspace]",
+ .name = "P[diskspace]",
.config_section = CONFIG_SECTION_PLUGINS,
.config_name = "diskspace",
.enabled = 1,
@@ -28,7 +28,7 @@ const struct netdata_static_thread static_threads_linux[] = {
.start_routine = diskspace_main
},
{
- .name = "PLUGIN[proc]",
+ .name = "P[proc]",
.config_section = CONFIG_SECTION_PLUGINS,
.config_name = "proc",
.enabled = 1,
@@ -37,7 +37,7 @@ const struct netdata_static_thread static_threads_linux[] = {
.start_routine = proc_main
},
{
- .name = "PLUGIN[cgroups]",
+ .name = "P[cgroups]",
.config_section = CONFIG_SECTION_PLUGINS,
.config_name = "cgroups",
.enabled = 1,
diff --git a/daemon/static_threads_macos.c b/daemon/static_threads_macos.c
index 72c03245..aaf7df6f 100644
--- a/daemon/static_threads_macos.c
+++ b/daemon/static_threads_macos.c
@@ -6,7 +6,7 @@ extern void *macos_main(void *ptr);
const struct netdata_static_thread static_threads_macos[] = {
{
- .name = "PLUGIN[macos]",
+ .name = "P[macos]",
.config_section = CONFIG_SECTION_PLUGINS,
.config_name = "macos",
.enabled = 1,
diff --git a/daemon/system-info.sh b/daemon/system-info.sh
index 68cdc481..1e334a3d 100755
--- a/daemon/system-info.sh
+++ b/daemon/system-info.sh
@@ -217,6 +217,9 @@ if [ -n "${lscpu}" ] && lscpu > /dev/null 2>&1; then
LCPU_COUNT="$(echo "${lscpu_output}" | grep "^CPU(s):" | cut -f 2 -d ':' | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')"
CPU_VENDOR="$(echo "${lscpu_output}" | grep "^Vendor ID:" | cut -f 2 -d ':' | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')"
CPU_MODEL="$(echo "${lscpu_output}" | grep "^Model name:" | cut -f 2 -d ':' | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')"
+ if grep -q "^lxcfs /proc" /proc/self/mounts 2>/dev/null && count=$(grep -c ^processor /proc/cpuinfo 2>/dev/null); then
+ LCPU_COUNT="$count"
+ fi
possible_cpu_freq="$(echo "${lscpu_output}" | grep -F "CPU max MHz:" | cut -f 2 -d ':' | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//' | grep -o '^[0-9]*')"
if [ -z "$possible_cpu_freq" ]; then
possible_cpu_freq="$(echo "${lscpu_output}" | grep -F "CPU MHz:" | cut -f 2 -d ':' | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//' | grep -o '^[0-9]*')"
@@ -437,7 +440,7 @@ CLOUD_INSTANCE_TYPE="unknown"
CLOUD_INSTANCE_REGION="unknown"
if [ "${VIRTUALIZATION}" != "none" ] && command -v curl > /dev/null 2>&1; then
- # Returned HTTP status codes: GCP is 200, AWS is 200, DO is 404.
+ # Returned HTTP status codes: GCP is 200, AWS is 200, DO is 404.
curl --fail -s -m 1 --noproxy "*" http://169.254.169.254 >/dev/null 2>&1
ret=$?
# anything but operation timeout.
diff --git a/daemon/unit_test.c b/daemon/unit_test.c
index f6986186..52b55c4e 100644
--- a/daemon/unit_test.c
+++ b/daemon/unit_test.c
@@ -439,7 +439,7 @@ int unit_test_str2ld() {
}
int unit_test_buffer() {
- BUFFER *wb = buffer_create(1);
+ BUFFER *wb = buffer_create(1, NULL);
char string[2048 + 1];
char final[9000 + 1];
int i;
@@ -1289,7 +1289,7 @@ int run_test(struct test *test)
fprintf(stderr, " %s/%s: checking position %lu (at %"PRId64" secs), expecting value " NETDATA_DOUBLE_FORMAT
", found " NETDATA_DOUBLE_FORMAT ", %s\n",
test->name, rrddim_name(rd), c+1,
- (int64_t)((rrdset_first_entry_t(st) + c * st->update_every) - time_start),
+ (int64_t)((rrdset_first_entry_s(st) + c * st->update_every) - time_start),
n, v, (same)?"OK":"### E R R O R ###");
if(!same) errors++;
@@ -1301,7 +1301,7 @@ int run_test(struct test *test)
fprintf(stderr, " %s/%s: checking position %lu (at %"PRId64" secs), expecting value " NETDATA_DOUBLE_FORMAT
", found " NETDATA_DOUBLE_FORMAT ", %s\n",
test->name, rrddim_name(rd2), c+1,
- (int64_t)((rrdset_first_entry_t(st) + c * st->update_every) - time_start),
+ (int64_t)((rrdset_first_entry_s(st) + c * st->update_every) - time_start),
n, v, (same)?"OK":"### E R R O R ###");
if(!same) errors++;
}
@@ -1349,7 +1349,7 @@ static int test_variable_renames(void) {
rrddim_reset_name(st, rd2, "DIM2NAME2");
fprintf(stderr, "Renamed dimension with id '%s' to name '%s'\n", rrddim_id(rd2), rrddim_name(rd2));
- BUFFER *buf = buffer_create(1);
+ BUFFER *buf = buffer_create(1, NULL);
health_api_v1_chart_variables2json(st, buf);
fprintf(stderr, "%s", buffer_tostring(buf));
buffer_free(buf);
@@ -1604,7 +1604,7 @@ int test_sqlite(void) {
return 1;
}
- BUFFER *sql = buffer_create(ACLK_SYNC_QUERY_SIZE);
+ BUFFER *sql = buffer_create(ACLK_SYNC_QUERY_SIZE, NULL);
char *uuid_str = "0000_000";
buffer_sprintf(sql, TABLE_ACLK_ALERT, uuid_str);
@@ -1775,7 +1775,6 @@ static inline void rrddim_set_by_pointer_fake_time(RRDDIM *rd, collected_number
static RRDHOST *dbengine_rrdhost_find_or_create(char *name)
{
/* We don't want to drop metrics when generating load, we prefer to block data generation itself */
- rrdeng_drop_metrics_under_page_cache_pressure = 0;
return rrdhost_find_or_create(
name
@@ -1859,10 +1858,10 @@ static void test_dbengine_create_charts(RRDHOST *host, RRDSET *st[CHARTS], RRDDI
now_realtime_timeval(&now);
rrdset_timed_done(st[i], now, false);
}
- // Fluh pages for subsequent real values
+ // Flush pages for subsequent real values
for (i = 0 ; i < CHARTS ; ++i) {
for (j = 0; j < DIMS; ++j) {
- rrdeng_store_metric_flush_current_page((rd[i][j])->tiers[0]->db_collection_handle);
+ rrdeng_store_metric_flush_current_page((rd[i][j])->tiers[0].db_collection_handle);
}
}
}
@@ -1881,7 +1880,7 @@ static time_t test_dbengine_create_metrics(RRDSET *st[CHARTS], RRDDIM *rd[CHARTS
// feed it with the test data
for (i = 0 ; i < CHARTS ; ++i) {
for (j = 0 ; j < DIMS ; ++j) {
- rd[i][j]->tiers[0]->collect_ops->change_collection_frequency(rd[i][j]->tiers[0]->db_collection_handle, update_every);
+ rd[i][j]->tiers[0].collect_ops->change_collection_frequency(rd[i][j]->tiers[0].db_collection_handle, update_every);
rd[i][j]->last_collected_time.tv_sec =
st[i]->last_collected_time.tv_sec = st[i]->last_updated.tv_sec = time_now;
@@ -1932,16 +1931,16 @@ static int test_dbengine_check_metrics(RRDSET *st[CHARTS], RRDDIM *rd[CHARTS][DI
time_now = time_start + (c + 1) * update_every;
for (i = 0 ; i < CHARTS ; ++i) {
for (j = 0; j < DIMS; ++j) {
- rd[i][j]->tiers[0]->query_ops->init(rd[i][j]->tiers[0]->db_metric_handle, &handle, time_now, time_now + QUERY_BATCH * update_every);
+ rd[i][j]->tiers[0].query_ops->init(rd[i][j]->tiers[0].db_metric_handle, &handle, time_now, time_now + QUERY_BATCH * update_every, STORAGE_PRIORITY_NORMAL);
for (k = 0; k < QUERY_BATCH; ++k) {
last = ((collected_number)i * DIMS) * REGION_POINTS[current_region] +
j * REGION_POINTS[current_region] + c + k;
expected = unpack_storage_number(pack_storage_number((NETDATA_DOUBLE)last, SN_DEFAULT_FLAGS));
- STORAGE_POINT sp = rd[i][j]->tiers[0]->query_ops->next_metric(&handle);
+ STORAGE_POINT sp = rd[i][j]->tiers[0].query_ops->next_metric(&handle);
value = sp.sum;
- time_retrieved = sp.start_time;
- end_time = sp.end_time;
+ time_retrieved = sp.start_time_s;
+ end_time = sp.end_time_s;
same = (roundndd(value) == roundndd(expected)) ? 1 : 0;
if(!same) {
@@ -1960,7 +1959,7 @@ static int test_dbengine_check_metrics(RRDSET *st[CHARTS], RRDDIM *rd[CHARTS][DI
errors++;
}
}
- rd[i][j]->tiers[0]->query_ops->finalize(&handle);
+ rd[i][j]->tiers[0].query_ops->finalize(&handle);
}
}
}
@@ -1979,10 +1978,11 @@ static int test_dbengine_check_rrdr(RRDSET *st[CHARTS], RRDDIM *rd[CHARTS][DIMS]
int current_region, time_t time_start, time_t time_end)
{
int update_every = REGION_UPDATE_EVERY[current_region];
- fprintf(stderr, "%s() running on region %d, start time %lld, end time %lld, update every %d...\n", __FUNCTION__, current_region, (long long)time_start, (long long)time_end, update_every);
+ fprintf(stderr, "%s() running on region %d, start time %lld, end time %lld, update every %d, on %d dimensions...\n",
+ __FUNCTION__, current_region, (long long)time_start, (long long)time_end, update_every, CHARTS * DIMS);
uint8_t same;
time_t time_now, time_retrieved;
- int i, j, errors, value_errors = 0, time_errors = 0;
+ int i, j, errors, value_errors = 0, time_errors = 0, value_right = 0, time_right = 0;
long c;
collected_number last;
NETDATA_DOUBLE value, expected;
@@ -1993,7 +1993,8 @@ static int test_dbengine_check_rrdr(RRDSET *st[CHARTS], RRDDIM *rd[CHARTS][DIMS]
ONEWAYALLOC *owa = onewayalloc_create(0);
RRDR *r = rrd2rrdr_legacy(owa, st[i], points, time_start, time_end,
RRDR_GROUPING_AVERAGE, 0, RRDR_OPTION_NATURAL_POINTS,
- NULL, NULL, 0, 0, QUERY_SOURCE_UNITTEST);
+ NULL, NULL, 0, 0,
+ QUERY_SOURCE_UNITTEST, STORAGE_PRIORITY_NORMAL);
if (!r) {
fprintf(stderr, " DB-engine unittest %s: empty RRDR on region %d ### E R R O R ###\n", rrdset_name(st[i]), current_region);
return ++errors;
@@ -2020,17 +2021,22 @@ static int test_dbengine_check_rrdr(RRDSET *st[CHARTS], RRDDIM *rd[CHARTS][DIMS]
same = (roundndd(value) == roundndd(expected)) ? 1 : 0;
if(!same) {
if(value_errors < 20)
- fprintf(stderr, " DB-engine unittest %s/%s: at %lu secs, expecting value " NETDATA_DOUBLE_FORMAT
+ fprintf(stderr, " DB-engine unittest %s/%s: point #%ld, at %lu secs, expecting value " NETDATA_DOUBLE_FORMAT
", RRDR found " NETDATA_DOUBLE_FORMAT ", ### E R R O R ###\n",
- rrdset_name(st[i]), rrddim_name(rd[i][j]), (unsigned long)time_now, expected, value);
+ rrdset_name(st[i]), rrddim_name(rd[i][j]), (long) c+1, (unsigned long)time_now, expected, value);
value_errors++;
}
+ else
+ value_right++;
+
if(time_retrieved != time_now) {
if(time_errors < 20)
- fprintf(stderr, " DB-engine unittest %s/%s: at %lu secs, found RRDR timestamp %lu ### E R R O R ###\n",
- rrdset_name(st[i]), rrddim_name(rd[i][j]), (unsigned long)time_now, (unsigned long)time_retrieved);
+ fprintf(stderr, " DB-engine unittest %s/%s: point #%ld at %lu secs, found RRDR timestamp %lu ### E R R O R ###\n",
+ rrdset_name(st[i]), rrddim_name(rd[i][j]), (long)c+1, (unsigned long)time_now, (unsigned long)time_retrieved);
time_errors++;
}
+ else
+ time_right++;
}
rrddim_foreach_done(d);
}
@@ -2040,10 +2046,10 @@ static int test_dbengine_check_rrdr(RRDSET *st[CHARTS], RRDDIM *rd[CHARTS][DIMS]
}
if(value_errors)
- fprintf(stderr, "%d value errors encountered\n", value_errors);
+ fprintf(stderr, "%d value errors encountered (%d were ok)\n", value_errors, value_right);
if(time_errors)
- fprintf(stderr, "%d time errors encountered\n", time_errors);
+ fprintf(stderr, "%d time errors encountered (%d were ok)\n", time_errors, value_right);
return errors + value_errors + time_errors;
}
@@ -2051,7 +2057,7 @@ static int test_dbengine_check_rrdr(RRDSET *st[CHARTS], RRDDIM *rd[CHARTS][DIMS]
int test_dbengine(void)
{
fprintf(stderr, "%s() running...\n", __FUNCTION__ );
- int i, j, errors, value_errors = 0, time_errors = 0, update_every, current_region;
+ int i, j, errors = 0, value_errors = 0, time_errors = 0, update_every, current_region;
RRDHOST *host = NULL;
RRDSET *st[CHARTS];
RRDDIM *rd[CHARTS][DIMS];
@@ -2074,9 +2080,7 @@ int test_dbengine(void)
time_start[current_region] = 2 * API_RELATIVE_TIME_MAX;
time_end[current_region] = test_dbengine_create_metrics(st,rd, current_region, time_start[current_region]);
- errors = test_dbengine_check_metrics(st, rd, current_region, time_start[current_region]);
- if (errors)
- goto error_out;
+ errors += test_dbengine_check_metrics(st, rd, current_region, time_start[current_region]);
current_region = 1; //this is the second region of data
update_every = REGION_UPDATE_EVERY[current_region]; // set data collection frequency to 3 seconds
@@ -2084,7 +2088,7 @@ int test_dbengine(void)
for (i = 0 ; i < CHARTS ; ++i) {
st[i]->update_every = update_every;
for (j = 0; j < DIMS; ++j) {
- rrdeng_store_metric_flush_current_page((rd[i][j])->tiers[0]->db_collection_handle);
+ rrdeng_store_metric_flush_current_page((rd[i][j])->tiers[0].db_collection_handle);
}
}
@@ -2093,9 +2097,7 @@ int test_dbengine(void)
time_start[current_region] += update_every - time_start[current_region] % update_every;
time_end[current_region] = test_dbengine_create_metrics(st,rd, current_region, time_start[current_region]);
- errors = test_dbengine_check_metrics(st, rd, current_region, time_start[current_region]);
- if (errors)
- goto error_out;
+ errors += test_dbengine_check_metrics(st, rd, current_region, time_start[current_region]);
current_region = 2; //this is the third region of data
update_every = REGION_UPDATE_EVERY[current_region]; // set data collection frequency to 1 seconds
@@ -2103,7 +2105,7 @@ int test_dbengine(void)
for (i = 0 ; i < CHARTS ; ++i) {
st[i]->update_every = update_every;
for (j = 0; j < DIMS; ++j) {
- rrdeng_store_metric_flush_current_page((rd[i][j])->tiers[0]->db_collection_handle);
+ rrdeng_store_metric_flush_current_page((rd[i][j])->tiers[0].db_collection_handle);
}
}
@@ -2112,26 +2114,22 @@ int test_dbengine(void)
time_start[current_region] += update_every - time_start[current_region] % update_every;
time_end[current_region] = test_dbengine_create_metrics(st,rd, current_region, time_start[current_region]);
- errors = test_dbengine_check_metrics(st, rd, current_region, time_start[current_region]);
- if (errors)
- goto error_out;
+ errors += test_dbengine_check_metrics(st, rd, current_region, time_start[current_region]);
for (current_region = 0 ; current_region < REGIONS ; ++current_region) {
- errors = test_dbengine_check_rrdr(st, rd, current_region, time_start[current_region], time_end[current_region]);
- if (errors)
- goto error_out;
+ errors += test_dbengine_check_rrdr(st, rd, current_region, time_start[current_region], time_end[current_region]);
}
current_region = 1;
update_every = REGION_UPDATE_EVERY[current_region]; // use the maximum update_every = 3
- errors = 0;
long points = (time_end[REGIONS - 1] - time_start[0]) / update_every; // cover all time regions with RRDR
long point_offset = (time_start[current_region] - time_start[0]) / update_every;
for (i = 0 ; i < CHARTS ; ++i) {
ONEWAYALLOC *owa = onewayalloc_create(0);
RRDR *r = rrd2rrdr_legacy(owa, st[i], points, time_start[0] + update_every,
time_end[REGIONS - 1], RRDR_GROUPING_AVERAGE, 0,
- RRDR_OPTION_NATURAL_POINTS, NULL, NULL, 0, 0, QUERY_SOURCE_UNITTEST);
+ RRDR_OPTION_NATURAL_POINTS, NULL, NULL, 0, 0,
+ QUERY_SOURCE_UNITTEST, STORAGE_PRIORITY_NORMAL);
if (!r) {
fprintf(stderr, " DB-engine unittest %s: empty RRDR ### E R R O R ###\n", rrdset_name(st[i]));
@@ -2180,7 +2178,7 @@ int test_dbengine(void)
}
onewayalloc_destroy(owa);
}
-error_out:
+
rrd_wrlock();
rrdeng_prepare_exit((struct rrdengine_instance *)host->db[0].instance);
rrdhost_delete_charts(host);
@@ -2269,7 +2267,7 @@ static void generate_dbengine_chart(void *arg)
thread_info->time_max = time_current;
}
for (j = 0; j < DSET_DIMS; ++j) {
- rrdeng_store_metric_finalize((rd[j])->tiers[0]->db_collection_handle);
+ rrdeng_store_metric_finalize((rd[j])->tiers[0].db_collection_handle);
}
}
@@ -2329,7 +2327,7 @@ void generate_dbengine_dataset(unsigned history_seconds)
}
freez(thread_info);
rrd_wrlock();
- rrdhost_free(host, 1);
+ rrdhost_free___while_having_rrd_wrlock(host, true);
rrd_unlock();
}
@@ -2389,13 +2387,13 @@ static void query_dbengine_chart(void *arg)
time_before = MIN(time_after + duration, time_max); /* up to 1 hour queries */
}
- rd->tiers[0]->query_ops->init(rd->tiers[0]->db_metric_handle, &handle, time_after, time_before);
+ rd->tiers[0].query_ops->init(rd->tiers[0].db_metric_handle, &handle, time_after, time_before, STORAGE_PRIORITY_NORMAL);
++thread_info->queries_nr;
for (time_now = time_after ; time_now <= time_before ; time_now += update_every) {
generatedv = generate_dbengine_chart_value(i, j, time_now);
expected = unpack_storage_number(pack_storage_number((NETDATA_DOUBLE) generatedv, SN_DEFAULT_FLAGS));
- if (unlikely(rd->tiers[0]->query_ops->is_finished(&handle))) {
+ if (unlikely(rd->tiers[0].query_ops->is_finished(&handle))) {
if (!thread_info->delete_old_data) { /* data validation only when we don't delete */
fprintf(stderr, " DB-engine stresstest %s/%s: at %lu secs, expecting value " NETDATA_DOUBLE_FORMAT
", found data gap, ### E R R O R ###\n",
@@ -2405,10 +2403,10 @@ static void query_dbengine_chart(void *arg)
break;
}
- STORAGE_POINT sp = rd->tiers[0]->query_ops->next_metric(&handle);
+ STORAGE_POINT sp = rd->tiers[0].query_ops->next_metric(&handle);
value = sp.sum;
- time_retrieved = sp.start_time;
- end_time = sp.end_time;
+ time_retrieved = sp.start_time_s;
+ end_time = sp.end_time_s;
if (!netdata_double_isnumber(value)) {
if (!thread_info->delete_old_data) { /* data validation only when we don't delete */
@@ -2443,7 +2441,7 @@ static void query_dbengine_chart(void *arg)
}
}
}
- rd->tiers[0]->query_ops->finalize(&handle);
+ rd->tiers[0].query_ops->finalize(&handle);
} while(!thread_info->done);
if(value_errors)
diff --git a/database/README.md b/database/README.md
index 1453f9b3..becd4165 100644
--- a/database/README.md
+++ b/database/README.md
@@ -1,7 +1,11 @@
<!--
title: "Database"
description: "The Netdata Agent leverages multiple, user-configurable time-series databases that use RAM and/or disk to store metrics on any type of node."
-custom_edit_url: https://github.com/netdata/netdata/edit/master/database/README.md
+custom_edit_url: "https://github.com/netdata/netdata/edit/master/database/README.md"
+sidebar_label: "Database"
+learn_status: "Published"
+learn_topic_type: "Tasks"
+learn_rel_path: "Setup"
-->
# Database
@@ -9,12 +13,12 @@ custom_edit_url: https://github.com/netdata/netdata/edit/master/database/README.
Netdata is fully capable of long-term metrics storage, at per-second granularity, via its default database engine
(`dbengine`). But to remain as flexible as possible, Netdata supports several storage options:
-1. `dbengine`, (the default) data are in database files. The [Database Engine](/database/engine/README.md) works like a
+1. `dbengine`, (the default) data are in database files. The [Database Engine](https://github.com/netdata/netdata/blob/master/database/engine/README.md) works like a
traditional database. There is some amount of RAM dedicated to data caching and indexing and the rest of the data
reside compressed on disk. The number of history entries is not fixed in this case, but depends on the configured
disk space and the effective compression ratio of the data stored. This is the **only mode** that supports changing
the data collection update frequency (`update every`) **without losing** the previously stored metrics. For more
- details see [here](/database/engine/README.md).
+ details see [here](https://github.com/netdata/netdata/blob/master/database/engine/README.md).
2. `ram`, data are purely in memory. Data are never saved on disk. This mode uses `mmap()` and supports [KSM](#ksm).
@@ -38,13 +42,13 @@ The default mode `[db].mode = dbengine` has been designed to scale for longer re
for parent Agents in the _Parent - Child_ setups
The other available database modes are designed to minimize resource utilization and should only be considered on
-[Parent - Child](/docs/metrics-storage-management/how-streaming-works.mdx) setups at the children side and only when the
+[Parent - Child](https://github.com/netdata/netdata/blob/master/docs/metrics-storage-management/how-streaming-works.mdx) setups at the children side and only when the
resource constraints are very strict.
So,
- On a single node setup, use `[db].mode = dbengine`.
-- On a [Parent - Child](/docs/metrics-storage-management/how-streaming-works.mdx) setup, use `[db].mode = dbengine` on the
+- On a [Parent - Child](https://github.com/netdata/netdata/blob/master/docs/metrics-storage-management/how-streaming-works.mdx) setup, use `[db].mode = dbengine` on the
parent to increase retention, a more resource efficient mode like, `dbengine` with light retention settings, and
`save`, `ram` or `none` modes for the children to minimize resource utilization.
@@ -64,7 +68,7 @@ Metrics retention is controlled only by the disk space allocated to storing metr
CPU required by the agent to query longer timeframes.
Since Netdata Agents usually run on the edge, on production systems, Netdata Agent **parents** should be considered.
-When having a [**parent - child**](/docs/metrics-storage-management/how-streaming-works.mdx) setup, the child (the
+When having a [**parent - child**](https://github.com/netdata/netdata/blob/master/docs/metrics-storage-management/how-streaming-works.mdx) setup, the child (the
Netdata Agent running on a production system) delegates all of its functions, including longer metrics retention and
querying, to the parent node that can dedicate more resources to this task. A single Netdata Agent parent can centralize
multiple children Netdata Agents (dozens, hundreds, or even thousands depending on its available resources).
@@ -85,7 +89,7 @@ every 2 seconds. This will **cut in half** both CPU and RAM resources consumed b
On very weak devices you might have to use `[db].update every = 5` and `[db].retention = 720` (still 1 hour of data, but
1/5 of the CPU and RAM resources).
-You can also disable [data collection plugins](/collectors/README.md) that you don't need. Disabling such plugins will also
+You can also disable [data collection plugins](https://github.com/netdata/netdata/blob/master/collectors/README.md) that you don't need. Disabling such plugins will also
free both CPU and RAM resources.
## Memory optimizations
diff --git a/database/engine/README.md b/database/engine/README.md
index c67e400f..664d4050 100644
--- a/database/engine/README.md
+++ b/database/engine/README.md
@@ -1,48 +1,126 @@
<!--
title: "Database engine"
description: "Netdata's highly-efficient database engine use both RAM and disk for distributed, long-term storage of per-second metrics."
-custom_edit_url: https://github.com/netdata/netdata/edit/master/database/engine/README.md
+custom_edit_url: "https://github.com/netdata/netdata/edit/master/database/engine/README.md"
+sidebar_label: "Database engine"
+learn_status: "Published"
+learn_topic_type: "Concepts"
+learn_rel_path: "Concepts"
-->
-# Database engine
+# DBENGINE
-The Database Engine works like a traditional time series database. Unlike other [database modes](/database/README.md),
-the amount of historical metrics stored is based on the amount of disk space you allocate and the effective compression
-ratio, not a fixed number of metrics collected.
+DBENGINE is the time-series database of Netdata.
-## Tiering
+## Design
-Tiering is a mechanism of providing multiple tiers of data with
-different [granularity on metrics](/docs/store/distributed-data-architecture.md#granularity-of-metrics).
+### Data Points
-For Netdata Agents with version `netdata-1.35.0.138.nightly` and greater, `dbengine` supports Tiering, allowing almost
-unlimited retention of data.
+**Data points** represent the collected values of metrics.
+A **data point** has:
-### Metric size
+1. A **value**, the data collected for a metric. There is a special **value** to indicate that the collector failed to collect a valid value, and thus the data point is a **gap**.
+2. A **timestamp**, the time it has been collected.
+3. A **duration**, the time between this and the previous data collection.
+4. A flag which is set when machine-learning categorized the collected value as **anomalous** (an outlier based on the trained models).
-Every Tier down samples the exact lower tier (lower tiers have greater resolution). You can have up to 5
-Tiers **[0. . 4]** of data (including the Tier 0, which has the highest resolution)
+Using the **timestamp** and **duration**, Netdata calculates for each point its **start time**, **end time** and **update every**.
-Tier 0 is the default that was always available in `dbengine` mode. Tier 1 is the first level of aggregation, Tier 2 is
-the second, and so on.
+For incremental metrics (counters), Netdata interpolates the collected values to align them to the expected **end time** at the microsecond level, absorbing data collection micro-latencies.
-Metrics on all tiers except of the _Tier 0_ also store the following five additional values for every point for accurate
-representation:
+When data points are stored in higher tiers (time aggregations - see [Tiers](#Tiers) below), each data point has:
-1. The `sum` of the points aggregated
-2. The `min` of the points aggregated
-3. The `max` of the points aggregated
-4. The `count` of the points aggregated (could be constant, but it may not be due to gaps in data collection)
-5. The `anomaly_count` of the points aggregated (how many of the aggregated points found anomalous)
+1. The **sum** of the original values that have been aggregated,
+2. The **count** of all the original values aggregated,
+3. The **minimum** value among them,
+4. The **maximum** value among them,
+5. Their **anomaly rate**, i.e. the count of values that were detected as outliers based on the currently trained models for the metric,
+6. A **timestamp**, which is the equal to the **end time** of the last point aggregated,
+7. A **duration**, which is the duration between the **first time** of the first point aggregated to the **end time** of the last point aggregated.
-Among `min`, `max` and `sum`, the correct value is chosen based on the user query. `average` is calculated on the fly at
-query time.
+This design allows Netdata to accurately know the **average**, **minimum**, **maximum** and **anomaly rate** values even when using higher tiers to satisfy a query.
-### Tiering in a nutshell
+### Pages
+Data points are organized into **pages**, i.e. segments of contiguous data collections of the same metric.
-The `dbengine` is capable of retaining metrics for years. To further understand the `dbengine` tiering mechanism let's
-explore the following configuration.
+Each page:
+
+1. Contains contiguous **data points** of a single metric.
+2. Contains **data points** having the same **update every**. If a metric changes **update every** on the fly, the page is flushed and a new one with the new **update every** is created. If a data collection is missed, a **gap point** is inserted into the page, so that the data points in a page remain contiguous.
+3. Has a **start time**, which is equivalent to the **end time** of the first data point stored into it,
+4. Has an **end time**, which is equal to the **end time** of the last data point stored into it,
+5. Has an **update every**, common for all points in the page.
+
+A **page** is a simple array of values. Each slot in the array has a **timestamp** implied by its position in the array, and each value stored represents the **data point** for that time, for the metric the page belongs to.
+
+This simple fixed step page design allows Netdata to collect several millions of points per second and pack all the values in a compact form with minimal metadata overhead.
+
+#### Hot Pages
+
+While a metric is collected, there is one **hot page** in memory for each of the configured tiers. Values collected for a metric are appended to its **hot page** until that page becomes full.
+
+#### Dirty Pages
+
+Once a **hot page** is full, it becomes a **dirty page**, and it is scheduled for immediate **flushing** (saving) to disk.
+
+#### Clean Pages
+
+Flushed (saved) pages are **clean pages**, i.e. read-only pages that reside primarily on disk, and are loaded on demand to satisfy data queries.
+
+#### Pages Configuration
+
+Pages are configured like this:
+
+| Attribute | Tier0 | Tier1 | Tier2 |
+|---------------------------------------------------------------------------------------|:-------------------------------------:|:---------------------------------------------------------------:|:---------------------------------------------------------------:|
+| Point Size in Memory, in Bytes | 4 | 16 | 16 |
+| Point Size on Disk, in Bytes<br/><small>after LZ4 compression, on the average</small> | 1 | 4 | 4 |
+| Page Size in Bytes | 4096<br/><small>2048 in 32bit</small> | 2048<br/><small>1024 in 32bit</small> | 384<br/><small>192 in 32bit</small> |
+| Collections per Point | 1 | 60x Tier0<br/><small>configurable in<br/>`netdata.conf`</small> | 60x Tier1<br/><small>configurable in<br/>`netdata.conf`</small> |
+| Points per Page | 1024<br/><small>512 in 32bit</small> | 128<br/><small>64 in 32bit</small> | 24<br/><small>12 in 32bit</small> |
+
+### Files
+
+To minimize the amount of data written to disk and the amount of storage required for storing metrics, Netdata aggregates up to 64 **dirty pages** of independent metrics, packs them all together into one bigger buffer, compresses this buffer with LZ4 (about 75% savings on the average) and commits a transaction to the disk files.
+
+#### Extents
+
+This collection of 64 pages that is packed and compressed together is called an **extent**. Netdata tries to store together, in the same **extent**, metrics that are meant to be "close". Dimensions of the same chart are such. They are usually queried together, so it is beneficial to have them in the same **extent** to read all of them at once at query time.
+
+#### Datafiles
+
+Multiple **extents** are appended to **datafiles** (filename suffix `.ndf`), until these **datafiles** become full. The size of each **datafile** is determined automatically by Netdata. The minimum for each **datafile** is 4MB and the maximum 512MB. Depending on the amount of disk space configured for each tier, Netdata will decide a **datafile** size trying to maintain about 50 datafiles for the whole database, within the limits mentioned (4MB min, 512MB max per file). The maximum number of datafiles supported is 65536, and therefore the maximum database size (per tier) that Netdata can support is 32TB.
+
+#### Journal Files
+
+Each **datafile** has two **journal files** with metadata related to the stored data in the **datafile**.
+
+- **journal file v1**, with filename suffix `.njf`, holds information about the transactions in its **datafile** and provides the ability to recover as much data as possible, in case either the datafile or the journal files get corrupted. This journal file has a maximum transaction size of 4KB, so in case data are corrupted on disk transactions of 4KB are lost. Each transaction holds the metadata of one **extent** (this is why DBENGINE supports up to 64 pages per extent).
+
+- **journal file v2**, with filename suffix `.njfv2`, which is a disk-based index for all the **pages** and **extents**. This file is memory mapped at runtime and is consulted to find where the data of a metric are in the datafile. This journal file is automatically re-created from **journal file v1** if it is missing. It is safe to delete these files (when Netdata does not run). Netdata will re-create them on the next run. Journal files v2 are supported in Netdata Agents with version `netdata-1.37.0-115-nightly`. Older versions maintain the journal index in memory.
+
+#### Database Rotation
+
+Database rotation is achieved by deleting the oldest **datafile** (and its journals) and creating a new one (with its journals).
+
+Data on disk are append-only. There is no way to delete, add, or update data in the middle of the database. If data are not useful for whatever reason, Netdata can be instructed to ignore these data. They will eventually be deleted from disk when the database is rotated. New data are always appended.
+
+#### Tiers
+
+Tiers are supported in Netdata Agents with version `netdata-1.35.0.138.nightly` and greater.
+
+**datafiles** and **journal files** are organized in **tiers**. All tiers share the same metrics and same collected values.
+
+- **tier 0** is the high resolution tier that stores the collected data at the frequency they are collected.
+- **tier 1** by default aggregates 60 values of **tier 0**.
+- **tier 2** by default aggregates 60 values of **tier 1**, or 3600 values of **tier 0**.
+
+Updating the higher **tiers** is automated, and it happens in real-time while data are being collected for **tier 0**.
+
+When the Netdata Agent starts, during the first data collection of each metric, higher tiers are automatically **backfilled** with data from lower tiers, so that the aggregation they provide will be accurate.
+
+3 tiers are enabled by default in Netdata, with the following configuration:
```
[db]
@@ -51,46 +129,151 @@ explore the following configuration.
# per second data collection
update every = 1
- # enables Tier 1 and Tier 2, Tier 0 is always enabled in dbengine mode
+ # number of tiers used (1 to 5, 3 being default)
storage tiers = 3
- # Tier 0, per second data for a week
- dbengine multihost disk space MB = 1100
+ # Tier 0, per second data
+ dbengine multihost disk space MB = 256
- # Tier 1, per minute data for a month
- dbengine tier 1 multihost disk space MB = 330
+ # Tier 1, per minute data
+ dbengine tier 1 multihost disk space MB = 128
+
+ # Tier 2, per hour data
+ dbengine tier 2 multihost disk space MB = 64
+```
+
+The exact retention that can be achieved by each tier depends on the number of metrics collected. The more the metrics, the smaller the retention that will fit in a given size. The general rule is that Netdata needs about **1 byte per data point on disk for tier 0**, and **4 bytes per data point on disk for tier 1 and above**.
+
+So, for 1000 metrics collected per second and 256 MB for tier 0, Netdata will store about:
+
+```
+256MB on disk / 1 byte per point / 1000 metrics => 256k points per metric / 86400 seconds per day = about 3 days
+```
+
+At tier 1 (per minute):
+
+```
+128MB on disk / 4 bytes per point / 1000 metrics => 32k points per metric / (24 hours * 60 minutes) = about 22 days
+```
+
+At tier 2 (per hour):
+
+```
+64MB on disk / 4 bytes per point / 1000 metrics => 16k points per metric / 24 hours per day = about 2 years
+```
+
+Of course double the metrics, half the retention. There are more factors that affect retention. The number of ephemeral metrics (i.e. metrics that are collected for part of the time). The number of metrics that are usually constant over time (affecting compression efficiency). The number of restarts a Netdata Agents gets through time (because it has to break pages prematurely, increasing the metadata overhead). But the actual numbers should not deviate significantly from the above.
+
+### Data Loss
+
+Until **hot pages** and **dirty pages** are **flushed** to disk they are at risk (e.g. due to a crash, or
+power failure), as they are stored only in memory.
+
+The supported way of ensuring high data availability is the use of Netdata Parents to stream the data in real-time to
+multiple other Netdata agents.
+
+## Memory Requirements
+
+DBENGINE memory is related to the number of metrics concurrently being collected, the retention of the metrics on disk in relation with the queries running, and the number of metrics for which retention is maintained.
+
+### Memory for concurrently collected metrics
+
+DBENGINE is automatically sized to use memory according to this equation:
+
+```
+memory in KiB = METRICS x (TIERS - 1) x 4KiB x 2 + 32768 KiB
+```
+
+Where:
+- `METRICS`: the maximum number of concurrently collected metrics (dimensions) from the time the agent started.
+- `TIERS`: the number of storage tiers configured, by default 3 ( `-1` when using 3+ tiers)
+- `x 2`, to accommodate room for flushing data to disk
+- `x 4KiB`, the data segment size of each metric
+- `+ 32768 KiB`, 32 MB for operational caches
+
+So, for 2000 metrics (dimensions) in 3 storage tiers:
+
+```
+memory for 2k metrics = 2000 x (3 - 1) x 4 KiB x 2 + 32768 KiB = 64 MiB
+```
+
+For 100k concurrently collected metrics in 3 storage tiers:
+
+```
+memory for 100k metrics = 100000 x (3 - 1) x 4 KiB x 2 + 32768 KiB = 1.6 GiB
+```
+
+#### Exceptions
+
+Netdata has several protection mechanisms to prevent the use of more memory (than the above), by incrementally fetching data from disk and aggressively evicting old data to make room for new data, but still memory may grow beyond the above limit under the following conditions:
+
+1. The number of pages concurrently used in queries do not fit the in the above size. This can happen when multiple queries of unreasonably long time-frames run on lower, higher resolution, tiers. The Netdata query planner attempts to avoid such situations by gradually loading pages, but still under extreme conditions the system may use more memory to satisfy these queries.
+
+2. The disks that host Netdata files are extremely slow for the workload required by the database so that data cannot be flushed to disk quickly to free memory. Netdata will automatically spawn more flushing workers in an attempt to parallelize and speed up flushing, but still if the disks cannot write the data quickly enough, they will remain in memory until they are written to disk.
+
+### Caches
+
+DBENGINE stores metric data to disk. To achieve high performance even under severe stress, it uses several layers of caches.
+
+#### Main Cache
+
+Stores page data. It is the primary storage of hot and dirty pages (before they are saved to disk), and its clean queue is the LRU cache for speeding up queries.
+
+The entire DBENGINE is designed to use the hot queue size (the currently collected metrics) as the key for sizing all its memory consumption. We call this feature **memory ballooning**. More collected metrics, bigger main cache and vice versa.
+
+In the equation:
- # Tier 2, per hour data for a year
- dbengine tier 2 multihost disk space MB = 67
```
+memory in KiB = METRICS x (TIERS - 1) x 4KiB x 2 + 32768 KiB
+```
+
+the part `METRICS x (TIERS - 1) x 4KiB` is an estimate for the max hot size of the main cache. Tier 0 pages are 4KiB, but tier 1 pages are 2 KiB and tier 2 pages are 384 bytes. So a single metric in 3 tiers uses 4096 + 2048 + 384 = 6528 bytes. The equation estimates 8192 per metric, which includes cache internal structures and leaves some spare.
+
+Then `x 2` is the worst case estimate for the dirty queue. If all collected metrics (hot) become available for saving at once, to avoid stopping data collection all their pages will become dirty and new hot pages will be created instantly. To save memory, when Netdata starts, DBENGINE allocates randomly smaller pages for metrics, to spread their completion evenly across time.
+
+The memory we saved with the above is used to improve the LRU cache. So, although we reserved 32MiB for the LRU, in bigger setups (Netdata Parents) the LRU grows a lot more, within the limits of the equation.
+
+In practice, the main cache sizes itself with `hot x 1.5` instead of `host x 2`. The reason is that 5% of main cache is reserved for expanding open cache, 5% for expanding extent cache and we need room for the extensive buffers that are allocated in these setups. When the main cache exceeds `hot x 1.5` it enters a mode of critical evictions, and aggresively frees pages from the LRU to maintain a healthy memory footprint within its design limits.
+
+#### Open Cache
-For 2000 metrics, collected every second and retained for a week, Tier 0 needs: 1 byte x 2000 metrics x 3600 secs per
-hour x 24 hours per day x 7 days per week = 1100MB.
+Stores metadata about on disk pages. Not the data itself. Only metadata about the location of the data on disk.
-By setting `dbengine multihost disk space MB` to `1100`, this node will start maintaining about a week of data. But pay
-attention to the number of metrics. If you have more than 2000 metrics on a node, or you need more that a week of high
-resolution metrics, you may need to adjust this setting accordingly.
+Its primary use is to index information about the open datafile, the one that still accepts new pages. Once that datafile becomes full, all the hot pages of the open cache are indexed in journal v2 files.
-Tier 1 is by default sampling the data every **60 points of Tier 0**. In our case, Tier 0 is per second, if we want to
-transform this information in terms of time then the Tier 1 "resolution" is per minute.
+The clean queue is an LRU for reducing the journal v2 scans during quering.
-Tier 1 needs four times more storage per point compared to Tier 0. So, for 2000 metrics, with per minute resolution,
-retained for a month, Tier 1 needs: 4 bytes x 2000 metrics x 60 minutes per hour x 24 hours per day x 30 days per month
-= 330MB.
+Open cache uses memory ballooning too, like the main cache, based on its own hot pages. Open cache hot size is mainly controlled by the size of the open datafile. This is why on netdata versions with journal files v2, we decreased the maximum datafile size from 1GB to 512MB and we increased the target number of datafiles from 20 to 50.
-Tier 2 is by default sampling data every 3600 points of Tier 0 (60 of Tier 1, which is the previous exact Tier). Again
-in term of "time" (Tier 0 is per second), then Tier 2 is per hour.
+On bigger setups open cache will get a bigger LRU by automatically sizing it (the whole open cache) to 5% to the size of (the whole) main cache.
-The storage requirements are the same to Tier 1.
+#### Extent Cache
+
+Caches compressed **extent** data, to avoid reading too repeatedly the same data from disks.
+
+
+### Shared Memory
+
+Journal v2 indexes are mapped into memory. Netdata attempts to minimize shared memory use by instructing the kernel about the use of these files, or even unmounting them when they are not needed.
+
+The time-ranges of the queries running control the amount of shared memory required.
+
+## Metrics Registry
+
+DBENGINE uses 150 bytes of memory for every metric for which retention is maintained but is not currently being collected.
+
+---
+
+--- OLD DOCS BELOW THIS POINT ---
+
+---
-For 2000 metrics, with per hour resolution, retained for a year, Tier 2 needs: 4 bytes x 2000 metrics x 24 hours per day
-x 365 days per year = 67MB.
## Legacy configuration
### v1.35.1 and prior
-These versions of the Agent do not support [Tiering](#Tiering). You could change the metric retention for the parent and
+These versions of the Agent do not support [Tiers](#Tiers). You could change the metric retention for the parent and
all of its children only with the `dbengine multihost disk space MB` setting. This setting accounts the space allocation
for the parent node and all of its children.
@@ -105,15 +288,9 @@ the `[db]` section of your `netdata.conf`.
### v1.23.2 and prior
-_For Netdata Agents earlier than v1.23.2_, the Agent on the parent node uses one dbengine instance for itself, and
-another instance for every child node it receives metrics from. If you had four streaming nodes, you would have five
-instances in total (`1 parent + 4 child nodes = 5 instances`).
+_For Netdata Agents earlier than v1.23.2_, the Agent on the parent node uses one dbengine instance for itself, and another instance for every child node it receives metrics from. If you had four streaming nodes, you would have five instances in total (`1 parent + 4 child nodes = 5 instances`).
-The Agent allocates resources for each instance separately using the `dbengine disk space MB` (**deprecated**) setting.
-If
-`dbengine disk space MB`(**deprecated**) is set to the default `256`, each instance is given 256 MiB in disk space,
-which means the total disk space required to store all instances is,
-roughly, `256 MiB * 1 parent * 4 child nodes = 1280 MiB`.
+The Agent allocates resources for each instance separately using the `dbengine disk space MB` (**deprecated**) setting. If `dbengine disk space MB`(**deprecated**) is set to the default `256`, each instance is given 256 MiB in disk space, which means the total disk space required to store all instances is, roughly, `256 MiB * 1 parent * 4 child nodes = 1280 MiB`.
#### Backward compatibility
@@ -128,7 +305,7 @@ Agent.
##### Information
For more information about setting `[db].mode` on your nodes, in addition to other streaming configurations, see
-[streaming](/streaming/README.md).
+[streaming](https://github.com/netdata/netdata/blob/master/streaming/README.md).
## Requirements & limitations
@@ -154,7 +331,7 @@ An important observation is that RAM usage depends on both the `page cache size`
options.
You can use
-our [database engine calculator](/docs/store/change-metrics-storage.md#calculate-the-system-resources-ram-disk-space-needed-to-store-metrics)
+our [database engine calculator](https://github.com/netdata/netdata/blob/master/docs/store/change-metrics-storage.md#calculate-the-system-resources-ram-disk-space-needed-to-store-metrics)
to validate the memory requirements for your particular system(s) and configuration (**out-of-date**).
### Disk space
@@ -208,7 +385,7 @@ You can apply the settings by running `sysctl -p` or by rebooting.
## Files
-With the DB engine mode the metric data are stored in database files. These files are organized in pairs, the datafiles
+With the DB engine mode the metric data are stored in database files. These files are organized in pairs, the datafiles
and their corresponding journalfiles, e.g.:
```sh
@@ -226,7 +403,7 @@ location is `/var/cache/netdata/dbengine/*`). The higher numbered filenames cont
can safely delete some pairs of files when Netdata is stopped to manually free up some space.
_Users should_ **back up** _their `./dbengine` folders if they consider this data to be important._ You can also set up
-one or more [exporting connectors](/exporting/README.md) to send your Netdata metrics to other databases for long-term
+one or more [exporting connectors](https://github.com/netdata/netdata/blob/master/exporting/README.md) to send your Netdata metrics to other databases for long-term
storage at lower granularity.
## Operation
@@ -298,5 +475,3 @@ An interesting observation to make is that the CPU-bound run (16 GiB page cache)
and generate a read load of 1.7M/sec, whereas in the CPU-bound scenario the read load is 70 times higher at 118M/sec.
Consequently, there is a significant degree of interference by the reader threads, that slow down the writer threads.
This is also possible because the interference effects are greater than the SSD impact on data generation throughput.
-
-
diff --git a/database/engine/cache.c b/database/engine/cache.c
new file mode 100644
index 00000000..4091684b
--- /dev/null
+++ b/database/engine/cache.c
@@ -0,0 +1,2737 @@
+#include "cache.h"
+
+/* STATES AND TRANSITIONS
+ *
+ * entry | entry
+ * v v
+ * HOT -> DIRTY --> CLEAN --> EVICT
+ * v | v
+ * flush | evict
+ * v | v
+ * save | free
+ * callback | callback
+ *
+ */
+
+typedef int32_t REFCOUNT;
+#define REFCOUNT_DELETING (-100)
+
+// to use ARAL uncomment the following line:
+#define PGC_WITH_ARAL 1
+
+typedef enum __attribute__ ((__packed__)) {
+ // mutually exclusive flags
+ PGC_PAGE_CLEAN = (1 << 0), // none of the following
+ PGC_PAGE_DIRTY = (1 << 1), // contains unsaved data
+ PGC_PAGE_HOT = (1 << 2), // currently being collected
+
+ // flags related to various actions on each page
+ PGC_PAGE_IS_BEING_DELETED = (1 << 3),
+ PGC_PAGE_IS_BEING_MIGRATED_TO_V2 = (1 << 4),
+ PGC_PAGE_HAS_NO_DATA_IGNORE_ACCESSES = (1 << 5),
+ PGC_PAGE_HAS_BEEN_ACCESSED = (1 << 6),
+} PGC_PAGE_FLAGS;
+
+#define page_flag_check(page, flag) (__atomic_load_n(&((page)->flags), __ATOMIC_ACQUIRE) & (flag))
+#define page_flag_set(page, flag) __atomic_or_fetch(&((page)->flags), flag, __ATOMIC_RELEASE)
+#define page_flag_clear(page, flag) __atomic_and_fetch(&((page)->flags), ~(flag), __ATOMIC_RELEASE)
+
+#define page_get_status_flags(page) page_flag_check(page, PGC_PAGE_HOT | PGC_PAGE_DIRTY | PGC_PAGE_CLEAN)
+#define is_page_hot(page) (page_get_status_flags(page) == PGC_PAGE_HOT)
+#define is_page_dirty(page) (page_get_status_flags(page) == PGC_PAGE_DIRTY)
+#define is_page_clean(page) (page_get_status_flags(page) == PGC_PAGE_CLEAN)
+
+struct pgc_page {
+ // indexing data
+ Word_t section;
+ Word_t metric_id;
+ time_t start_time_s;
+ time_t end_time_s;
+ uint32_t update_every_s;
+ uint32_t assumed_size;
+
+ REFCOUNT refcount;
+ uint16_t accesses; // counts the number of accesses on this page
+ PGC_PAGE_FLAGS flags;
+ SPINLOCK transition_spinlock; // when the page changes between HOT, DIRTY, CLEAN, we have to get this lock
+
+ struct {
+ struct pgc_page *next;
+ struct pgc_page *prev;
+ } link;
+
+ void *data;
+ uint8_t custom_data[];
+
+ // IMPORTANT!
+ // THIS STRUCTURE NEEDS TO BE INITIALIZED BY HAND!
+};
+
+struct pgc_linked_list {
+ SPINLOCK spinlock;
+ union {
+ PGC_PAGE *base;
+ Pvoid_t sections_judy;
+ };
+ PGC_PAGE_FLAGS flags;
+ size_t version;
+ size_t last_version_checked;
+ bool linked_list_in_sections_judy; // when true, we use 'sections_judy', otherwise we use 'base'
+ struct pgc_queue_statistics *stats;
+};
+
+struct pgc {
+ struct {
+ char name[PGC_NAME_MAX + 1];
+
+ size_t partitions;
+ size_t clean_size;
+ size_t max_dirty_pages_per_call;
+ size_t max_pages_per_inline_eviction;
+ size_t max_skip_pages_per_inline_eviction;
+ size_t max_flushes_inline;
+ size_t max_workers_evict_inline;
+ size_t additional_bytes_per_page;
+ free_clean_page_callback pgc_free_clean_cb;
+ save_dirty_page_callback pgc_save_dirty_cb;
+ save_dirty_init_callback pgc_save_init_cb;
+ PGC_OPTIONS options;
+
+ size_t severe_pressure_per1000;
+ size_t aggressive_evict_per1000;
+ size_t healthy_size_per1000;
+ size_t evict_low_threshold_per1000;
+
+ dynamic_target_cache_size_callback dynamic_target_size_cb;
+ } config;
+
+#ifdef PGC_WITH_ARAL
+ ARAL **aral;
+#endif
+
+ PGC_CACHE_LINE_PADDING(0);
+
+ struct pgc_index {
+ netdata_rwlock_t rwlock;
+ Pvoid_t sections_judy;
+ } *index;
+
+ PGC_CACHE_LINE_PADDING(1);
+
+ struct {
+ SPINLOCK spinlock;
+ size_t per1000;
+ } usage;
+
+ PGC_CACHE_LINE_PADDING(2);
+
+ struct pgc_linked_list clean; // LRU is applied here to free memory from the cache
+
+ PGC_CACHE_LINE_PADDING(3);
+
+ struct pgc_linked_list dirty; // in the dirty list, pages are ordered the way they were marked dirty
+
+ PGC_CACHE_LINE_PADDING(4);
+
+ struct pgc_linked_list hot; // in the hot list, pages are order the way they were marked hot
+
+ PGC_CACHE_LINE_PADDING(5);
+
+ struct pgc_statistics stats; // statistics
+
+#ifdef NETDATA_PGC_POINTER_CHECK
+ PGC_CACHE_LINE_PADDING(6);
+ netdata_mutex_t global_pointer_registry_mutex;
+ Pvoid_t global_pointer_registry;
+#endif
+};
+
+
+
+// ----------------------------------------------------------------------------
+// validate each pointer is indexed once - internal checks only
+
+static inline void pointer_index_init(PGC *cache __maybe_unused) {
+#ifdef NETDATA_PGC_POINTER_CHECK
+ netdata_mutex_init(&cache->global_pointer_registry_mutex);
+#else
+ ;
+#endif
+}
+
+static inline void pointer_destroy_index(PGC *cache __maybe_unused) {
+#ifdef NETDATA_PGC_POINTER_CHECK
+ netdata_mutex_lock(&cache->global_pointer_registry_mutex);
+ JudyHSFreeArray(&cache->global_pointer_registry, PJE0);
+ netdata_mutex_unlock(&cache->global_pointer_registry_mutex);
+#else
+ ;
+#endif
+}
+static inline void pointer_add(PGC *cache __maybe_unused, PGC_PAGE *page __maybe_unused) {
+#ifdef NETDATA_PGC_POINTER_CHECK
+ netdata_mutex_lock(&cache->global_pointer_registry_mutex);
+ Pvoid_t *PValue = JudyHSIns(&cache->global_pointer_registry, &page, sizeof(void *), PJE0);
+ if(*PValue != NULL)
+ fatal("pointer already exists in registry");
+ *PValue = page;
+ netdata_mutex_unlock(&cache->global_pointer_registry_mutex);
+#else
+ ;
+#endif
+}
+
+static inline void pointer_check(PGC *cache __maybe_unused, PGC_PAGE *page __maybe_unused) {
+#ifdef NETDATA_PGC_POINTER_CHECK
+ netdata_mutex_lock(&cache->global_pointer_registry_mutex);
+ Pvoid_t *PValue = JudyHSGet(cache->global_pointer_registry, &page, sizeof(void *));
+ if(PValue == NULL)
+ fatal("pointer is not found in registry");
+ netdata_mutex_unlock(&cache->global_pointer_registry_mutex);
+#else
+ ;
+#endif
+}
+
+static inline void pointer_del(PGC *cache __maybe_unused, PGC_PAGE *page __maybe_unused) {
+#ifdef NETDATA_PGC_POINTER_CHECK
+ netdata_mutex_lock(&cache->global_pointer_registry_mutex);
+ int ret = JudyHSDel(&cache->global_pointer_registry, &page, sizeof(void *), PJE0);
+ if(!ret)
+ fatal("pointer to be deleted does not exist in registry");
+ netdata_mutex_unlock(&cache->global_pointer_registry_mutex);
+#else
+ ;
+#endif
+}
+
+// ----------------------------------------------------------------------------
+// locking
+
+static inline size_t pgc_indexing_partition(PGC *cache, Word_t metric_id) {
+ static __thread Word_t last_metric_id = 0;
+ static __thread size_t last_partition = 0;
+
+ if(metric_id == last_metric_id || cache->config.partitions == 1)
+ return last_partition;
+
+ last_metric_id = metric_id;
+ last_partition = indexing_partition(metric_id, cache->config.partitions);
+
+ return last_partition;
+}
+
+static inline void pgc_index_read_lock(PGC *cache, size_t partition) {
+ netdata_rwlock_rdlock(&cache->index[partition].rwlock);
+}
+static inline void pgc_index_read_unlock(PGC *cache, size_t partition) {
+ netdata_rwlock_unlock(&cache->index[partition].rwlock);
+}
+//static inline bool pgc_index_write_trylock(PGC *cache, size_t partition) {
+// return !netdata_rwlock_trywrlock(&cache->index[partition].rwlock);
+//}
+static inline void pgc_index_write_lock(PGC *cache, size_t partition) {
+ netdata_rwlock_wrlock(&cache->index[partition].rwlock);
+}
+static inline void pgc_index_write_unlock(PGC *cache, size_t partition) {
+ netdata_rwlock_unlock(&cache->index[partition].rwlock);
+}
+
+static inline bool pgc_ll_trylock(PGC *cache __maybe_unused, struct pgc_linked_list *ll) {
+ return netdata_spinlock_trylock(&ll->spinlock);
+}
+
+static inline void pgc_ll_lock(PGC *cache __maybe_unused, struct pgc_linked_list *ll) {
+ netdata_spinlock_lock(&ll->spinlock);
+}
+
+static inline void pgc_ll_unlock(PGC *cache __maybe_unused, struct pgc_linked_list *ll) {
+ netdata_spinlock_unlock(&ll->spinlock);
+}
+
+static inline bool page_transition_trylock(PGC *cache __maybe_unused, PGC_PAGE *page) {
+ return netdata_spinlock_trylock(&page->transition_spinlock);
+}
+
+static inline void page_transition_lock(PGC *cache __maybe_unused, PGC_PAGE *page) {
+ netdata_spinlock_lock(&page->transition_spinlock);
+}
+
+static inline void page_transition_unlock(PGC *cache __maybe_unused, PGC_PAGE *page) {
+ netdata_spinlock_unlock(&page->transition_spinlock);
+}
+
+// ----------------------------------------------------------------------------
+// evictions control
+
+static inline size_t cache_usage_per1000(PGC *cache, size_t *size_to_evict) {
+
+ if(size_to_evict)
+ netdata_spinlock_lock(&cache->usage.spinlock);
+
+ else if(!netdata_spinlock_trylock(&cache->usage.spinlock))
+ return __atomic_load_n(&cache->usage.per1000, __ATOMIC_RELAXED);
+
+ size_t current_cache_size;
+ size_t wanted_cache_size;
+ size_t per1000;
+
+ size_t dirty = __atomic_load_n(&cache->dirty.stats->size, __ATOMIC_RELAXED);
+ size_t hot = __atomic_load_n(&cache->hot.stats->size, __ATOMIC_RELAXED);
+
+ if(cache->config.options & PGC_OPTIONS_AUTOSCALE) {
+ size_t dirty_max = __atomic_load_n(&cache->dirty.stats->max_size, __ATOMIC_RELAXED);
+ size_t hot_max = __atomic_load_n(&cache->hot.stats->max_size, __ATOMIC_RELAXED);
+
+ // our promise to users
+ size_t max_size1 = MAX(hot_max, hot) * 2;
+
+ // protection against slow flushing
+ size_t max_size2 = hot_max + ((dirty_max < hot_max / 2) ? hot_max / 2 : dirty_max * 2);
+
+ // the final wanted cache size
+ wanted_cache_size = MIN(max_size1, max_size2);
+
+ if(cache->config.dynamic_target_size_cb) {
+ size_t wanted_cache_size_cb = cache->config.dynamic_target_size_cb();
+ if(wanted_cache_size_cb > wanted_cache_size)
+ wanted_cache_size = wanted_cache_size_cb;
+ }
+
+ if (wanted_cache_size < hot + dirty + cache->config.clean_size)
+ wanted_cache_size = hot + dirty + cache->config.clean_size;
+ }
+ else
+ wanted_cache_size = hot + dirty + cache->config.clean_size;
+
+ // protection again huge queries
+ // if huge queries are running, or huge amounts need to be saved
+ // allow the cache to grow more (hot pages in main cache are also referenced)
+ size_t referenced_size = __atomic_load_n(&cache->stats.referenced_size, __ATOMIC_RELAXED);
+ if(unlikely(wanted_cache_size < referenced_size * 2 / 3))
+ wanted_cache_size = referenced_size * 2 / 3;
+
+ current_cache_size = __atomic_load_n(&cache->stats.size, __ATOMIC_RELAXED); // + pgc_aral_overhead();
+
+ per1000 = (size_t)((unsigned long long)current_cache_size * 1000ULL / (unsigned long long)wanted_cache_size);
+
+ __atomic_store_n(&cache->usage.per1000, per1000, __ATOMIC_RELAXED);
+ __atomic_store_n(&cache->stats.wanted_cache_size, wanted_cache_size, __ATOMIC_RELAXED);
+ __atomic_store_n(&cache->stats.current_cache_size, current_cache_size, __ATOMIC_RELAXED);
+
+ netdata_spinlock_unlock(&cache->usage.spinlock);
+
+ if(size_to_evict) {
+ size_t target = (size_t)((unsigned long long)wanted_cache_size * (unsigned long long)cache->config.evict_low_threshold_per1000 / 1000ULL);
+ if(current_cache_size > target)
+ *size_to_evict = current_cache_size - target;
+ else
+ *size_to_evict = 0;
+ }
+
+ if(per1000 >= cache->config.severe_pressure_per1000)
+ __atomic_add_fetch(&cache->stats.events_cache_under_severe_pressure, 1, __ATOMIC_RELAXED);
+
+ else if(per1000 >= cache->config.aggressive_evict_per1000)
+ __atomic_add_fetch(&cache->stats.events_cache_needs_space_aggressively, 1, __ATOMIC_RELAXED);
+
+ return per1000;
+}
+
+static inline bool cache_pressure(PGC *cache, size_t limit) {
+ return (cache_usage_per1000(cache, NULL) >= limit);
+}
+
+#define cache_under_severe_pressure(cache) cache_pressure(cache, (cache)->config.severe_pressure_per1000)
+#define cache_needs_space_aggressively(cache) cache_pressure(cache, (cache)->config.aggressive_evict_per1000)
+#define cache_above_healthy_limit(cache) cache_pressure(cache, (cache)->config.healthy_size_per1000)
+
+typedef bool (*evict_filter)(PGC_PAGE *page, void *data);
+static bool evict_pages_with_filter(PGC *cache, size_t max_skip, size_t max_evict, bool wait, bool all_of_them, evict_filter filter, void *data);
+#define evict_pages(cache, max_skip, max_evict, wait, all_of_them) evict_pages_with_filter(cache, max_skip, max_evict, wait, all_of_them, NULL, NULL)
+
+static inline void evict_on_clean_page_added(PGC *cache __maybe_unused) {
+ if((cache->config.options & PGC_OPTIONS_EVICT_PAGES_INLINE) || cache_needs_space_aggressively(cache)) {
+ evict_pages(cache,
+ cache->config.max_skip_pages_per_inline_eviction,
+ cache->config.max_pages_per_inline_eviction,
+ false, false);
+ }
+}
+
+static inline void evict_on_page_release_when_permitted(PGC *cache __maybe_unused) {
+ if ((cache->config.options & PGC_OPTIONS_EVICT_PAGES_INLINE) || cache_under_severe_pressure(cache)) {
+ evict_pages(cache,
+ cache->config.max_skip_pages_per_inline_eviction,
+ cache->config.max_pages_per_inline_eviction,
+ false, false);
+ }
+}
+
+// ----------------------------------------------------------------------------
+// flushing control
+
+static bool flush_pages(PGC *cache, size_t max_flushes, Word_t section, bool wait, bool all_of_them);
+
+static inline bool flushing_critical(PGC *cache) {
+ if(unlikely(__atomic_load_n(&cache->dirty.stats->size, __ATOMIC_RELAXED) > __atomic_load_n(&cache->hot.stats->max_size, __ATOMIC_RELAXED))) {
+ __atomic_add_fetch(&cache->stats.events_flush_critical, 1, __ATOMIC_RELAXED);
+ return true;
+ }
+
+ return false;
+}
+
+// ----------------------------------------------------------------------------
+// helpers
+
+static inline size_t page_assumed_size(PGC *cache, size_t size) {
+ return size + (sizeof(PGC_PAGE) + cache->config.additional_bytes_per_page + sizeof(Word_t) * 3);
+}
+
+static inline size_t page_size_from_assumed_size(PGC *cache, size_t assumed_size) {
+ return assumed_size - (sizeof(PGC_PAGE) + cache->config.additional_bytes_per_page + sizeof(Word_t) * 3);
+}
+
+// ----------------------------------------------------------------------------
+// Linked list management
+
+static inline void atomic_set_max(size_t *max, size_t desired) {
+ size_t expected;
+
+ expected = __atomic_load_n(max, __ATOMIC_RELAXED);
+
+ do {
+
+ if(expected >= desired)
+ return;
+
+ } while(!__atomic_compare_exchange_n(max, &expected, desired,
+ false, __ATOMIC_RELAXED, __ATOMIC_RELAXED));
+}
+
+struct section_pages {
+ SPINLOCK migration_to_v2_spinlock;
+ size_t entries;
+ size_t size;
+ PGC_PAGE *base;
+};
+
+static ARAL *pgc_section_pages_aral = NULL;
+static void pgc_section_pages_static_aral_init(void) {
+ static SPINLOCK spinlock = NETDATA_SPINLOCK_INITIALIZER;
+
+ if(unlikely(!pgc_section_pages_aral)) {
+ netdata_spinlock_lock(&spinlock);
+
+ // we have to check again
+ if(!pgc_section_pages_aral)
+ pgc_section_pages_aral = aral_create(
+ "pgc_section",
+ sizeof(struct section_pages),
+ 0,
+ 65536, NULL,
+ NULL, NULL, false, false);
+
+ netdata_spinlock_unlock(&spinlock);
+ }
+}
+
+static inline void pgc_stats_ll_judy_change(PGC *cache, struct pgc_linked_list *ll, size_t mem_before_judyl, size_t mem_after_judyl) {
+ if(mem_after_judyl > mem_before_judyl) {
+ __atomic_add_fetch(&ll->stats->size, mem_after_judyl - mem_before_judyl, __ATOMIC_RELAXED);
+ __atomic_add_fetch(&cache->stats.size, mem_after_judyl - mem_before_judyl, __ATOMIC_RELAXED);
+ }
+ else if(mem_after_judyl < mem_before_judyl) {
+ __atomic_sub_fetch(&ll->stats->size, mem_before_judyl - mem_after_judyl, __ATOMIC_RELAXED);
+ __atomic_sub_fetch(&cache->stats.size, mem_before_judyl - mem_after_judyl, __ATOMIC_RELAXED);
+ }
+}
+
+static inline void pgc_stats_index_judy_change(PGC *cache, size_t mem_before_judyl, size_t mem_after_judyl) {
+ if(mem_after_judyl > mem_before_judyl) {
+ __atomic_add_fetch(&cache->stats.size, mem_after_judyl - mem_before_judyl, __ATOMIC_RELAXED);
+ }
+ else if(mem_after_judyl < mem_before_judyl) {
+ __atomic_sub_fetch(&cache->stats.size, mem_before_judyl - mem_after_judyl, __ATOMIC_RELAXED);
+ }
+}
+
+static void pgc_ll_add(PGC *cache __maybe_unused, struct pgc_linked_list *ll, PGC_PAGE *page, bool having_lock) {
+ if(!having_lock)
+ pgc_ll_lock(cache, ll);
+
+ internal_fatal(page_get_status_flags(page) != 0,
+ "DBENGINE CACHE: invalid page flags, the page has %d, but it is should be %d",
+ page_get_status_flags(page),
+ 0);
+
+ if(ll->linked_list_in_sections_judy) {
+ size_t mem_before_judyl, mem_after_judyl;
+
+ mem_before_judyl = JudyLMemUsed(ll->sections_judy);
+ Pvoid_t *section_pages_pptr = JudyLIns(&ll->sections_judy, page->section, PJE0);
+ mem_after_judyl = JudyLMemUsed(ll->sections_judy);
+
+ struct section_pages *sp = *section_pages_pptr;
+ if(!sp) {
+ // sp = callocz(1, sizeof(struct section_pages));
+ sp = aral_mallocz(pgc_section_pages_aral);
+ memset(sp, 0, sizeof(struct section_pages));
+
+ *section_pages_pptr = sp;
+
+ mem_after_judyl += sizeof(struct section_pages);
+ }
+ pgc_stats_ll_judy_change(cache, ll, mem_before_judyl, mem_after_judyl);
+
+ sp->entries++;
+ sp->size += page->assumed_size;
+ DOUBLE_LINKED_LIST_APPEND_ITEM_UNSAFE(sp->base, page, link.prev, link.next);
+
+ if((sp->entries % cache->config.max_dirty_pages_per_call) == 0)
+ ll->version++;
+ }
+ else {
+ // CLEAN pages end up here.
+ // - New pages created as CLEAN, always have 1 access.
+ // - DIRTY pages made CLEAN, depending on their accesses may be appended (accesses > 0) or prepended (accesses = 0).
+
+ if(page->accesses || page_flag_check(page, PGC_PAGE_HAS_BEEN_ACCESSED | PGC_PAGE_HAS_NO_DATA_IGNORE_ACCESSES) == PGC_PAGE_HAS_BEEN_ACCESSED) {
+ DOUBLE_LINKED_LIST_APPEND_ITEM_UNSAFE(ll->base, page, link.prev, link.next);
+ page_flag_clear(page, PGC_PAGE_HAS_BEEN_ACCESSED);
+ }
+ else
+ DOUBLE_LINKED_LIST_PREPEND_ITEM_UNSAFE(ll->base, page, link.prev, link.next);
+
+ ll->version++;
+ }
+
+ page_flag_set(page, ll->flags);
+
+ if(!having_lock)
+ pgc_ll_unlock(cache, ll);
+
+ size_t entries = __atomic_add_fetch(&ll->stats->entries, 1, __ATOMIC_RELAXED);
+ size_t size = __atomic_add_fetch(&ll->stats->size, page->assumed_size, __ATOMIC_RELAXED);
+ __atomic_add_fetch(&ll->stats->added_entries, 1, __ATOMIC_RELAXED);
+ __atomic_add_fetch(&ll->stats->added_size, page->assumed_size, __ATOMIC_RELAXED);
+
+ atomic_set_max(&ll->stats->max_entries, entries);
+ atomic_set_max(&ll->stats->max_size, size);
+}
+
+static void pgc_ll_del(PGC *cache __maybe_unused, struct pgc_linked_list *ll, PGC_PAGE *page, bool having_lock) {
+ __atomic_sub_fetch(&ll->stats->entries, 1, __ATOMIC_RELAXED);
+ __atomic_sub_fetch(&ll->stats->size, page->assumed_size, __ATOMIC_RELAXED);
+ __atomic_add_fetch(&ll->stats->removed_entries, 1, __ATOMIC_RELAXED);
+ __atomic_add_fetch(&ll->stats->removed_size, page->assumed_size, __ATOMIC_RELAXED);
+
+ if(!having_lock)
+ pgc_ll_lock(cache, ll);
+
+ internal_fatal(page_get_status_flags(page) != ll->flags,
+ "DBENGINE CACHE: invalid page flags, the page has %d, but it is should be %d",
+ page_get_status_flags(page),
+ ll->flags);
+
+ page_flag_clear(page, ll->flags);
+
+ if(ll->linked_list_in_sections_judy) {
+ Pvoid_t *section_pages_pptr = JudyLGet(ll->sections_judy, page->section, PJE0);
+ internal_fatal(!section_pages_pptr, "DBENGINE CACHE: page should be in Judy LL, but it is not");
+
+ struct section_pages *sp = *section_pages_pptr;
+ sp->entries--;
+ sp->size -= page->assumed_size;
+ DOUBLE_LINKED_LIST_REMOVE_ITEM_UNSAFE(sp->base, page, link.prev, link.next);
+
+ if(!sp->base) {
+ size_t mem_before_judyl, mem_after_judyl;
+
+ mem_before_judyl = JudyLMemUsed(ll->sections_judy);
+ int rc = JudyLDel(&ll->sections_judy, page->section, PJE0);
+ mem_after_judyl = JudyLMemUsed(ll->sections_judy);
+
+ if(!rc)
+ fatal("DBENGINE CACHE: cannot delete section from Judy LL");
+
+ // freez(sp);
+ aral_freez(pgc_section_pages_aral, sp);
+ mem_after_judyl -= sizeof(struct section_pages);
+ pgc_stats_ll_judy_change(cache, ll, mem_before_judyl, mem_after_judyl);
+ }
+ }
+ else {
+ DOUBLE_LINKED_LIST_REMOVE_ITEM_UNSAFE(ll->base, page, link.prev, link.next);
+ ll->version++;
+ }
+
+ if(!having_lock)
+ pgc_ll_unlock(cache, ll);
+}
+
+static inline void page_has_been_accessed(PGC *cache, PGC_PAGE *page) {
+ PGC_PAGE_FLAGS flags = page_flag_check(page, PGC_PAGE_CLEAN | PGC_PAGE_HAS_NO_DATA_IGNORE_ACCESSES);
+
+ if (!(flags & PGC_PAGE_HAS_NO_DATA_IGNORE_ACCESSES)) {
+ __atomic_add_fetch(&page->accesses, 1, __ATOMIC_RELAXED);
+
+ if (flags & PGC_PAGE_CLEAN) {
+ if(pgc_ll_trylock(cache, &cache->clean)) {
+ DOUBLE_LINKED_LIST_REMOVE_ITEM_UNSAFE(cache->clean.base, page, link.prev, link.next);
+ DOUBLE_LINKED_LIST_APPEND_ITEM_UNSAFE(cache->clean.base, page, link.prev, link.next);
+ pgc_ll_unlock(cache, &cache->clean);
+ page_flag_clear(page, PGC_PAGE_HAS_BEEN_ACCESSED);
+ }
+ else
+ page_flag_set(page, PGC_PAGE_HAS_BEEN_ACCESSED);
+ }
+ }
+}
+
+
+// ----------------------------------------------------------------------------
+// state transitions
+
+static inline void page_set_clean(PGC *cache, PGC_PAGE *page, bool having_transition_lock, bool having_clean_lock) {
+ if(!having_transition_lock)
+ page_transition_lock(cache, page);
+
+ PGC_PAGE_FLAGS flags = page_get_status_flags(page);
+
+ if(flags & PGC_PAGE_CLEAN) {
+ if(!having_transition_lock)
+ page_transition_unlock(cache, page);
+ return;
+ }
+
+ if(flags & PGC_PAGE_HOT)
+ pgc_ll_del(cache, &cache->hot, page, false);
+
+ if(flags & PGC_PAGE_DIRTY)
+ pgc_ll_del(cache, &cache->dirty, page, false);
+
+ // first add to linked list, the set the flag (required for move_page_last())
+ pgc_ll_add(cache, &cache->clean, page, having_clean_lock);
+
+ if(!having_transition_lock)
+ page_transition_unlock(cache, page);
+}
+
+static inline void page_set_dirty(PGC *cache, PGC_PAGE *page, bool having_hot_lock) {
+ if(!having_hot_lock)
+ // to avoid deadlocks, we have to get the hot lock before the page transition
+ // since this is what all_hot_to_dirty() does
+ pgc_ll_lock(cache, &cache->hot);
+
+ page_transition_lock(cache, page);
+
+ PGC_PAGE_FLAGS flags = page_get_status_flags(page);
+
+ if(flags & PGC_PAGE_DIRTY) {
+ page_transition_unlock(cache, page);
+
+ if(!having_hot_lock)
+ // we don't need the hot lock anymore
+ pgc_ll_unlock(cache, &cache->hot);
+
+ return;
+ }
+
+ __atomic_add_fetch(&cache->stats.hot2dirty_entries, 1, __ATOMIC_RELAXED);
+ __atomic_add_fetch(&cache->stats.hot2dirty_size, page->assumed_size, __ATOMIC_RELAXED);
+
+ if(likely(flags & PGC_PAGE_HOT))
+ pgc_ll_del(cache, &cache->hot, page, true);
+
+ if(!having_hot_lock)
+ // we don't need the hot lock anymore
+ pgc_ll_unlock(cache, &cache->hot);
+
+ if(unlikely(flags & PGC_PAGE_CLEAN))
+ pgc_ll_del(cache, &cache->clean, page, false);
+
+ // first add to linked list, the set the flag (required for move_page_last())
+ pgc_ll_add(cache, &cache->dirty, page, false);
+
+ __atomic_sub_fetch(&cache->stats.hot2dirty_entries, 1, __ATOMIC_RELAXED);
+ __atomic_sub_fetch(&cache->stats.hot2dirty_size, page->assumed_size, __ATOMIC_RELAXED);
+
+ page_transition_unlock(cache, page);
+}
+
+static inline void page_set_hot(PGC *cache, PGC_PAGE *page) {
+ page_transition_lock(cache, page);
+
+ PGC_PAGE_FLAGS flags = page_get_status_flags(page);
+
+ if(flags & PGC_PAGE_HOT) {
+ page_transition_unlock(cache, page);
+ return;
+ }
+
+ if(flags & PGC_PAGE_DIRTY)
+ pgc_ll_del(cache, &cache->dirty, page, false);
+
+ if(flags & PGC_PAGE_CLEAN)
+ pgc_ll_del(cache, &cache->clean, page, false);
+
+ // first add to linked list, the set the flag (required for move_page_last())
+ pgc_ll_add(cache, &cache->hot, page, false);
+
+ page_transition_unlock(cache, page);
+}
+
+
+// ----------------------------------------------------------------------------
+// Referencing
+
+static inline size_t PGC_REFERENCED_PAGES(PGC *cache) {
+ return __atomic_load_n(&cache->stats.referenced_entries, __ATOMIC_RELAXED);
+}
+
+static inline void PGC_REFERENCED_PAGES_PLUS1(PGC *cache, PGC_PAGE *page) {
+ __atomic_add_fetch(&cache->stats.referenced_entries, 1, __ATOMIC_RELAXED);
+ __atomic_add_fetch(&cache->stats.referenced_size, page->assumed_size, __ATOMIC_RELAXED);
+}
+
+static inline void PGC_REFERENCED_PAGES_MINUS1(PGC *cache, size_t assumed_size) {
+ __atomic_sub_fetch(&cache->stats.referenced_entries, 1, __ATOMIC_RELAXED);
+ __atomic_sub_fetch(&cache->stats.referenced_size, assumed_size, __ATOMIC_RELAXED);
+}
+
+// If the page is not already acquired,
+// YOU HAVE TO HAVE THE QUEUE (hot, dirty, clean) THE PAGE IS IN, L O C K E D !
+// If you don't have it locked, NOTHING PREVENTS THIS PAGE FOR VANISHING WHILE THIS IS CALLED!
+static inline bool page_acquire(PGC *cache, PGC_PAGE *page) {
+ __atomic_add_fetch(&cache->stats.acquires, 1, __ATOMIC_RELAXED);
+
+ REFCOUNT expected, desired;
+
+ expected = __atomic_load_n(&page->refcount, __ATOMIC_RELAXED);
+ size_t spins = 0;
+
+ do {
+ spins++;
+
+ if(unlikely(expected < 0))
+ return false;
+
+ desired = expected + 1;
+
+ } while(!__atomic_compare_exchange_n(&page->refcount, &expected, desired, false, __ATOMIC_ACQUIRE, __ATOMIC_RELAXED));
+
+ if(unlikely(spins > 1))
+ __atomic_add_fetch(&cache->stats.acquire_spins, spins - 1, __ATOMIC_RELAXED);
+
+ if(desired == 1)
+ PGC_REFERENCED_PAGES_PLUS1(cache, page);
+
+ return true;
+}
+
+static inline void page_release(PGC *cache, PGC_PAGE *page, bool evict_if_necessary) {
+ __atomic_add_fetch(&cache->stats.releases, 1, __ATOMIC_RELAXED);
+
+ size_t assumed_size = page->assumed_size; // take the size before we release it
+ REFCOUNT expected, desired;
+
+ expected = __atomic_load_n(&page->refcount, __ATOMIC_RELAXED);
+
+ size_t spins = 0;
+ do {
+ spins++;
+
+ internal_fatal(expected <= 0,
+ "DBENGINE CACHE: trying to release a page with reference counter %d", expected);
+
+ desired = expected - 1;
+
+ } while(!__atomic_compare_exchange_n(&page->refcount, &expected, desired, false, __ATOMIC_RELEASE, __ATOMIC_RELAXED));
+
+ if(unlikely(spins > 1))
+ __atomic_add_fetch(&cache->stats.release_spins, spins - 1, __ATOMIC_RELAXED);
+
+ if(desired == 0) {
+ PGC_REFERENCED_PAGES_MINUS1(cache, assumed_size);
+
+ if(evict_if_necessary)
+ evict_on_page_release_when_permitted(cache);
+ }
+}
+
+static inline bool non_acquired_page_get_for_deletion___while_having_clean_locked(PGC *cache __maybe_unused, PGC_PAGE *page) {
+ __atomic_add_fetch(&cache->stats.acquires_for_deletion, 1, __ATOMIC_RELAXED);
+
+ internal_fatal(!is_page_clean(page),
+ "DBENGINE CACHE: only clean pages can be deleted");
+
+ REFCOUNT expected, desired;
+
+ expected = __atomic_load_n(&page->refcount, __ATOMIC_RELAXED);
+ size_t spins = 0;
+ bool delete_it;
+
+ do {
+ spins++;
+
+ if (expected == 0) {
+ desired = REFCOUNT_DELETING;
+ delete_it = true;
+ }
+ else {
+ delete_it = false;
+ break;
+ }
+
+ } while(!__atomic_compare_exchange_n(&page->refcount, &expected, desired, false, __ATOMIC_RELEASE, __ATOMIC_RELAXED));
+
+ if(delete_it) {
+ // we can delete this page
+ internal_fatal(page_flag_check(page, PGC_PAGE_IS_BEING_DELETED),
+ "DBENGINE CACHE: page is already being deleted");
+
+ page_flag_set(page, PGC_PAGE_IS_BEING_DELETED);
+ }
+
+ if(unlikely(spins > 1))
+ __atomic_add_fetch(&cache->stats.delete_spins, spins - 1, __ATOMIC_RELAXED);
+
+ return delete_it;
+}
+
+static inline bool acquired_page_get_for_deletion_or_release_it(PGC *cache __maybe_unused, PGC_PAGE *page) {
+ __atomic_add_fetch(&cache->stats.acquires_for_deletion, 1, __ATOMIC_RELAXED);
+
+ size_t assumed_size = page->assumed_size; // take the size before we release it
+
+ REFCOUNT expected, desired;
+
+ expected = __atomic_load_n(&page->refcount, __ATOMIC_RELAXED);
+ size_t spins = 0;
+ bool delete_it;
+
+ do {
+ spins++;
+
+ internal_fatal(expected < 1,
+ "DBENGINE CACHE: page to be deleted should be acquired by the caller.");
+
+ if (expected == 1) {
+ // we are the only one having this page referenced
+ desired = REFCOUNT_DELETING;
+ delete_it = true;
+ }
+ else {
+ // this page cannot be deleted
+ desired = expected - 1;
+ delete_it = false;
+ }
+
+ } while(!__atomic_compare_exchange_n(&page->refcount, &expected, desired, false, __ATOMIC_RELEASE, __ATOMIC_RELAXED));
+
+ if(delete_it) {
+ PGC_REFERENCED_PAGES_MINUS1(cache, assumed_size);
+
+ // we can delete this page
+ internal_fatal(page_flag_check(page, PGC_PAGE_IS_BEING_DELETED),
+ "DBENGINE CACHE: page is already being deleted");
+
+ page_flag_set(page, PGC_PAGE_IS_BEING_DELETED);
+ }
+
+ if(unlikely(spins > 1))
+ __atomic_add_fetch(&cache->stats.delete_spins, spins - 1, __ATOMIC_RELAXED);
+
+ return delete_it;
+}
+
+
+// ----------------------------------------------------------------------------
+// Indexing
+
+static inline void free_this_page(PGC *cache, PGC_PAGE *page, size_t partition __maybe_unused) {
+ // call the callback to free the user supplied memory
+ cache->config.pgc_free_clean_cb(cache, (PGC_ENTRY){
+ .section = page->section,
+ .metric_id = page->metric_id,
+ .start_time_s = page->start_time_s,
+ .end_time_s = __atomic_load_n(&page->end_time_s, __ATOMIC_RELAXED),
+ .update_every_s = page->update_every_s,
+ .size = page_size_from_assumed_size(cache, page->assumed_size),
+ .hot = (is_page_hot(page)) ? true : false,
+ .data = page->data,
+ .custom_data = (cache->config.additional_bytes_per_page) ? page->custom_data : NULL,
+ });
+
+ // update statistics
+ __atomic_add_fetch(&cache->stats.removed_entries, 1, __ATOMIC_RELAXED);
+ __atomic_add_fetch(&cache->stats.removed_size, page->assumed_size, __ATOMIC_RELAXED);
+
+ __atomic_sub_fetch(&cache->stats.entries, 1, __ATOMIC_RELAXED);
+ __atomic_sub_fetch(&cache->stats.size, page->assumed_size, __ATOMIC_RELAXED);
+
+ // free our memory
+#ifdef PGC_WITH_ARAL
+ aral_freez(cache->aral[partition], page);
+#else
+ freez(page);
+#endif
+}
+
+static void remove_this_page_from_index_unsafe(PGC *cache, PGC_PAGE *page, size_t partition) {
+ // remove it from the Judy arrays
+
+ pointer_check(cache, page);
+
+ internal_fatal(page_flag_check(page, PGC_PAGE_HOT | PGC_PAGE_DIRTY | PGC_PAGE_CLEAN),
+ "DBENGINE CACHE: page to be removed from the cache is still in the linked-list");
+
+ internal_fatal(!page_flag_check(page, PGC_PAGE_IS_BEING_DELETED),
+ "DBENGINE CACHE: page to be removed from the index, is not marked for deletion");
+
+ internal_fatal(partition != pgc_indexing_partition(cache, page->metric_id),
+ "DBENGINE CACHE: attempted to remove this page from the wrong partition of the cache");
+
+ Pvoid_t *metrics_judy_pptr = JudyLGet(cache->index[partition].sections_judy, page->section, PJE0);
+ if(unlikely(!metrics_judy_pptr))
+ fatal("DBENGINE CACHE: section '%lu' should exist, but it does not.", page->section);
+
+ Pvoid_t *pages_judy_pptr = JudyLGet(*metrics_judy_pptr, page->metric_id, PJE0);
+ if(unlikely(!pages_judy_pptr))
+ fatal("DBENGINE CACHE: metric '%lu' in section '%lu' should exist, but it does not.",
+ page->metric_id, page->section);
+
+ Pvoid_t *page_ptr = JudyLGet(*pages_judy_pptr, page->start_time_s, PJE0);
+ if(unlikely(!page_ptr))
+ fatal("DBENGINE CACHE: page with start time '%ld' of metric '%lu' in section '%lu' should exist, but it does not.",
+ page->start_time_s, page->metric_id, page->section);
+
+ PGC_PAGE *found_page = *page_ptr;
+ if(unlikely(found_page != page))
+ fatal("DBENGINE CACHE: page with start time '%ld' of metric '%lu' in section '%lu' should exist, but the index returned a different address.",
+ page->start_time_s, page->metric_id, page->section);
+
+ size_t mem_before_judyl = 0, mem_after_judyl = 0;
+
+ mem_before_judyl += JudyLMemUsed(*pages_judy_pptr);
+ if(unlikely(!JudyLDel(pages_judy_pptr, page->start_time_s, PJE0)))
+ fatal("DBENGINE CACHE: page with start time '%ld' of metric '%lu' in section '%lu' exists, but cannot be deleted.",
+ page->start_time_s, page->metric_id, page->section);
+ mem_after_judyl += JudyLMemUsed(*pages_judy_pptr);
+
+ mem_before_judyl += JudyLMemUsed(*metrics_judy_pptr);
+ if(!*pages_judy_pptr && !JudyLDel(metrics_judy_pptr, page->metric_id, PJE0))
+ fatal("DBENGINE CACHE: metric '%lu' in section '%lu' exists and is empty, but cannot be deleted.",
+ page->metric_id, page->section);
+ mem_after_judyl += JudyLMemUsed(*metrics_judy_pptr);
+
+ mem_before_judyl += JudyLMemUsed(cache->index[partition].sections_judy);
+ if(!*metrics_judy_pptr && !JudyLDel(&cache->index[partition].sections_judy, page->section, PJE0))
+ fatal("DBENGINE CACHE: section '%lu' exists and is empty, but cannot be deleted.", page->section);
+ mem_after_judyl += JudyLMemUsed(cache->index[partition].sections_judy);
+
+ pgc_stats_index_judy_change(cache, mem_before_judyl, mem_after_judyl);
+
+ pointer_del(cache, page);
+}
+
+static inline void remove_and_free_page_not_in_any_queue_and_acquired_for_deletion(PGC *cache, PGC_PAGE *page) {
+ size_t partition = pgc_indexing_partition(cache, page->metric_id);
+ pgc_index_write_lock(cache, partition);
+ remove_this_page_from_index_unsafe(cache, page, partition);
+ pgc_index_write_unlock(cache, partition);
+ free_this_page(cache, page, partition);
+}
+
+static inline bool make_acquired_page_clean_and_evict_or_page_release(PGC *cache, PGC_PAGE *page) {
+ pointer_check(cache, page);
+
+ page_transition_lock(cache, page);
+ pgc_ll_lock(cache, &cache->clean);
+
+ // make it clean - it does not have any accesses, so it will be prepended
+ page_set_clean(cache, page, true, true);
+
+ if(!acquired_page_get_for_deletion_or_release_it(cache, page)) {
+ pgc_ll_unlock(cache, &cache->clean);
+ page_transition_unlock(cache, page);
+ return false;
+ }
+
+ // remove it from the linked list
+ pgc_ll_del(cache, &cache->clean, page, true);
+ pgc_ll_unlock(cache, &cache->clean);
+ page_transition_unlock(cache, page);
+
+ remove_and_free_page_not_in_any_queue_and_acquired_for_deletion(cache, page);
+
+ return true;
+}
+
+// returns true, when there is more work to do
+static bool evict_pages_with_filter(PGC *cache, size_t max_skip, size_t max_evict, bool wait, bool all_of_them, evict_filter filter, void *data) {
+ size_t per1000 = cache_usage_per1000(cache, NULL);
+
+ if(!all_of_them && per1000 < cache->config.healthy_size_per1000)
+ // don't bother - not enough to do anything
+ return false;
+
+ size_t workers_running = __atomic_add_fetch(&cache->stats.workers_evict, 1, __ATOMIC_RELAXED);
+ if(!wait && !all_of_them && workers_running > cache->config.max_workers_evict_inline && per1000 < cache->config.severe_pressure_per1000) {
+ __atomic_sub_fetch(&cache->stats.workers_evict, 1, __ATOMIC_RELAXED);
+ return false;
+ }
+
+ internal_fatal(cache->clean.linked_list_in_sections_judy,
+ "wrong clean pages configuration - clean pages need to have a linked list, not a judy array");
+
+ if(unlikely(!max_skip))
+ max_skip = SIZE_MAX;
+ else if(unlikely(max_skip < 2))
+ max_skip = 2;
+
+ if(unlikely(!max_evict))
+ max_evict = SIZE_MAX;
+ else if(unlikely(max_evict < 2))
+ max_evict = 2;
+
+ size_t total_pages_evicted = 0;
+ size_t total_pages_skipped = 0;
+ bool stopped_before_finishing = false;
+ size_t spins = 0;
+
+ do {
+ if(++spins > 1)
+ __atomic_add_fetch(&cache->stats.evict_spins, 1, __ATOMIC_RELAXED);
+
+ bool batch;
+ size_t max_size_to_evict = 0;
+ if (unlikely(all_of_them)) {
+ max_size_to_evict = SIZE_MAX;
+ batch = true;
+ }
+ else if(unlikely(wait)) {
+ per1000 = cache_usage_per1000(cache, &max_size_to_evict);
+ batch = (wait && per1000 > cache->config.severe_pressure_per1000) ? true : false;
+ }
+ else {
+ batch = false;
+ max_size_to_evict = (cache_above_healthy_limit(cache)) ? 1 : 0;
+ }
+
+ if (!max_size_to_evict)
+ break;
+
+ // check if we have to stop
+ if(total_pages_evicted >= max_evict && !all_of_them) {
+ stopped_before_finishing = true;
+ break;
+ }
+
+ if(!all_of_them && !wait) {
+ if(!pgc_ll_trylock(cache, &cache->clean)) {
+ stopped_before_finishing = true;
+ goto premature_exit;
+ }
+
+ // at this point we have the clean lock
+ }
+ else
+ pgc_ll_lock(cache, &cache->clean);
+
+ // find a page to evict
+ PGC_PAGE *pages_to_evict = NULL;
+ size_t pages_to_evict_size = 0;
+ for(PGC_PAGE *page = cache->clean.base, *next = NULL, *first_page_we_relocated = NULL; page ; page = next) {
+ next = page->link.next;
+
+ if(unlikely(page == first_page_we_relocated))
+ // we did a complete loop on all pages
+ break;
+
+ if(unlikely(page_flag_check(page, PGC_PAGE_HAS_BEEN_ACCESSED | PGC_PAGE_HAS_NO_DATA_IGNORE_ACCESSES) == PGC_PAGE_HAS_BEEN_ACCESSED)) {
+ DOUBLE_LINKED_LIST_REMOVE_ITEM_UNSAFE(cache->clean.base, page, link.prev, link.next);
+ DOUBLE_LINKED_LIST_APPEND_ITEM_UNSAFE(cache->clean.base, page, link.prev, link.next);
+ page_flag_clear(page, PGC_PAGE_HAS_BEEN_ACCESSED);
+ continue;
+ }
+
+ if(unlikely(filter && !filter(page, data)))
+ continue;
+
+ if(non_acquired_page_get_for_deletion___while_having_clean_locked(cache, page)) {
+ // we can delete this page
+
+ // remove it from the clean list
+ pgc_ll_del(cache, &cache->clean, page, true);
+
+ __atomic_add_fetch(&cache->stats.evicting_entries, 1, __ATOMIC_RELAXED);
+ __atomic_add_fetch(&cache->stats.evicting_size, page->assumed_size, __ATOMIC_RELAXED);
+
+ DOUBLE_LINKED_LIST_APPEND_ITEM_UNSAFE(pages_to_evict, page, link.prev, link.next);
+
+ pages_to_evict_size += page->assumed_size;
+
+ if(unlikely(all_of_them || (batch && pages_to_evict_size < max_size_to_evict)))
+ // get more pages
+ ;
+ else
+ // one page at a time
+ break;
+ }
+ else {
+ // we can't delete this page
+
+ if(!first_page_we_relocated)
+ first_page_we_relocated = page;
+
+ DOUBLE_LINKED_LIST_REMOVE_ITEM_UNSAFE(cache->clean.base, page, link.prev, link.next);
+ DOUBLE_LINKED_LIST_APPEND_ITEM_UNSAFE(cache->clean.base, page, link.prev, link.next);
+
+ // check if we have to stop
+ if(unlikely(++total_pages_skipped >= max_skip && !all_of_them)) {
+ stopped_before_finishing = true;
+ break;
+ }
+ }
+ }
+ pgc_ll_unlock(cache, &cache->clean);
+
+ if(likely(pages_to_evict)) {
+ // remove them from the index
+
+ if(unlikely(pages_to_evict->link.next)) {
+ // we have many pages, let's minimize the index locks we are going to get
+
+ PGC_PAGE *pages_per_partition[cache->config.partitions];
+ memset(pages_per_partition, 0, sizeof(PGC_PAGE *) * cache->config.partitions);
+
+ // sort them by partition
+ for (PGC_PAGE *page = pages_to_evict, *next = NULL; page; page = next) {
+ next = page->link.next;
+
+ size_t partition = pgc_indexing_partition(cache, page->metric_id);
+ DOUBLE_LINKED_LIST_REMOVE_ITEM_UNSAFE(pages_to_evict, page, link.prev, link.next);
+ DOUBLE_LINKED_LIST_APPEND_ITEM_UNSAFE(pages_per_partition[partition], page, link.prev, link.next);
+ }
+
+ // remove them from the index
+ for (size_t partition = 0; partition < cache->config.partitions; partition++) {
+ if (!pages_per_partition[partition]) continue;
+
+ pgc_index_write_lock(cache, partition);
+
+ for (PGC_PAGE *page = pages_per_partition[partition]; page; page = page->link.next)
+ remove_this_page_from_index_unsafe(cache, page, partition);
+
+ pgc_index_write_unlock(cache, partition);
+ }
+
+ // free them
+ for (size_t partition = 0; partition < cache->config.partitions; partition++) {
+ if (!pages_per_partition[partition]) continue;
+
+ for (PGC_PAGE *page = pages_per_partition[partition], *next = NULL; page; page = next) {
+ next = page->link.next;
+
+ size_t page_size = page->assumed_size;
+ free_this_page(cache, page, partition);
+
+ __atomic_sub_fetch(&cache->stats.evicting_entries, 1, __ATOMIC_RELAXED);
+ __atomic_sub_fetch(&cache->stats.evicting_size, page_size, __ATOMIC_RELAXED);
+
+ total_pages_evicted++;
+ }
+ }
+ }
+ else {
+ // just one page to be evicted
+ PGC_PAGE *page = pages_to_evict;
+
+ size_t page_size = page->assumed_size;
+
+ size_t partition = pgc_indexing_partition(cache, page->metric_id);
+ pgc_index_write_lock(cache, partition);
+ remove_this_page_from_index_unsafe(cache, page, partition);
+ pgc_index_write_unlock(cache, partition);
+ free_this_page(cache, page, partition);
+
+ __atomic_sub_fetch(&cache->stats.evicting_entries, 1, __ATOMIC_RELAXED);
+ __atomic_sub_fetch(&cache->stats.evicting_size, page_size, __ATOMIC_RELAXED);
+
+ total_pages_evicted++;
+ }
+ }
+ else
+ break;
+
+ } while(all_of_them || (total_pages_evicted < max_evict && total_pages_skipped < max_skip));
+
+ if(all_of_them && !filter) {
+ pgc_ll_lock(cache, &cache->clean);
+ if(cache->clean.stats->entries) {
+ error_limit_static_global_var(erl, 1, 0);
+ error_limit(&erl, "DBENGINE CACHE: cannot free all clean pages, %zu are still in the clean queue",
+ cache->clean.stats->entries);
+ }
+ pgc_ll_unlock(cache, &cache->clean);
+ }
+
+premature_exit:
+ if(unlikely(total_pages_skipped))
+ __atomic_add_fetch(&cache->stats.evict_skipped, total_pages_skipped, __ATOMIC_RELAXED);
+
+ __atomic_sub_fetch(&cache->stats.workers_evict, 1, __ATOMIC_RELAXED);
+
+ return stopped_before_finishing;
+}
+
+static PGC_PAGE *page_add(PGC *cache, PGC_ENTRY *entry, bool *added) {
+ __atomic_add_fetch(&cache->stats.workers_add, 1, __ATOMIC_RELAXED);
+
+ size_t partition = pgc_indexing_partition(cache, entry->metric_id);
+
+#ifdef PGC_WITH_ARAL
+ PGC_PAGE *allocation = aral_mallocz(cache->aral[partition]);
+#endif
+ PGC_PAGE *page;
+ size_t spins = 0;
+
+ do {
+ if(++spins > 1)
+ __atomic_add_fetch(&cache->stats.insert_spins, 1, __ATOMIC_RELAXED);
+
+ pgc_index_write_lock(cache, partition);
+
+ size_t mem_before_judyl = 0, mem_after_judyl = 0;
+
+ mem_before_judyl += JudyLMemUsed(cache->index[partition].sections_judy);
+ Pvoid_t *metrics_judy_pptr = JudyLIns(&cache->index[partition].sections_judy, entry->section, PJE0);
+ if(unlikely(!metrics_judy_pptr || metrics_judy_pptr == PJERR))
+ fatal("DBENGINE CACHE: corrupted sections judy array");
+ mem_after_judyl += JudyLMemUsed(cache->index[partition].sections_judy);
+
+ mem_before_judyl += JudyLMemUsed(*metrics_judy_pptr);
+ Pvoid_t *pages_judy_pptr = JudyLIns(metrics_judy_pptr, entry->metric_id, PJE0);
+ if(unlikely(!pages_judy_pptr || pages_judy_pptr == PJERR))
+ fatal("DBENGINE CACHE: corrupted pages judy array");
+ mem_after_judyl += JudyLMemUsed(*metrics_judy_pptr);
+
+ mem_before_judyl += JudyLMemUsed(*pages_judy_pptr);
+ Pvoid_t *page_ptr = JudyLIns(pages_judy_pptr, entry->start_time_s, PJE0);
+ if(unlikely(!page_ptr || page_ptr == PJERR))
+ fatal("DBENGINE CACHE: corrupted page in judy array");
+ mem_after_judyl += JudyLMemUsed(*pages_judy_pptr);
+
+ pgc_stats_index_judy_change(cache, mem_before_judyl, mem_after_judyl);
+
+ page = *page_ptr;
+
+ if (likely(!page)) {
+#ifdef PGC_WITH_ARAL
+ page = allocation;
+ allocation = NULL;
+#else
+ page = mallocz(sizeof(PGC_PAGE) + cache->config.additional_bytes_per_page);
+#endif
+ page->refcount = 1;
+ page->accesses = (entry->hot) ? 0 : 1;
+ page->flags = 0;
+ page->section = entry->section;
+ page->metric_id = entry->metric_id;
+ page->start_time_s = entry->start_time_s;
+ page->end_time_s = entry->end_time_s,
+ page->update_every_s = entry->update_every_s,
+ page->data = entry->data;
+ page->assumed_size = page_assumed_size(cache, entry->size);
+ netdata_spinlock_init(&page->transition_spinlock);
+ page->link.prev = NULL;
+ page->link.next = NULL;
+
+ if(cache->config.additional_bytes_per_page) {
+ if(entry->custom_data)
+ memcpy(page->custom_data, entry->custom_data, cache->config.additional_bytes_per_page);
+ else
+ memset(page->custom_data, 0, cache->config.additional_bytes_per_page);
+ }
+
+ // put it in the index
+ *page_ptr = page;
+ pointer_add(cache, page);
+ pgc_index_write_unlock(cache, partition);
+
+ if (entry->hot)
+ page_set_hot(cache, page);
+ else
+ page_set_clean(cache, page, false, false);
+
+ PGC_REFERENCED_PAGES_PLUS1(cache, page);
+
+ // update statistics
+ __atomic_add_fetch(&cache->stats.added_entries, 1, __ATOMIC_RELAXED);
+ __atomic_add_fetch(&cache->stats.added_size, page->assumed_size, __ATOMIC_RELAXED);
+
+ __atomic_add_fetch(&cache->stats.entries, 1, __ATOMIC_RELAXED);
+ __atomic_add_fetch(&cache->stats.size, page->assumed_size, __ATOMIC_RELAXED);
+
+ if(added)
+ *added = true;
+ }
+ else {
+ if (!page_acquire(cache, page))
+ page = NULL;
+
+ else if(added)
+ *added = false;
+
+ pgc_index_write_unlock(cache, partition);
+
+ if(unlikely(!page)) {
+ // now that we don't have the lock,
+ // give it some time for the old page to go away
+ struct timespec ns = { .tv_sec = 0, .tv_nsec = 1 };
+ nanosleep(&ns, NULL);
+ }
+ }
+
+ } while(!page);
+
+#ifdef PGC_WITH_ARAL
+ if(allocation)
+ aral_freez(cache->aral[partition], allocation);
+#endif
+
+ __atomic_sub_fetch(&cache->stats.workers_add, 1, __ATOMIC_RELAXED);
+
+ if(!entry->hot)
+ evict_on_clean_page_added(cache);
+
+ if((cache->config.options & PGC_OPTIONS_FLUSH_PAGES_INLINE) || flushing_critical(cache)) {
+ flush_pages(cache, cache->config.max_flushes_inline, PGC_SECTION_ALL,
+ false, false);
+ }
+
+ return page;
+}
+
+static PGC_PAGE *page_find_and_acquire(PGC *cache, Word_t section, Word_t metric_id, time_t start_time_s, PGC_SEARCH method) {
+ __atomic_add_fetch(&cache->stats.workers_search, 1, __ATOMIC_RELAXED);
+
+ size_t *stats_hit_ptr, *stats_miss_ptr;
+
+ if(method == PGC_SEARCH_CLOSEST) {
+ __atomic_add_fetch(&cache->stats.searches_closest, 1, __ATOMIC_RELAXED);
+ stats_hit_ptr = &cache->stats.searches_closest_hits;
+ stats_miss_ptr = &cache->stats.searches_closest_misses;
+ }
+ else {
+ __atomic_add_fetch(&cache->stats.searches_exact, 1, __ATOMIC_RELAXED);
+ stats_hit_ptr = &cache->stats.searches_exact_hits;
+ stats_miss_ptr = &cache->stats.searches_exact_misses;
+ }
+
+ PGC_PAGE *page = NULL;
+ size_t partition = pgc_indexing_partition(cache, metric_id);
+
+ pgc_index_read_lock(cache, partition);
+
+ Pvoid_t *metrics_judy_pptr = JudyLGet(cache->index[partition].sections_judy, section, PJE0);
+ if(unlikely(metrics_judy_pptr == PJERR))
+ fatal("DBENGINE CACHE: corrupted sections judy array");
+
+ if(unlikely(!metrics_judy_pptr)) {
+ // section does not exist
+ goto cleanup;
+ }
+
+ Pvoid_t *pages_judy_pptr = JudyLGet(*metrics_judy_pptr, metric_id, PJE0);
+ if(unlikely(pages_judy_pptr == PJERR))
+ fatal("DBENGINE CACHE: corrupted pages judy array");
+
+ if(unlikely(!pages_judy_pptr)) {
+ // metric does not exist
+ goto cleanup;
+ }
+
+ switch(method) {
+ default:
+ case PGC_SEARCH_CLOSEST: {
+ Pvoid_t *page_ptr = JudyLGet(*pages_judy_pptr, start_time_s, PJE0);
+ if (unlikely(page_ptr == PJERR))
+ fatal("DBENGINE CACHE: corrupted page in pages judy array");
+
+ if (page_ptr)
+ page = *page_ptr;
+
+ else {
+ Word_t time = start_time_s;
+
+ // find the previous page
+ page_ptr = JudyLLast(*pages_judy_pptr, &time, PJE0);
+ if(unlikely(page_ptr == PJERR))
+ fatal("DBENGINE CACHE: corrupted page in pages judy array #2");
+
+ if(page_ptr) {
+ // found a page starting before our timestamp
+ // check if our timestamp is included
+ page = *page_ptr;
+ if(start_time_s > page->end_time_s)
+ // it is not good for us
+ page = NULL;
+ }
+
+ if(!page) {
+ // find the next page then...
+ time = start_time_s;
+ page_ptr = JudyLNext(*pages_judy_pptr, &time, PJE0);
+ if(page_ptr)
+ page = *page_ptr;
+ }
+ }
+ }
+ break;
+
+ case PGC_SEARCH_EXACT: {
+ Pvoid_t *page_ptr = JudyLGet(*pages_judy_pptr, start_time_s, PJE0);
+ if (unlikely(page_ptr == PJERR))
+ fatal("DBENGINE CACHE: corrupted page in pages judy array");
+
+ if (page_ptr)
+ page = *page_ptr;
+ }
+ break;
+
+ case PGC_SEARCH_FIRST: {
+ Word_t time = start_time_s;
+ Pvoid_t *page_ptr = JudyLFirst(*pages_judy_pptr, &time, PJE0);
+ if (unlikely(page_ptr == PJERR))
+ fatal("DBENGINE CACHE: corrupted page in pages judy array");
+
+ if (page_ptr)
+ page = *page_ptr;
+ }
+ break;
+
+ case PGC_SEARCH_NEXT: {
+ Word_t time = start_time_s;
+ Pvoid_t *page_ptr = JudyLNext(*pages_judy_pptr, &time, PJE0);
+ if (unlikely(page_ptr == PJERR))
+ fatal("DBENGINE CACHE: corrupted page in pages judy array");
+
+ if (page_ptr)
+ page = *page_ptr;
+ }
+ break;
+
+ case PGC_SEARCH_LAST: {
+ Word_t time = start_time_s;
+ Pvoid_t *page_ptr = JudyLLast(*pages_judy_pptr, &time, PJE0);
+ if (unlikely(page_ptr == PJERR))
+ fatal("DBENGINE CACHE: corrupted page in pages judy array");
+
+ if (page_ptr)
+ page = *page_ptr;
+ }
+ break;
+
+ case PGC_SEARCH_PREV: {
+ Word_t time = start_time_s;
+ Pvoid_t *page_ptr = JudyLPrev(*pages_judy_pptr, &time, PJE0);
+ if (unlikely(page_ptr == PJERR))
+ fatal("DBENGINE CACHE: corrupted page in pages judy array");
+
+ if (page_ptr)
+ page = *page_ptr;
+ }
+ break;
+ }
+
+ if(page) {
+ pointer_check(cache, page);
+
+ if(!page_acquire(cache, page)) {
+ // this page is not good to use
+ page = NULL;
+ }
+ }
+
+cleanup:
+ pgc_index_read_unlock(cache, partition);
+
+ if(page) {
+ __atomic_add_fetch(stats_hit_ptr, 1, __ATOMIC_RELAXED);
+ page_has_been_accessed(cache, page);
+ }
+ else
+ __atomic_add_fetch(stats_miss_ptr, 1, __ATOMIC_RELAXED);
+
+ __atomic_sub_fetch(&cache->stats.workers_search, 1, __ATOMIC_RELAXED);
+
+ return page;
+}
+
+static void all_hot_pages_to_dirty(PGC *cache, Word_t section) {
+ pgc_ll_lock(cache, &cache->hot);
+
+ bool first = true;
+ Word_t last_section = (section == PGC_SECTION_ALL) ? 0 : section;
+ Pvoid_t *section_pages_pptr;
+ while ((section_pages_pptr = JudyLFirstThenNext(cache->hot.sections_judy, &last_section, &first))) {
+ if(section != PGC_SECTION_ALL && last_section != section)
+ break;
+
+ struct section_pages *sp = *section_pages_pptr;
+
+ PGC_PAGE *page = sp->base;
+ while(page) {
+ PGC_PAGE *next = page->link.next;
+
+ if(page_acquire(cache, page)) {
+ page_set_dirty(cache, page, true);
+ page_release(cache, page, false);
+ // page ptr may be invalid now
+ }
+
+ page = next;
+ }
+ }
+ pgc_ll_unlock(cache, &cache->hot);
+}
+
+// returns true when there is more work to do
+static bool flush_pages(PGC *cache, size_t max_flushes, Word_t section, bool wait, bool all_of_them) {
+ internal_fatal(!cache->dirty.linked_list_in_sections_judy,
+ "wrong dirty pages configuration - dirty pages need to have a judy array, not a linked list");
+
+ if(!all_of_them && !wait) {
+ // we have been called from a data collection thread
+ // let's not waste its time...
+
+ if(!pgc_ll_trylock(cache, &cache->dirty)) {
+ // we would block, so give up...
+ return true;
+ }
+
+ // we got the lock at this point
+ }
+ else
+ pgc_ll_lock(cache, &cache->dirty);
+
+ size_t optimal_flush_size = cache->config.max_dirty_pages_per_call;
+ size_t dirty_version_at_entry = cache->dirty.version;
+ if(!all_of_them && (cache->dirty.stats->entries < optimal_flush_size || cache->dirty.last_version_checked == dirty_version_at_entry)) {
+ pgc_ll_unlock(cache, &cache->dirty);
+ return false;
+ }
+
+ __atomic_add_fetch(&cache->stats.workers_flush, 1, __ATOMIC_RELAXED);
+
+ bool have_dirty_lock = true;
+
+ if(all_of_them || !max_flushes)
+ max_flushes = SIZE_MAX;
+
+ Word_t last_section = (section == PGC_SECTION_ALL) ? 0 : section;
+ size_t flushes_so_far = 0;
+ Pvoid_t *section_pages_pptr;
+ bool stopped_before_finishing = false;
+ size_t spins = 0;
+ bool first = true;
+
+ while (have_dirty_lock && (section_pages_pptr = JudyLFirstThenNext(cache->dirty.sections_judy, &last_section, &first))) {
+ if(section != PGC_SECTION_ALL && last_section != section)
+ break;
+
+ struct section_pages *sp = *section_pages_pptr;
+ if(!all_of_them && sp->entries < optimal_flush_size)
+ continue;
+
+ if(!all_of_them && flushes_so_far > max_flushes) {
+ stopped_before_finishing = true;
+ break;
+ }
+
+ if(++spins > 1)
+ __atomic_add_fetch(&cache->stats.flush_spins, 1, __ATOMIC_RELAXED);
+
+ PGC_ENTRY array[optimal_flush_size];
+ PGC_PAGE *pages[optimal_flush_size];
+ size_t pages_added = 0, pages_added_size = 0;
+ size_t pages_removed_dirty = 0, pages_removed_dirty_size = 0;
+ size_t pages_cancelled = 0, pages_cancelled_size = 0;
+ size_t pages_made_clean = 0, pages_made_clean_size = 0;
+
+ PGC_PAGE *page = sp->base;
+ while (page && pages_added < optimal_flush_size) {
+ PGC_PAGE *next = page->link.next;
+
+ internal_fatal(page_get_status_flags(page) != PGC_PAGE_DIRTY,
+ "DBENGINE CACHE: page should be in the dirty list before saved");
+
+ if (page_acquire(cache, page)) {
+ internal_fatal(page_get_status_flags(page) != PGC_PAGE_DIRTY,
+ "DBENGINE CACHE: page should be in the dirty list before saved");
+
+ internal_fatal(page->section != last_section,
+ "DBENGINE CACHE: dirty page is not in the right section (tier)");
+
+ if(!page_transition_trylock(cache, page)) {
+ page_release(cache, page, false);
+ // page ptr may be invalid now
+ }
+ else {
+ pages[pages_added] = page;
+ array[pages_added] = (PGC_ENTRY) {
+ .section = page->section,
+ .metric_id = page->metric_id,
+ .start_time_s = page->start_time_s,
+ .end_time_s = __atomic_load_n(&page->end_time_s, __ATOMIC_RELAXED),
+ .update_every_s = page->update_every_s,
+ .size = page_size_from_assumed_size(cache, page->assumed_size),
+ .data = page->data,
+ .custom_data = (cache->config.additional_bytes_per_page) ? page->custom_data : NULL,
+ .hot = false,
+ };
+
+ pages_added_size += page->assumed_size;
+ pages_added++;
+ }
+ }
+
+ page = next;
+ }
+
+ // do we have enough to save?
+ if(all_of_them || pages_added == optimal_flush_size) {
+ // we should do it
+
+ for (size_t i = 0; i < pages_added; i++) {
+ PGC_PAGE *tpg = pages[i];
+
+ internal_fatal(page_get_status_flags(tpg) != PGC_PAGE_DIRTY,
+ "DBENGINE CACHE: page should be in the dirty list before saved");
+
+ __atomic_add_fetch(&cache->stats.flushing_entries, 1, __ATOMIC_RELAXED);
+ __atomic_add_fetch(&cache->stats.flushing_size, tpg->assumed_size, __ATOMIC_RELAXED);
+
+ // remove it from the dirty list
+ pgc_ll_del(cache, &cache->dirty, tpg, true);
+
+ pages_removed_dirty_size += tpg->assumed_size;
+ pages_removed_dirty++;
+ }
+
+ // next time, repeat the same section (tier)
+ first = true;
+ }
+ else {
+ // we can't do it
+
+ for (size_t i = 0; i < pages_added; i++) {
+ PGC_PAGE *tpg = pages[i];
+
+ internal_fatal(page_get_status_flags(tpg) != PGC_PAGE_DIRTY,
+ "DBENGINE CACHE: page should be in the dirty list before saved");
+
+ pages_cancelled_size += tpg->assumed_size;
+ pages_cancelled++;
+
+ page_transition_unlock(cache, tpg);
+ page_release(cache, tpg, false);
+ // page ptr may be invalid now
+ }
+
+ __atomic_add_fetch(&cache->stats.flushes_cancelled, pages_cancelled, __ATOMIC_RELAXED);
+ __atomic_add_fetch(&cache->stats.flushes_cancelled_size, pages_cancelled_size, __ATOMIC_RELAXED);
+
+ internal_fatal(pages_added != pages_cancelled || pages_added_size != pages_cancelled_size,
+ "DBENGINE CACHE: flushing cancel pages mismatch");
+
+ // next time, continue to the next section (tier)
+ first = false;
+ continue;
+ }
+
+ if(cache->config.pgc_save_init_cb)
+ cache->config.pgc_save_init_cb(cache, last_section);
+
+ pgc_ll_unlock(cache, &cache->dirty);
+ have_dirty_lock = false;
+
+ // call the callback to save them
+ // it may take some time, so let's release the lock
+ cache->config.pgc_save_dirty_cb(cache, array, pages, pages_added);
+ flushes_so_far++;
+
+ __atomic_add_fetch(&cache->stats.flushes_completed, pages_added, __ATOMIC_RELAXED);
+ __atomic_add_fetch(&cache->stats.flushes_completed_size, pages_added_size, __ATOMIC_RELAXED);
+
+ size_t pages_to_evict = 0; (void)pages_to_evict;
+ for (size_t i = 0; i < pages_added; i++) {
+ PGC_PAGE *tpg = pages[i];
+
+ internal_fatal(page_get_status_flags(tpg) != 0,
+ "DBENGINE CACHE: page should not be in any list while it is being saved");
+
+ __atomic_sub_fetch(&cache->stats.flushing_entries, 1, __ATOMIC_RELAXED);
+ __atomic_sub_fetch(&cache->stats.flushing_size, tpg->assumed_size, __ATOMIC_RELAXED);
+
+ pages_made_clean_size += tpg->assumed_size;
+ pages_made_clean++;
+
+ if(!tpg->accesses)
+ pages_to_evict++;
+
+ page_set_clean(cache, tpg, true, false);
+ page_transition_unlock(cache, tpg);
+ page_release(cache, tpg, false);
+ // tpg ptr may be invalid now
+ }
+
+ internal_fatal(pages_added != pages_made_clean || pages_added != pages_removed_dirty ||
+ pages_added_size != pages_made_clean_size || pages_added_size != pages_removed_dirty_size
+ , "DBENGINE CACHE: flushing pages mismatch");
+
+ if(!all_of_them && !wait) {
+ if(pgc_ll_trylock(cache, &cache->dirty))
+ have_dirty_lock = true;
+
+ else {
+ stopped_before_finishing = true;
+ have_dirty_lock = false;
+ }
+ }
+ else {
+ pgc_ll_lock(cache, &cache->dirty);
+ have_dirty_lock = true;
+ }
+ }
+
+ if(have_dirty_lock) {
+ if(!stopped_before_finishing && dirty_version_at_entry > cache->dirty.last_version_checked)
+ cache->dirty.last_version_checked = dirty_version_at_entry;
+
+ pgc_ll_unlock(cache, &cache->dirty);
+ }
+
+ __atomic_sub_fetch(&cache->stats.workers_flush, 1, __ATOMIC_RELAXED);
+
+ return stopped_before_finishing;
+}
+
+void free_all_unreferenced_clean_pages(PGC *cache) {
+ evict_pages(cache, 0, 0, true, true);
+}
+
+// ----------------------------------------------------------------------------
+// public API
+
+PGC *pgc_create(const char *name,
+ size_t clean_size_bytes, free_clean_page_callback pgc_free_cb,
+ size_t max_dirty_pages_per_flush,
+ save_dirty_init_callback pgc_save_init_cb,
+ save_dirty_page_callback pgc_save_dirty_cb,
+ size_t max_pages_per_inline_eviction, size_t max_inline_evictors,
+ size_t max_skip_pages_per_inline_eviction,
+ size_t max_flushes_inline,
+ PGC_OPTIONS options, size_t partitions, size_t additional_bytes_per_page) {
+
+ if(max_pages_per_inline_eviction < 2)
+ max_pages_per_inline_eviction = 2;
+
+ if(max_dirty_pages_per_flush < 1)
+ max_dirty_pages_per_flush = 1;
+
+ if(max_flushes_inline * max_dirty_pages_per_flush < 2)
+ max_flushes_inline = 2;
+
+ PGC *cache = callocz(1, sizeof(PGC));
+ strncpyz(cache->config.name, name, PGC_NAME_MAX);
+ cache->config.options = options;
+ cache->config.clean_size = (clean_size_bytes < 1 * 1024 * 1024) ? 1 * 1024 * 1024 : clean_size_bytes;
+ cache->config.pgc_free_clean_cb = pgc_free_cb;
+ cache->config.max_dirty_pages_per_call = max_dirty_pages_per_flush;
+ cache->config.pgc_save_init_cb = pgc_save_init_cb;
+ cache->config.pgc_save_dirty_cb = pgc_save_dirty_cb;
+ cache->config.max_pages_per_inline_eviction = (max_pages_per_inline_eviction < 2) ? 2 : max_pages_per_inline_eviction;
+ cache->config.max_skip_pages_per_inline_eviction = (max_skip_pages_per_inline_eviction < 2) ? 2 : max_skip_pages_per_inline_eviction;
+ cache->config.max_flushes_inline = (max_flushes_inline < 1) ? 1 : max_flushes_inline;
+ cache->config.partitions = partitions < 1 ? (size_t)get_netdata_cpus() : partitions;
+ cache->config.additional_bytes_per_page = additional_bytes_per_page;
+
+ cache->config.max_workers_evict_inline = max_inline_evictors;
+ cache->config.severe_pressure_per1000 = 1010;
+ cache->config.aggressive_evict_per1000 = 990;
+ cache->config.healthy_size_per1000 = 980;
+ cache->config.evict_low_threshold_per1000 = 970;
+
+ cache->index = callocz(cache->config.partitions, sizeof(struct pgc_index));
+
+ for(size_t part = 0; part < cache->config.partitions ; part++)
+ netdata_rwlock_init(&cache->index[part].rwlock);
+
+ netdata_spinlock_init(&cache->hot.spinlock);
+ netdata_spinlock_init(&cache->dirty.spinlock);
+ netdata_spinlock_init(&cache->clean.spinlock);
+
+ cache->hot.flags = PGC_PAGE_HOT;
+ cache->hot.linked_list_in_sections_judy = true;
+ cache->hot.stats = &cache->stats.queues.hot;
+
+ cache->dirty.flags = PGC_PAGE_DIRTY;
+ cache->dirty.linked_list_in_sections_judy = true;
+ cache->dirty.stats = &cache->stats.queues.dirty;
+
+ cache->clean.flags = PGC_PAGE_CLEAN;
+ cache->clean.linked_list_in_sections_judy = false;
+ cache->clean.stats = &cache->stats.queues.clean;
+
+ pgc_section_pages_static_aral_init();
+
+#ifdef PGC_WITH_ARAL
+ cache->aral = callocz(cache->config.partitions, sizeof(ARAL *));
+ for(size_t part = 0; part < cache->config.partitions ; part++) {
+ char buf[100 +1];
+ snprintfz(buf, 100, "%s[%zu]", name, part);
+ cache->aral[part] = aral_create(
+ buf,
+ sizeof(PGC_PAGE) + cache->config.additional_bytes_per_page,
+ 0,
+ 16384,
+ aral_statistics(pgc_section_pages_aral),
+ NULL, NULL, false, false);
+ }
+#endif
+
+ pointer_index_init(cache);
+
+ return cache;
+}
+
+struct aral_statistics *pgc_aral_statistics(void) {
+ return aral_statistics(pgc_section_pages_aral);
+}
+
+size_t pgc_aral_structures(void) {
+ return aral_structures(pgc_section_pages_aral);
+}
+
+size_t pgc_aral_overhead(void) {
+ return aral_overhead(pgc_section_pages_aral);
+}
+
+void pgc_flush_all_hot_and_dirty_pages(PGC *cache, Word_t section) {
+ all_hot_pages_to_dirty(cache, section);
+
+ // save all dirty pages to make them clean
+ flush_pages(cache, 0, section, true, true);
+}
+
+void pgc_destroy(PGC *cache) {
+ // convert all hot pages to dirty
+ all_hot_pages_to_dirty(cache, PGC_SECTION_ALL);
+
+ // save all dirty pages to make them clean
+ flush_pages(cache, 0, PGC_SECTION_ALL, true, true);
+
+ // free all unreferenced clean pages
+ free_all_unreferenced_clean_pages(cache);
+
+ if(PGC_REFERENCED_PAGES(cache))
+ error("DBENGINE CACHE: there are %zu referenced cache pages - leaving the cache allocated", PGC_REFERENCED_PAGES(cache));
+ else {
+ pointer_destroy_index(cache);
+
+ for(size_t part = 0; part < cache->config.partitions ; part++)
+ netdata_rwlock_destroy(&cache->index[part].rwlock);
+
+#ifdef PGC_WITH_ARAL
+ for(size_t part = 0; part < cache->config.partitions ; part++)
+ aral_destroy(cache->aral[part]);
+
+ freez(cache->aral);
+#endif
+
+ freez(cache);
+ }
+}
+
+PGC_PAGE *pgc_page_add_and_acquire(PGC *cache, PGC_ENTRY entry, bool *added) {
+ return page_add(cache, &entry, added);
+}
+
+PGC_PAGE *pgc_page_dup(PGC *cache, PGC_PAGE *page) {
+ if(!page_acquire(cache, page))
+ fatal("DBENGINE CACHE: tried to dup a page that is not acquired!");
+
+ return page;
+}
+
+void pgc_page_release(PGC *cache, PGC_PAGE *page) {
+ page_release(cache, page, is_page_clean(page));
+}
+
+void pgc_page_hot_to_dirty_and_release(PGC *cache, PGC_PAGE *page) {
+ __atomic_add_fetch(&cache->stats.workers_hot2dirty, 1, __ATOMIC_RELAXED);
+
+//#ifdef NETDATA_INTERNAL_CHECKS
+// page_transition_lock(cache, page);
+// internal_fatal(!is_page_hot(page), "DBENGINE CACHE: called %s() but page is not hot", __FUNCTION__ );
+// page_transition_unlock(cache, page);
+//#endif
+
+ // make page dirty
+ page_set_dirty(cache, page, false);
+
+ // release the page
+ page_release(cache, page, true);
+ // page ptr may be invalid now
+
+ __atomic_sub_fetch(&cache->stats.workers_hot2dirty, 1, __ATOMIC_RELAXED);
+
+ // flush, if we have to
+ if((cache->config.options & PGC_OPTIONS_FLUSH_PAGES_INLINE) || flushing_critical(cache)) {
+ flush_pages(cache, cache->config.max_flushes_inline, PGC_SECTION_ALL,
+ false, false);
+ }
+}
+
+bool pgc_page_to_clean_evict_or_release(PGC *cache, PGC_PAGE *page) {
+ bool ret;
+
+ __atomic_add_fetch(&cache->stats.workers_hot2dirty, 1, __ATOMIC_RELAXED);
+
+ // prevent accesses from increasing the accesses counter
+ page_flag_set(page, PGC_PAGE_HAS_NO_DATA_IGNORE_ACCESSES);
+
+ // zero the accesses counter
+ __atomic_store_n(&page->accesses, 0, __ATOMIC_RELEASE);
+
+ // if there are no other references to it, evict it immediately
+ if(make_acquired_page_clean_and_evict_or_page_release(cache, page)) {
+ __atomic_add_fetch(&cache->stats.hot_empty_pages_evicted_immediately, 1, __ATOMIC_RELAXED);
+ ret = true;
+ }
+ else {
+ __atomic_add_fetch(&cache->stats.hot_empty_pages_evicted_later, 1, __ATOMIC_RELAXED);
+ ret = false;
+ }
+
+ __atomic_sub_fetch(&cache->stats.workers_hot2dirty, 1, __ATOMIC_RELAXED);
+
+ return ret;
+}
+
+Word_t pgc_page_section(PGC_PAGE *page) {
+ return page->section;
+}
+
+Word_t pgc_page_metric(PGC_PAGE *page) {
+ return page->metric_id;
+}
+
+time_t pgc_page_start_time_s(PGC_PAGE *page) {
+ return page->start_time_s;
+}
+
+time_t pgc_page_end_time_s(PGC_PAGE *page) {
+ return page->end_time_s;
+}
+
+time_t pgc_page_update_every_s(PGC_PAGE *page) {
+ return page->update_every_s;
+}
+
+time_t pgc_page_fix_update_every(PGC_PAGE *page, time_t update_every_s) {
+ if(page->update_every_s == 0)
+ page->update_every_s = update_every_s;
+
+ return page->update_every_s;
+}
+
+time_t pgc_page_fix_end_time_s(PGC_PAGE *page, time_t end_time_s) {
+ page->end_time_s = end_time_s;
+ return page->end_time_s;
+}
+
+void *pgc_page_data(PGC_PAGE *page) {
+ return page->data;
+}
+
+void *pgc_page_custom_data(PGC *cache, PGC_PAGE *page) {
+ if(cache->config.additional_bytes_per_page)
+ return page->custom_data;
+
+ return NULL;
+}
+
+size_t pgc_page_data_size(PGC *cache, PGC_PAGE *page) {
+ return page_size_from_assumed_size(cache, page->assumed_size);
+}
+
+bool pgc_is_page_hot(PGC_PAGE *page) {
+ return is_page_hot(page);
+}
+
+bool pgc_is_page_dirty(PGC_PAGE *page) {
+ return is_page_dirty(page);
+}
+
+bool pgc_is_page_clean(PGC_PAGE *page) {
+ return is_page_clean(page);
+}
+
+void pgc_reset_hot_max(PGC *cache) {
+ size_t entries = __atomic_load_n(&cache->hot.stats->entries, __ATOMIC_RELAXED);
+ size_t size = __atomic_load_n(&cache->hot.stats->size, __ATOMIC_RELAXED);
+
+ __atomic_store_n(&cache->hot.stats->max_entries, entries, __ATOMIC_RELAXED);
+ __atomic_store_n(&cache->hot.stats->max_size, size, __ATOMIC_RELAXED);
+
+ size_t size_to_evict = 0;
+ cache_usage_per1000(cache, &size_to_evict);
+ evict_pages(cache, 0, 0, true, false);
+}
+
+void pgc_set_dynamic_target_cache_size_callback(PGC *cache, dynamic_target_cache_size_callback callback) {
+ cache->config.dynamic_target_size_cb = callback;
+
+ size_t size_to_evict = 0;
+ cache_usage_per1000(cache, &size_to_evict);
+ evict_pages(cache, 0, 0, true, false);
+}
+
+size_t pgc_get_current_cache_size(PGC *cache) {
+ cache_usage_per1000(cache, NULL);
+ return __atomic_load_n(&cache->stats.current_cache_size, __ATOMIC_RELAXED);
+}
+
+size_t pgc_get_wanted_cache_size(PGC *cache) {
+ cache_usage_per1000(cache, NULL);
+ return __atomic_load_n(&cache->stats.wanted_cache_size, __ATOMIC_RELAXED);
+}
+
+bool pgc_evict_pages(PGC *cache, size_t max_skip, size_t max_evict) {
+ bool under_pressure = cache_needs_space_aggressively(cache);
+ return evict_pages(cache,
+ under_pressure ? 0 : max_skip,
+ under_pressure ? 0 : max_evict,
+ true, false);
+}
+
+bool pgc_flush_pages(PGC *cache, size_t max_flushes) {
+ bool under_pressure = flushing_critical(cache);
+ return flush_pages(cache, under_pressure ? 0 : max_flushes, PGC_SECTION_ALL, true, false);
+}
+
+void pgc_page_hot_set_end_time_s(PGC *cache __maybe_unused, PGC_PAGE *page, time_t end_time_s) {
+ internal_fatal(!is_page_hot(page),
+ "DBENGINE CACHE: end_time_s update on non-hot page");
+
+ internal_fatal(end_time_s < __atomic_load_n(&page->end_time_s, __ATOMIC_RELAXED),
+ "DBENGINE CACHE: end_time_s is not bigger than existing");
+
+ __atomic_store_n(&page->end_time_s, end_time_s, __ATOMIC_RELAXED);
+
+#ifdef PGC_COUNT_POINTS_COLLECTED
+ __atomic_add_fetch(&cache->stats.points_collected, 1, __ATOMIC_RELAXED);
+#endif
+}
+
+PGC_PAGE *pgc_page_get_and_acquire(PGC *cache, Word_t section, Word_t metric_id, time_t start_time_s, PGC_SEARCH method) {
+ return page_find_and_acquire(cache, section, metric_id, start_time_s, method);
+}
+
+struct pgc_statistics pgc_get_statistics(PGC *cache) {
+ // FIXME - get the statistics atomically
+ return cache->stats;
+}
+
+size_t pgc_hot_and_dirty_entries(PGC *cache) {
+ size_t entries = 0;
+
+ entries += __atomic_load_n(&cache->hot.stats->entries, __ATOMIC_RELAXED);
+ entries += __atomic_load_n(&cache->dirty.stats->entries, __ATOMIC_RELAXED);
+ entries += __atomic_load_n(&cache->stats.flushing_entries, __ATOMIC_RELAXED);
+ entries += __atomic_load_n(&cache->stats.hot2dirty_entries, __ATOMIC_RELAXED);
+
+ return entries;
+}
+
+void pgc_open_cache_to_journal_v2(PGC *cache, Word_t section, unsigned datafile_fileno, uint8_t type, migrate_to_v2_callback cb, void *data) {
+ __atomic_add_fetch(&rrdeng_cache_efficiency_stats.journal_v2_indexing_started, 1, __ATOMIC_RELAXED);
+ __atomic_add_fetch(&cache->stats.workers_jv2_flush, 1, __ATOMIC_RELAXED);
+
+ pgc_ll_lock(cache, &cache->hot);
+
+ Pvoid_t JudyL_metrics = NULL;
+ Pvoid_t JudyL_extents_pos = NULL;
+
+ size_t count_of_unique_extents = 0;
+ size_t count_of_unique_metrics = 0;
+ size_t count_of_unique_pages = 0;
+
+ size_t master_extent_index_id = 0;
+
+ Pvoid_t *section_pages_pptr = JudyLGet(cache->hot.sections_judy, section, PJE0);
+ if(!section_pages_pptr) {
+ pgc_ll_unlock(cache, &cache->hot);
+ return;
+ }
+
+ struct section_pages *sp = *section_pages_pptr;
+ if(!netdata_spinlock_trylock(&sp->migration_to_v2_spinlock)) {
+ internal_fatal(true, "DBENGINE: migration to journal v2 is already running for this section");
+ pgc_ll_unlock(cache, &cache->hot);
+ return;
+ }
+
+ ARAL *ar_mi = aral_by_size_acquire(sizeof(struct jv2_metrics_info));
+ ARAL *ar_pi = aral_by_size_acquire(sizeof(struct jv2_page_info));
+ ARAL *ar_ei = aral_by_size_acquire(sizeof(struct jv2_extents_info));
+
+ for(PGC_PAGE *page = sp->base; page ; page = page->link.next) {
+ struct extent_io_data *xio = (struct extent_io_data *)page->custom_data;
+ if(xio->fileno != datafile_fileno) continue;
+
+ if(page_flag_check(page, PGC_PAGE_IS_BEING_MIGRATED_TO_V2)) {
+ internal_fatal(true, "Migration to journal v2: page has already been migrated to v2");
+ continue;
+ }
+
+ if(!page_transition_trylock(cache, page)) {
+ internal_fatal(true, "Migration to journal v2: cannot get page transition lock");
+ continue;
+ }
+
+ if(!page_acquire(cache, page)) {
+ internal_fatal(true, "Migration to journal v2: cannot acquire page for migration to v2");
+ continue;
+ }
+
+ page_flag_set(page, PGC_PAGE_IS_BEING_MIGRATED_TO_V2);
+
+ pgc_ll_unlock(cache, &cache->hot);
+
+ // update the extents JudyL
+
+ size_t current_extent_index_id;
+ Pvoid_t *PValue = JudyLIns(&JudyL_extents_pos, xio->pos, PJE0);
+ if(!PValue || *PValue == PJERR)
+ fatal("Corrupted JudyL extents pos");
+
+ struct jv2_extents_info *ei;
+ if(!*PValue) {
+ ei = aral_mallocz(ar_ei); // callocz(1, sizeof(struct jv2_extents_info));
+ ei->pos = xio->pos;
+ ei->bytes = xio->bytes;
+ ei->number_of_pages = 1;
+ ei->index = master_extent_index_id++;
+ *PValue = ei;
+
+ count_of_unique_extents++;
+ }
+ else {
+ ei = *PValue;
+ ei->number_of_pages++;
+ }
+
+ current_extent_index_id = ei->index;
+
+ // update the metrics JudyL
+
+ PValue = JudyLIns(&JudyL_metrics, page->metric_id, PJE0);
+ if(!PValue || *PValue == PJERR)
+ fatal("Corrupted JudyL metrics");
+
+ struct jv2_metrics_info *mi;
+ if(!*PValue) {
+ mi = aral_mallocz(ar_mi); // callocz(1, sizeof(struct jv2_metrics_info));
+ mi->uuid = mrg_metric_uuid(main_mrg, (METRIC *)page->metric_id);
+ mi->first_time_s = page->start_time_s;
+ mi->last_time_s = page->end_time_s;
+ mi->number_of_pages = 1;
+ mi->page_list_header = 0;
+ mi->JudyL_pages_by_start_time = NULL;
+ *PValue = mi;
+
+ count_of_unique_metrics++;
+ }
+ else {
+ mi = *PValue;
+ mi->number_of_pages++;
+ if(page->start_time_s < mi->first_time_s)
+ mi->first_time_s = page->start_time_s;
+ if(page->end_time_s > mi->last_time_s)
+ mi->last_time_s = page->end_time_s;
+ }
+
+ PValue = JudyLIns(&mi->JudyL_pages_by_start_time, page->start_time_s, PJE0);
+ if(!PValue || *PValue == PJERR)
+ fatal("Corrupted JudyL metric pages");
+
+ if(!*PValue) {
+ struct jv2_page_info *pi = aral_mallocz(ar_pi); // callocz(1, (sizeof(struct jv2_page_info)));
+ pi->start_time_s = page->start_time_s;
+ pi->end_time_s = page->end_time_s;
+ pi->update_every_s = page->update_every_s;
+ pi->page_length = page_size_from_assumed_size(cache, page->assumed_size);
+ pi->page = page;
+ pi->extent_index = current_extent_index_id;
+ pi->custom_data = (cache->config.additional_bytes_per_page) ? page->custom_data : NULL;
+ *PValue = pi;
+
+ count_of_unique_pages++;
+ }
+ else {
+ // impossible situation
+ internal_fatal(true, "Page is already in JudyL metric pages");
+ page_flag_clear(page, PGC_PAGE_IS_BEING_MIGRATED_TO_V2);
+ page_transition_unlock(cache, page);
+ page_release(cache, page, false);
+ }
+
+ pgc_ll_lock(cache, &cache->hot);
+ }
+
+ netdata_spinlock_unlock(&sp->migration_to_v2_spinlock);
+ pgc_ll_unlock(cache, &cache->hot);
+
+ // callback
+ cb(section, datafile_fileno, type, JudyL_metrics, JudyL_extents_pos, count_of_unique_extents, count_of_unique_metrics, count_of_unique_pages, data);
+
+ {
+ Pvoid_t *PValue1;
+ bool metric_id_first = true;
+ Word_t metric_id = 0;
+ while ((PValue1 = JudyLFirstThenNext(JudyL_metrics, &metric_id, &metric_id_first))) {
+ struct jv2_metrics_info *mi = *PValue1;
+
+ Pvoid_t *PValue2;
+ bool start_time_first = true;
+ Word_t start_time = 0;
+ while ((PValue2 = JudyLFirstThenNext(mi->JudyL_pages_by_start_time, &start_time, &start_time_first))) {
+ struct jv2_page_info *pi = *PValue2;
+ page_transition_unlock(cache, pi->page);
+ pgc_page_hot_to_dirty_and_release(cache, pi->page);
+ // make_acquired_page_clean_and_evict_or_page_release(cache, pi->page);
+ aral_freez(ar_pi, pi);
+ }
+
+ JudyLFreeArray(&mi->JudyL_pages_by_start_time, PJE0);
+ aral_freez(ar_mi, mi);
+ }
+ JudyLFreeArray(&JudyL_metrics, PJE0);
+ }
+
+ {
+ Pvoid_t *PValue;
+ bool extent_pos_first = true;
+ Word_t extent_pos = 0;
+ while ((PValue = JudyLFirstThenNext(JudyL_extents_pos, &extent_pos, &extent_pos_first))) {
+ struct jv2_extents_info *ei = *PValue;
+ aral_freez(ar_ei, ei);
+ }
+ JudyLFreeArray(&JudyL_extents_pos, PJE0);
+ }
+
+ aral_by_size_release(ar_ei);
+ aral_by_size_release(ar_pi);
+ aral_by_size_release(ar_mi);
+
+ __atomic_sub_fetch(&cache->stats.workers_jv2_flush, 1, __ATOMIC_RELAXED);
+}
+
+static bool match_page_data(PGC_PAGE *page, void *data) {
+ return (page->data == data);
+}
+
+void pgc_open_evict_clean_pages_of_datafile(PGC *cache, struct rrdengine_datafile *datafile) {
+ evict_pages_with_filter(cache, 0, 0, true, true, match_page_data, datafile);
+}
+
+size_t pgc_count_clean_pages_having_data_ptr(PGC *cache, Word_t section, void *ptr) {
+ size_t found = 0;
+
+ pgc_ll_lock(cache, &cache->clean);
+ for(PGC_PAGE *page = cache->clean.base; page ;page = page->link.next)
+ found += (page->data == ptr && page->section == section) ? 1 : 0;
+ pgc_ll_unlock(cache, &cache->clean);
+
+ return found;
+}
+
+size_t pgc_count_hot_pages_having_data_ptr(PGC *cache, Word_t section, void *ptr) {
+ size_t found = 0;
+
+ pgc_ll_lock(cache, &cache->hot);
+ Pvoid_t *section_pages_pptr = JudyLGet(cache->hot.sections_judy, section, PJE0);
+ if(section_pages_pptr) {
+ struct section_pages *sp = *section_pages_pptr;
+ for(PGC_PAGE *page = sp->base; page ;page = page->link.next)
+ found += (page->data == ptr) ? 1 : 0;
+ }
+ pgc_ll_unlock(cache, &cache->hot);
+
+ return found;
+}
+
+// ----------------------------------------------------------------------------
+// unittest
+
+static void unittest_free_clean_page_callback(PGC *cache __maybe_unused, PGC_ENTRY entry __maybe_unused) {
+ ;
+}
+
+static void unittest_save_dirty_page_callback(PGC *cache __maybe_unused, PGC_ENTRY *entries_array __maybe_unused, PGC_PAGE **pages_array __maybe_unused, size_t entries __maybe_unused) {
+ ;
+}
+
+#ifdef PGC_STRESS_TEST
+
+struct {
+ bool stop;
+ PGC *cache;
+ PGC_PAGE **metrics;
+ size_t clean_metrics;
+ size_t hot_metrics;
+ time_t first_time_t;
+ time_t last_time_t;
+ size_t cache_size;
+ size_t query_threads;
+ size_t collect_threads;
+ size_t partitions;
+ size_t points_per_page;
+ time_t time_per_collection_ut;
+ time_t time_per_query_ut;
+ time_t time_per_flush_ut;
+ PGC_OPTIONS options;
+ char rand_statebufs[1024];
+ struct random_data *random_data;
+} pgc_uts = {
+ .stop = false,
+ .metrics = NULL,
+ .clean_metrics = 100000,
+ .hot_metrics = 1000000,
+ .first_time_t = 100000000,
+ .last_time_t = 0,
+ .cache_size = 0, // get the default (8MB)
+ .collect_threads = 16,
+ .query_threads = 16,
+ .partitions = 0, // get the default (system cpus)
+ .options = PGC_OPTIONS_AUTOSCALE,/* PGC_OPTIONS_FLUSH_PAGES_INLINE | PGC_OPTIONS_EVICT_PAGES_INLINE,*/
+ .points_per_page = 10,
+ .time_per_collection_ut = 1000000,
+ .time_per_query_ut = 250,
+ .time_per_flush_ut = 100,
+ .rand_statebufs = {},
+ .random_data = NULL,
+};
+
+void *unittest_stress_test_collector(void *ptr) {
+ size_t id = *((size_t *)ptr);
+
+ size_t metric_start = pgc_uts.clean_metrics;
+ size_t metric_end = pgc_uts.clean_metrics + pgc_uts.hot_metrics;
+ size_t number_of_metrics = metric_end - metric_start;
+ size_t per_collector_metrics = number_of_metrics / pgc_uts.collect_threads;
+ metric_start = metric_start + per_collector_metrics * id + 1;
+ metric_end = metric_start + per_collector_metrics - 1;
+
+ time_t start_time_t = pgc_uts.first_time_t + 1;
+
+ heartbeat_t hb;
+ heartbeat_init(&hb);
+
+ while(!__atomic_load_n(&pgc_uts.stop, __ATOMIC_RELAXED)) {
+ // info("COLLECTOR %zu: collecting metrics %zu to %zu, from %ld to %lu", id, metric_start, metric_end, start_time_t, start_time_t + pgc_uts.points_per_page);
+
+ netdata_thread_disable_cancelability();
+
+ for (size_t i = metric_start; i < metric_end; i++) {
+ bool added;
+
+ pgc_uts.metrics[i] = pgc_page_add_and_acquire(pgc_uts.cache, (PGC_ENTRY) {
+ .section = 1,
+ .metric_id = i,
+ .start_time_t = start_time_t,
+ .end_time_t = start_time_t,
+ .update_every = 1,
+ .size = 4096,
+ .data = NULL,
+ .hot = true,
+ }, &added);
+
+ if(!pgc_is_page_hot(pgc_uts.metrics[i]) || !added) {
+ pgc_page_release(pgc_uts.cache, pgc_uts.metrics[i]);
+ pgc_uts.metrics[i] = NULL;
+ }
+ }
+
+ time_t end_time_t = start_time_t + (time_t)pgc_uts.points_per_page;
+ while(++start_time_t <= end_time_t && !__atomic_load_n(&pgc_uts.stop, __ATOMIC_RELAXED)) {
+ heartbeat_next(&hb, pgc_uts.time_per_collection_ut);
+
+ for (size_t i = metric_start; i < metric_end; i++) {
+ if(pgc_uts.metrics[i])
+ pgc_page_hot_set_end_time_t(pgc_uts.cache, pgc_uts.metrics[i], start_time_t);
+ }
+
+ __atomic_store_n(&pgc_uts.last_time_t, start_time_t, __ATOMIC_RELAXED);
+ }
+
+ for (size_t i = metric_start; i < metric_end; i++) {
+ if (pgc_uts.metrics[i]) {
+ if(i % 10 == 0)
+ pgc_page_to_clean_evict_or_release(pgc_uts.cache, pgc_uts.metrics[i]);
+ else
+ pgc_page_hot_to_dirty_and_release(pgc_uts.cache, pgc_uts.metrics[i]);
+ }
+ }
+
+ netdata_thread_enable_cancelability();
+ }
+
+ return ptr;
+}
+
+void *unittest_stress_test_queries(void *ptr) {
+ size_t id = *((size_t *)ptr);
+ struct random_data *random_data = &pgc_uts.random_data[id];
+
+ size_t start = 0;
+ size_t end = pgc_uts.clean_metrics + pgc_uts.hot_metrics;
+
+ while(!__atomic_load_n(&pgc_uts.stop, __ATOMIC_RELAXED)) {
+ netdata_thread_disable_cancelability();
+
+ int32_t random_number;
+ random_r(random_data, &random_number);
+
+ size_t metric_id = random_number % (end - start);
+ time_t start_time_t = pgc_uts.first_time_t;
+ time_t end_time_t = __atomic_load_n(&pgc_uts.last_time_t, __ATOMIC_RELAXED);
+ if(end_time_t <= start_time_t)
+ end_time_t = start_time_t + 1;
+ size_t pages = (end_time_t - start_time_t) / pgc_uts.points_per_page + 1;
+
+ PGC_PAGE *array[pages];
+ for(size_t i = 0; i < pages ;i++)
+ array[i] = NULL;
+
+ // find the pages the cache has
+ for(size_t i = 0; i < pages ;i++) {
+ time_t page_start_time = start_time_t + (time_t)(i * pgc_uts.points_per_page);
+ array[i] = pgc_page_get_and_acquire(pgc_uts.cache, 1, metric_id,
+ page_start_time, (i < pages - 1)?PGC_SEARCH_EXACT:PGC_SEARCH_CLOSEST);
+ }
+
+ // load the rest of the pages
+ for(size_t i = 0; i < pages ;i++) {
+ if(array[i]) continue;
+
+ time_t page_start_time = start_time_t + (time_t)(i * pgc_uts.points_per_page);
+ array[i] = pgc_page_add_and_acquire(pgc_uts.cache, (PGC_ENTRY) {
+ .section = 1,
+ .metric_id = metric_id,
+ .start_time_t = page_start_time,
+ .end_time_t = page_start_time + (time_t)pgc_uts.points_per_page,
+ .update_every = 1,
+ .size = 4096,
+ .data = NULL,
+ .hot = false,
+ }, NULL);
+ }
+
+ // do the query
+ // ...
+ struct timespec work_duration = {.tv_sec = 0, .tv_nsec = pgc_uts.time_per_query_ut * NSEC_PER_USEC };
+ nanosleep(&work_duration, NULL);
+
+ // release the pages
+ for(size_t i = 0; i < pages ;i++) {
+ if(!array[i]) continue;
+ pgc_page_release(pgc_uts.cache, array[i]);
+ array[i] = NULL;
+ }
+
+ netdata_thread_enable_cancelability();
+ }
+
+ return ptr;
+}
+
+void *unittest_stress_test_service(void *ptr) {
+ heartbeat_t hb;
+ heartbeat_init(&hb);
+ while(!__atomic_load_n(&pgc_uts.stop, __ATOMIC_RELAXED)) {
+ heartbeat_next(&hb, 1 * USEC_PER_SEC);
+
+ pgc_flush_pages(pgc_uts.cache, 1000);
+ pgc_evict_pages(pgc_uts.cache, 0, 0);
+ }
+ return ptr;
+}
+
+static void unittest_stress_test_save_dirty_page_callback(PGC *cache __maybe_unused, PGC_ENTRY *entries_array __maybe_unused, PGC_PAGE **pages_array __maybe_unused, size_t entries __maybe_unused) {
+ // info("SAVE %zu pages", entries);
+ if(!pgc_uts.stop) {
+ usec_t t = pgc_uts.time_per_flush_ut;
+
+ if(t > 0) {
+ struct timespec work_duration = {
+ .tv_sec = t / USEC_PER_SEC,
+ .tv_nsec = (long) ((t % USEC_PER_SEC) * NSEC_PER_USEC)
+ };
+
+ nanosleep(&work_duration, NULL);
+ }
+ }
+}
+
+void unittest_stress_test(void) {
+ pgc_uts.cache = pgc_create(pgc_uts.cache_size * 1024 * 1024,
+ unittest_free_clean_page_callback,
+ 64, unittest_stress_test_save_dirty_page_callback,
+ 1000, 10000, 1,
+ pgc_uts.options, pgc_uts.partitions, 0);
+
+ pgc_uts.metrics = callocz(pgc_uts.clean_metrics + pgc_uts.hot_metrics, sizeof(PGC_PAGE *));
+
+ pthread_t service_thread;
+ netdata_thread_create(&service_thread, "SERVICE",
+ NETDATA_THREAD_OPTION_JOINABLE | NETDATA_THREAD_OPTION_DONT_LOG,
+ unittest_stress_test_service, NULL);
+
+ pthread_t collect_threads[pgc_uts.collect_threads];
+ size_t collect_thread_ids[pgc_uts.collect_threads];
+ for(size_t i = 0; i < pgc_uts.collect_threads ;i++) {
+ collect_thread_ids[i] = i;
+ char buffer[100 + 1];
+ snprintfz(buffer, 100, "COLLECT_%zu", i);
+ netdata_thread_create(&collect_threads[i], buffer,
+ NETDATA_THREAD_OPTION_JOINABLE | NETDATA_THREAD_OPTION_DONT_LOG,
+ unittest_stress_test_collector, &collect_thread_ids[i]);
+ }
+
+ pthread_t queries_threads[pgc_uts.query_threads];
+ size_t query_thread_ids[pgc_uts.query_threads];
+ pgc_uts.random_data = callocz(pgc_uts.query_threads, sizeof(struct random_data));
+ for(size_t i = 0; i < pgc_uts.query_threads ;i++) {
+ query_thread_ids[i] = i;
+ char buffer[100 + 1];
+ snprintfz(buffer, 100, "QUERY_%zu", i);
+ initstate_r(1, pgc_uts.rand_statebufs, 1024, &pgc_uts.random_data[i]);
+ netdata_thread_create(&queries_threads[i], buffer,
+ NETDATA_THREAD_OPTION_JOINABLE | NETDATA_THREAD_OPTION_DONT_LOG,
+ unittest_stress_test_queries, &query_thread_ids[i]);
+ }
+
+ heartbeat_t hb;
+ heartbeat_init(&hb);
+
+ struct {
+ size_t entries;
+ size_t added;
+ size_t deleted;
+ size_t referenced;
+
+ size_t hot_entries;
+ size_t hot_added;
+ size_t hot_deleted;
+
+ size_t dirty_entries;
+ size_t dirty_added;
+ size_t dirty_deleted;
+
+ size_t clean_entries;
+ size_t clean_added;
+ size_t clean_deleted;
+
+ size_t searches_exact;
+ size_t searches_exact_hits;
+ size_t searches_closest;
+ size_t searches_closest_hits;
+
+ size_t collections;
+
+ size_t events_cache_under_severe_pressure;
+ size_t events_cache_needs_space_90;
+ size_t events_flush_critical;
+ } stats = {}, old_stats = {};
+
+ for(int i = 0; i < 86400 ;i++) {
+ heartbeat_next(&hb, 1 * USEC_PER_SEC);
+
+ old_stats = stats;
+ stats.entries = __atomic_load_n(&pgc_uts.cache->stats.entries, __ATOMIC_RELAXED);
+ stats.added = __atomic_load_n(&pgc_uts.cache->stats.added_entries, __ATOMIC_RELAXED);
+ stats.deleted = __atomic_load_n(&pgc_uts.cache->stats.removed_entries, __ATOMIC_RELAXED);
+ stats.referenced = __atomic_load_n(&pgc_uts.cache->stats.referenced_entries, __ATOMIC_RELAXED);
+
+ stats.hot_entries = __atomic_load_n(&pgc_uts.cache->hot.stats->entries, __ATOMIC_RELAXED);
+ stats.hot_added = __atomic_load_n(&pgc_uts.cache->hot.stats->added_entries, __ATOMIC_RELAXED);
+ stats.hot_deleted = __atomic_load_n(&pgc_uts.cache->hot.stats->removed_entries, __ATOMIC_RELAXED);
+
+ stats.dirty_entries = __atomic_load_n(&pgc_uts.cache->dirty.stats->entries, __ATOMIC_RELAXED);
+ stats.dirty_added = __atomic_load_n(&pgc_uts.cache->dirty.stats->added_entries, __ATOMIC_RELAXED);
+ stats.dirty_deleted = __atomic_load_n(&pgc_uts.cache->dirty.stats->removed_entries, __ATOMIC_RELAXED);
+
+ stats.clean_entries = __atomic_load_n(&pgc_uts.cache->clean.stats->entries, __ATOMIC_RELAXED);
+ stats.clean_added = __atomic_load_n(&pgc_uts.cache->clean.stats->added_entries, __ATOMIC_RELAXED);
+ stats.clean_deleted = __atomic_load_n(&pgc_uts.cache->clean.stats->removed_entries, __ATOMIC_RELAXED);
+
+ stats.searches_exact = __atomic_load_n(&pgc_uts.cache->stats.searches_exact, __ATOMIC_RELAXED);
+ stats.searches_exact_hits = __atomic_load_n(&pgc_uts.cache->stats.searches_exact_hits, __ATOMIC_RELAXED);
+
+ stats.searches_closest = __atomic_load_n(&pgc_uts.cache->stats.searches_closest, __ATOMIC_RELAXED);
+ stats.searches_closest_hits = __atomic_load_n(&pgc_uts.cache->stats.searches_closest_hits, __ATOMIC_RELAXED);
+
+ stats.events_cache_under_severe_pressure = __atomic_load_n(&pgc_uts.cache->stats.events_cache_under_severe_pressure, __ATOMIC_RELAXED);
+ stats.events_cache_needs_space_90 = __atomic_load_n(&pgc_uts.cache->stats.events_cache_needs_space_aggressively, __ATOMIC_RELAXED);
+ stats.events_flush_critical = __atomic_load_n(&pgc_uts.cache->stats.events_flush_critical, __ATOMIC_RELAXED);
+
+ size_t searches_exact = stats.searches_exact - old_stats.searches_exact;
+ size_t searches_closest = stats.searches_closest - old_stats.searches_closest;
+
+ size_t hit_exact = stats.searches_exact_hits - old_stats.searches_exact_hits;
+ size_t hit_closest = stats.searches_closest_hits - old_stats.searches_closest_hits;
+
+ double hit_exact_pc = (searches_exact > 0) ? (double)hit_exact * 100.0 / (double)searches_exact : 0.0;
+ double hit_closest_pc = (searches_closest > 0) ? (double)hit_closest * 100.0 / (double)searches_closest : 0.0;
+
+#ifdef PGC_COUNT_POINTS_COLLECTED
+ stats.collections = __atomic_load_n(&pgc_uts.cache->stats.points_collected, __ATOMIC_RELAXED);
+#endif
+
+ char *cache_status = "N";
+ if(stats.events_cache_under_severe_pressure > old_stats.events_cache_under_severe_pressure)
+ cache_status = "F";
+ else if(stats.events_cache_needs_space_90 > old_stats.events_cache_needs_space_90)
+ cache_status = "f";
+
+ char *flushing_status = "N";
+ if(stats.events_flush_critical > old_stats.events_flush_critical)
+ flushing_status = "F";
+
+ info("PGS %5zuk +%4zuk/-%4zuk "
+ "| RF %5zuk "
+ "| HOT %5zuk +%4zuk -%4zuk "
+ "| DRT %s %5zuk +%4zuk -%4zuk "
+ "| CLN %s %5zuk +%4zuk -%4zuk "
+ "| SRCH %4zuk %4zuk, HIT %4.1f%% %4.1f%% "
+#ifdef PGC_COUNT_POINTS_COLLECTED
+ "| CLCT %8.4f Mps"
+#endif
+ , stats.entries / 1000
+ , (stats.added - old_stats.added) / 1000, (stats.deleted - old_stats.deleted) / 1000
+ , stats.referenced / 1000
+ , stats.hot_entries / 1000, (stats.hot_added - old_stats.hot_added) / 1000, (stats.hot_deleted - old_stats.hot_deleted) / 1000
+ , flushing_status
+ , stats.dirty_entries / 1000
+ , (stats.dirty_added - old_stats.dirty_added) / 1000, (stats.dirty_deleted - old_stats.dirty_deleted) / 1000
+ , cache_status
+ , stats.clean_entries / 1000
+ , (stats.clean_added - old_stats.clean_added) / 1000, (stats.clean_deleted - old_stats.clean_deleted) / 1000
+ , searches_exact / 1000, searches_closest / 1000
+ , hit_exact_pc, hit_closest_pc
+#ifdef PGC_COUNT_POINTS_COLLECTED
+ , (double)(stats.collections - old_stats.collections) / 1000.0 / 1000.0
+#endif
+ );
+ }
+ info("Waiting for threads to stop...");
+ __atomic_store_n(&pgc_uts.stop, true, __ATOMIC_RELAXED);
+
+ netdata_thread_join(service_thread, NULL);
+
+ for(size_t i = 0; i < pgc_uts.collect_threads ;i++)
+ netdata_thread_join(collect_threads[i],NULL);
+
+ for(size_t i = 0; i < pgc_uts.query_threads ;i++)
+ netdata_thread_join(queries_threads[i],NULL);
+
+ pgc_destroy(pgc_uts.cache);
+
+ freez(pgc_uts.metrics);
+ freez(pgc_uts.random_data);
+}
+#endif
+
+int pgc_unittest(void) {
+ PGC *cache = pgc_create("test",
+ 32 * 1024 * 1024, unittest_free_clean_page_callback,
+ 64, NULL, unittest_save_dirty_page_callback,
+ 10, 10, 1000, 10,
+ PGC_OPTIONS_DEFAULT, 1, 11);
+
+ // FIXME - unit tests
+ // - add clean page
+ // - add clean page again (should not add it)
+ // - release page (should decrement counters)
+ // - add hot page
+ // - add hot page again (should not add it)
+ // - turn hot page to dirty, with and without a reference counter to it
+ // - dirty pages are saved once there are enough of them
+ // - find page exact
+ // - find page (should return last)
+ // - find page (should return next)
+ // - page cache full (should evict)
+ // - on destroy, turn hot pages to dirty and save them
+
+ PGC_PAGE *page1 = pgc_page_add_and_acquire(cache, (PGC_ENTRY){
+ .section = 1,
+ .metric_id = 10,
+ .start_time_s = 100,
+ .end_time_s = 1000,
+ .size = 4096,
+ .data = NULL,
+ .hot = false,
+ .custom_data = (uint8_t *)"0123456789",
+ }, NULL);
+
+ if(strcmp(pgc_page_custom_data(cache, page1), "0123456789") != 0)
+ fatal("custom data do not work");
+
+ memcpy(pgc_page_custom_data(cache, page1), "ABCDEFGHIJ", 11);
+ if(strcmp(pgc_page_custom_data(cache, page1), "ABCDEFGHIJ") != 0)
+ fatal("custom data do not work");
+
+ pgc_page_release(cache, page1);
+
+ PGC_PAGE *page2 = pgc_page_add_and_acquire(cache, (PGC_ENTRY){
+ .section = 2,
+ .metric_id = 10,
+ .start_time_s = 1001,
+ .end_time_s = 2000,
+ .size = 4096,
+ .data = NULL,
+ .hot = true,
+ }, NULL);
+
+ pgc_page_hot_set_end_time_s(cache, page2, 2001);
+ pgc_page_hot_to_dirty_and_release(cache, page2);
+
+ PGC_PAGE *page3 = pgc_page_add_and_acquire(cache, (PGC_ENTRY){
+ .section = 3,
+ .metric_id = 10,
+ .start_time_s = 1001,
+ .end_time_s = 2000,
+ .size = 4096,
+ .data = NULL,
+ .hot = true,
+ }, NULL);
+
+ pgc_page_hot_set_end_time_s(cache, page3, 2001);
+ pgc_page_hot_to_dirty_and_release(cache, page3);
+
+ pgc_destroy(cache);
+
+#ifdef PGC_STRESS_TEST
+ unittest_stress_test();
+#endif
+
+ return 0;
+}
diff --git a/database/engine/cache.h b/database/engine/cache.h
new file mode 100644
index 00000000..65e6a613
--- /dev/null
+++ b/database/engine/cache.h
@@ -0,0 +1,249 @@
+#ifndef DBENGINE_CACHE_H
+#define DBENGINE_CACHE_H
+
+#include "../rrd.h"
+
+// CACHE COMPILE TIME CONFIGURATION
+// #define PGC_COUNT_POINTS_COLLECTED 1
+
+typedef struct pgc PGC;
+typedef struct pgc_page PGC_PAGE;
+#define PGC_NAME_MAX 23
+
+typedef enum __attribute__ ((__packed__)) {
+ PGC_OPTIONS_NONE = 0,
+ PGC_OPTIONS_EVICT_PAGES_INLINE = (1 << 0),
+ PGC_OPTIONS_FLUSH_PAGES_INLINE = (1 << 1),
+ PGC_OPTIONS_AUTOSCALE = (1 << 2),
+} PGC_OPTIONS;
+
+#define PGC_OPTIONS_DEFAULT (PGC_OPTIONS_EVICT_PAGES_INLINE | PGC_OPTIONS_FLUSH_PAGES_INLINE | PGC_OPTIONS_AUTOSCALE)
+
+typedef struct pgc_entry {
+ Word_t section; // the section this belongs to
+ Word_t metric_id; // the metric this belongs to
+ time_t start_time_s; // the start time of the page
+ time_t end_time_s; // the end time of the page
+ size_t size; // the size in bytes of the allocation, outside the cache
+ void *data; // a pointer to data outside the cache
+ uint32_t update_every_s; // the update every of the page
+ bool hot; // true if this entry is currently being collected
+ uint8_t *custom_data;
+} PGC_ENTRY;
+
+#define PGC_CACHE_LINE_PADDING(x) uint8_t padding##x[128]
+
+struct pgc_queue_statistics {
+ size_t entries;
+ size_t size;
+
+ PGC_CACHE_LINE_PADDING(1);
+
+ size_t max_entries;
+ size_t max_size;
+
+ PGC_CACHE_LINE_PADDING(2);
+
+ size_t added_entries;
+ size_t added_size;
+
+ PGC_CACHE_LINE_PADDING(3);
+
+ size_t removed_entries;
+ size_t removed_size;
+
+ PGC_CACHE_LINE_PADDING(4);
+};
+
+struct pgc_statistics {
+ size_t wanted_cache_size;
+ size_t current_cache_size;
+
+ PGC_CACHE_LINE_PADDING(1);
+
+ size_t added_entries;
+ size_t added_size;
+
+ PGC_CACHE_LINE_PADDING(2);
+
+ size_t removed_entries;
+ size_t removed_size;
+
+ PGC_CACHE_LINE_PADDING(3);
+
+ size_t entries; // all the entries (includes clean, dirty, host)
+ size_t size; // all the entries (includes clean, dirty, host)
+
+ size_t evicting_entries;
+ size_t evicting_size;
+
+ size_t flushing_entries;
+ size_t flushing_size;
+
+ size_t hot2dirty_entries;
+ size_t hot2dirty_size;
+
+ PGC_CACHE_LINE_PADDING(4);
+
+ size_t acquires;
+ PGC_CACHE_LINE_PADDING(4a);
+ size_t releases;
+ PGC_CACHE_LINE_PADDING(4b);
+ size_t acquires_for_deletion;
+ PGC_CACHE_LINE_PADDING(4c);
+
+ size_t referenced_entries; // all the entries currently referenced
+ size_t referenced_size; // all the entries currently referenced
+
+ PGC_CACHE_LINE_PADDING(5);
+
+ size_t searches_exact;
+ size_t searches_exact_hits;
+ size_t searches_exact_misses;
+
+ PGC_CACHE_LINE_PADDING(6);
+
+ size_t searches_closest;
+ size_t searches_closest_hits;
+ size_t searches_closest_misses;
+
+ PGC_CACHE_LINE_PADDING(7);
+
+ size_t flushes_completed;
+ size_t flushes_completed_size;
+ size_t flushes_cancelled;
+ size_t flushes_cancelled_size;
+
+#ifdef PGC_COUNT_POINTS_COLLECTED
+ PGC_CACHE_LINE_PADDING(8);
+ size_t points_collected;
+#endif
+
+ PGC_CACHE_LINE_PADDING(9);
+
+ size_t insert_spins;
+ size_t evict_spins;
+ size_t release_spins;
+ size_t acquire_spins;
+ size_t delete_spins;
+ size_t flush_spins;
+
+ PGC_CACHE_LINE_PADDING(10);
+
+ size_t workers_search;
+ size_t workers_add;
+ size_t workers_evict;
+ size_t workers_flush;
+ size_t workers_jv2_flush;
+ size_t workers_hot2dirty;
+
+ size_t evict_skipped;
+ size_t hot_empty_pages_evicted_immediately;
+ size_t hot_empty_pages_evicted_later;
+
+ PGC_CACHE_LINE_PADDING(11);
+
+ // events
+ size_t events_cache_under_severe_pressure;
+ size_t events_cache_needs_space_aggressively;
+ size_t events_flush_critical;
+
+ PGC_CACHE_LINE_PADDING(12);
+
+ struct {
+ PGC_CACHE_LINE_PADDING(0);
+ struct pgc_queue_statistics hot;
+ PGC_CACHE_LINE_PADDING(1);
+ struct pgc_queue_statistics dirty;
+ PGC_CACHE_LINE_PADDING(2);
+ struct pgc_queue_statistics clean;
+ PGC_CACHE_LINE_PADDING(3);
+ } queues;
+};
+
+
+typedef void (*free_clean_page_callback)(PGC *cache, PGC_ENTRY entry);
+typedef void (*save_dirty_page_callback)(PGC *cache, PGC_ENTRY *entries_array, PGC_PAGE **pages_array, size_t entries);
+typedef void (*save_dirty_init_callback)(PGC *cache, Word_t section);
+// create a cache
+PGC *pgc_create(const char *name,
+ size_t clean_size_bytes, free_clean_page_callback pgc_free_clean_cb,
+ size_t max_dirty_pages_per_flush, save_dirty_init_callback pgc_save_init_cb, save_dirty_page_callback pgc_save_dirty_cb,
+ size_t max_pages_per_inline_eviction, size_t max_inline_evictors,
+ size_t max_skip_pages_per_inline_eviction,
+ size_t max_flushes_inline,
+ PGC_OPTIONS options, size_t partitions, size_t additional_bytes_per_page);
+
+// destroy the cache
+void pgc_destroy(PGC *cache);
+
+#define PGC_SECTION_ALL ((Word_t)0)
+void pgc_flush_all_hot_and_dirty_pages(PGC *cache, Word_t section);
+
+// add a page to the cache and return a pointer to it
+PGC_PAGE *pgc_page_add_and_acquire(PGC *cache, PGC_ENTRY entry, bool *added);
+
+// get another reference counter on an already referenced page
+PGC_PAGE *pgc_page_dup(PGC *cache, PGC_PAGE *page);
+
+// release a page (all pointers to it are now invalid)
+void pgc_page_release(PGC *cache, PGC_PAGE *page);
+
+// mark a hot page dirty, and release it
+void pgc_page_hot_to_dirty_and_release(PGC *cache, PGC_PAGE *page);
+
+// find a page from the cache
+typedef enum {
+ PGC_SEARCH_EXACT,
+ PGC_SEARCH_CLOSEST,
+ PGC_SEARCH_FIRST,
+ PGC_SEARCH_NEXT,
+ PGC_SEARCH_LAST,
+ PGC_SEARCH_PREV,
+} PGC_SEARCH;
+
+PGC_PAGE *pgc_page_get_and_acquire(PGC *cache, Word_t section, Word_t metric_id, time_t start_time_s, PGC_SEARCH method);
+
+// get information from an acquired page
+Word_t pgc_page_section(PGC_PAGE *page);
+Word_t pgc_page_metric(PGC_PAGE *page);
+time_t pgc_page_start_time_s(PGC_PAGE *page);
+time_t pgc_page_end_time_s(PGC_PAGE *page);
+time_t pgc_page_update_every_s(PGC_PAGE *page);
+time_t pgc_page_fix_update_every(PGC_PAGE *page, time_t update_every_s);
+time_t pgc_page_fix_end_time_s(PGC_PAGE *page, time_t end_time_s);
+void *pgc_page_data(PGC_PAGE *page);
+void *pgc_page_custom_data(PGC *cache, PGC_PAGE *page);
+size_t pgc_page_data_size(PGC *cache, PGC_PAGE *page);
+bool pgc_is_page_hot(PGC_PAGE *page);
+bool pgc_is_page_dirty(PGC_PAGE *page);
+bool pgc_is_page_clean(PGC_PAGE *page);
+void pgc_reset_hot_max(PGC *cache);
+size_t pgc_get_current_cache_size(PGC *cache);
+size_t pgc_get_wanted_cache_size(PGC *cache);
+
+// resetting the end time of a hot page
+void pgc_page_hot_set_end_time_s(PGC *cache, PGC_PAGE *page, time_t end_time_s);
+bool pgc_page_to_clean_evict_or_release(PGC *cache, PGC_PAGE *page);
+
+typedef void (*migrate_to_v2_callback)(Word_t section, unsigned datafile_fileno, uint8_t type, Pvoid_t JudyL_metrics, Pvoid_t JudyL_extents_pos, size_t count_of_unique_extents, size_t count_of_unique_metrics, size_t count_of_unique_pages, void *data);
+void pgc_open_cache_to_journal_v2(PGC *cache, Word_t section, unsigned datafile_fileno, uint8_t type, migrate_to_v2_callback cb, void *data);
+void pgc_open_evict_clean_pages_of_datafile(PGC *cache, struct rrdengine_datafile *datafile);
+size_t pgc_count_clean_pages_having_data_ptr(PGC *cache, Word_t section, void *ptr);
+size_t pgc_count_hot_pages_having_data_ptr(PGC *cache, Word_t section, void *ptr);
+
+typedef size_t (*dynamic_target_cache_size_callback)(void);
+void pgc_set_dynamic_target_cache_size_callback(PGC *cache, dynamic_target_cache_size_callback callback);
+
+// return true when there is more work to do
+bool pgc_evict_pages(PGC *cache, size_t max_skip, size_t max_evict);
+bool pgc_flush_pages(PGC *cache, size_t max_flushes);
+
+struct pgc_statistics pgc_get_statistics(PGC *cache);
+size_t pgc_hot_and_dirty_entries(PGC *cache);
+
+struct aral_statistics *pgc_aral_statistics(void);
+size_t pgc_aral_structures(void);
+size_t pgc_aral_overhead(void);
+
+#endif // DBENGINE_CACHE_H
diff --git a/database/engine/datafile.c b/database/engine/datafile.c
index 9c70068d..286ae1e3 100644
--- a/database/engine/datafile.c
+++ b/database/engine/datafile.c
@@ -1,58 +1,174 @@
// SPDX-License-Identifier: GPL-3.0-or-later
#include "rrdengine.h"
-void df_extent_insert(struct extent_info *extent)
-{
- struct rrdengine_datafile *datafile = extent->datafile;
-
- if (likely(NULL != datafile->extents.last)) {
- datafile->extents.last->next = extent;
- }
- if (unlikely(NULL == datafile->extents.first)) {
- datafile->extents.first = extent;
- }
- datafile->extents.last = extent;
-}
-
void datafile_list_insert(struct rrdengine_instance *ctx, struct rrdengine_datafile *datafile)
{
- if (likely(NULL != ctx->datafiles.last)) {
- ctx->datafiles.last->next = datafile;
- }
- if (unlikely(NULL == ctx->datafiles.first)) {
- ctx->datafiles.first = datafile;
- }
- ctx->datafiles.last = datafile;
+ uv_rwlock_wrlock(&ctx->datafiles.rwlock);
+ DOUBLE_LINKED_LIST_APPEND_ITEM_UNSAFE(ctx->datafiles.first, datafile, prev, next);
+ uv_rwlock_wrunlock(&ctx->datafiles.rwlock);
}
-void datafile_list_delete(struct rrdengine_instance *ctx, struct rrdengine_datafile *datafile)
+void datafile_list_delete_unsafe(struct rrdengine_instance *ctx, struct rrdengine_datafile *datafile)
{
- struct rrdengine_datafile *next;
-
- next = datafile->next;
- fatal_assert((NULL != next) && (ctx->datafiles.first == datafile) && (ctx->datafiles.last != datafile));
- ctx->datafiles.first = next;
+ DOUBLE_LINKED_LIST_REMOVE_ITEM_UNSAFE(ctx->datafiles.first, datafile, prev, next);
}
-static void datafile_init(struct rrdengine_datafile *datafile, struct rrdengine_instance *ctx,
- unsigned tier, unsigned fileno)
+static struct rrdengine_datafile *datafile_alloc_and_init(struct rrdengine_instance *ctx, unsigned tier, unsigned fileno)
{
fatal_assert(tier == 1);
+
+ struct rrdengine_datafile *datafile = callocz(1, sizeof(struct rrdengine_datafile));
+
datafile->tier = tier;
datafile->fileno = fileno;
- datafile->file = (uv_file)0;
- datafile->pos = 0;
- datafile->extents.first = datafile->extents.last = NULL; /* will be populated by journalfile */
- datafile->journalfile = NULL;
- datafile->next = NULL;
+ fatal_assert(0 == uv_rwlock_init(&datafile->extent_rwlock));
datafile->ctx = ctx;
+
+ datafile->users.available = true;
+
+ netdata_spinlock_init(&datafile->users.spinlock);
+ netdata_spinlock_init(&datafile->writers.spinlock);
+ netdata_spinlock_init(&datafile->extent_queries.spinlock);
+
+ return datafile;
+}
+
+void datafile_acquire_dup(struct rrdengine_datafile *df) {
+ netdata_spinlock_lock(&df->users.spinlock);
+
+ if(!df->users.lockers)
+ fatal("DBENGINE: datafile is not acquired to duplicate");
+
+ df->users.lockers++;
+
+ netdata_spinlock_unlock(&df->users.spinlock);
+}
+
+bool datafile_acquire(struct rrdengine_datafile *df, DATAFILE_ACQUIRE_REASONS reason) {
+ bool ret;
+
+ netdata_spinlock_lock(&df->users.spinlock);
+
+ if(df->users.available) {
+ ret = true;
+ df->users.lockers++;
+ df->users.lockers_by_reason[reason]++;
+ }
+ else
+ ret = false;
+
+ netdata_spinlock_unlock(&df->users.spinlock);
+
+ return ret;
+}
+
+void datafile_release(struct rrdengine_datafile *df, DATAFILE_ACQUIRE_REASONS reason) {
+ netdata_spinlock_lock(&df->users.spinlock);
+ if(!df->users.lockers)
+ fatal("DBENGINE DATAFILE: cannot release a datafile that is not acquired");
+
+ df->users.lockers--;
+ df->users.lockers_by_reason[reason]--;
+ netdata_spinlock_unlock(&df->users.spinlock);
+}
+
+bool datafile_acquire_for_deletion(struct rrdengine_datafile *df) {
+ bool can_be_deleted = false;
+
+ netdata_spinlock_lock(&df->users.spinlock);
+ df->users.available = false;
+
+ if(!df->users.lockers)
+ can_be_deleted = true;
+
+ else {
+ // there are lockers
+
+ // evict any pages referencing this in the open cache
+ netdata_spinlock_unlock(&df->users.spinlock);
+ pgc_open_evict_clean_pages_of_datafile(open_cache, df);
+ netdata_spinlock_lock(&df->users.spinlock);
+
+ if(!df->users.lockers)
+ can_be_deleted = true;
+
+ else {
+ // there are lockers still
+
+ // count the number of pages referencing this in the open cache
+ netdata_spinlock_unlock(&df->users.spinlock);
+ usec_t time_to_scan_ut = now_monotonic_usec();
+ size_t clean_pages_in_open_cache = pgc_count_clean_pages_having_data_ptr(open_cache, (Word_t)df->ctx, df);
+ size_t hot_pages_in_open_cache = pgc_count_hot_pages_having_data_ptr(open_cache, (Word_t)df->ctx, df);
+ time_to_scan_ut = now_monotonic_usec() - time_to_scan_ut;
+ netdata_spinlock_lock(&df->users.spinlock);
+
+ if(!df->users.lockers)
+ can_be_deleted = true;
+
+ else if(!clean_pages_in_open_cache && !hot_pages_in_open_cache) {
+ // no pages in the open cache related to this datafile
+
+ time_t now_s = now_monotonic_sec();
+
+ if(!df->users.time_to_evict) {
+ // first time we did the above
+ df->users.time_to_evict = now_s + 120;
+ internal_error(true, "DBENGINE: datafile %u of tier %d is not used by any open cache pages, "
+ "but it has %u lockers (oc:%u, pd:%u), "
+ "%zu clean and %zu hot open cache pages "
+ "- will be deleted shortly "
+ "(scanned open cache in %llu usecs)",
+ df->fileno, df->ctx->config.tier,
+ df->users.lockers,
+ df->users.lockers_by_reason[DATAFILE_ACQUIRE_OPEN_CACHE],
+ df->users.lockers_by_reason[DATAFILE_ACQUIRE_PAGE_DETAILS],
+ clean_pages_in_open_cache,
+ hot_pages_in_open_cache,
+ time_to_scan_ut);
+ }
+
+ else if(now_s > df->users.time_to_evict) {
+ // time expired, lets remove it
+ can_be_deleted = true;
+ internal_error(true, "DBENGINE: datafile %u of tier %d is not used by any open cache pages, "
+ "but it has %u lockers (oc:%u, pd:%u), "
+ "%zu clean and %zu hot open cache pages "
+ "- will be deleted now "
+ "(scanned open cache in %llu usecs)",
+ df->fileno, df->ctx->config.tier,
+ df->users.lockers,
+ df->users.lockers_by_reason[DATAFILE_ACQUIRE_OPEN_CACHE],
+ df->users.lockers_by_reason[DATAFILE_ACQUIRE_PAGE_DETAILS],
+ clean_pages_in_open_cache,
+ hot_pages_in_open_cache,
+ time_to_scan_ut);
+ }
+ }
+ else
+ internal_error(true, "DBENGINE: datafile %u of tier %d "
+ "has %u lockers (oc:%u, pd:%u), "
+ "%zu clean and %zu hot open cache pages "
+ "(scanned open cache in %llu usecs)",
+ df->fileno, df->ctx->config.tier,
+ df->users.lockers,
+ df->users.lockers_by_reason[DATAFILE_ACQUIRE_OPEN_CACHE],
+ df->users.lockers_by_reason[DATAFILE_ACQUIRE_PAGE_DETAILS],
+ clean_pages_in_open_cache,
+ hot_pages_in_open_cache,
+ time_to_scan_ut);
+ }
+ }
+ netdata_spinlock_unlock(&df->users.spinlock);
+
+ return can_be_deleted;
}
void generate_datafilepath(struct rrdengine_datafile *datafile, char *str, size_t maxlen)
{
(void) snprintfz(str, maxlen, "%s/" DATAFILE_PREFIX RRDENG_FILE_NUMBER_PRINT_TMPL DATAFILE_EXTENSION,
- datafile->ctx->dbfiles_path, datafile->tier, datafile->fileno);
+ datafile->ctx->config.dbfiles_path, datafile->tier, datafile->fileno);
}
int close_data_file(struct rrdengine_datafile *datafile)
@@ -66,9 +182,8 @@ int close_data_file(struct rrdengine_datafile *datafile)
ret = uv_fs_close(NULL, &req, datafile->file, NULL);
if (ret < 0) {
- error("uv_fs_close(%s): %s", path, uv_strerror(ret));
- ++ctx->stats.fs_errors;
- rrd_stat_atomic_add(&global_fs_errors, 1);
+ error("DBENGINE: uv_fs_close(%s): %s", path, uv_strerror(ret));
+ ctx_fs_error(ctx);
}
uv_fs_req_cleanup(&req);
@@ -86,18 +201,17 @@ int unlink_data_file(struct rrdengine_datafile *datafile)
ret = uv_fs_unlink(NULL, &req, path, NULL);
if (ret < 0) {
- error("uv_fs_fsunlink(%s): %s", path, uv_strerror(ret));
- ++ctx->stats.fs_errors;
- rrd_stat_atomic_add(&global_fs_errors, 1);
+ error("DBENGINE: uv_fs_fsunlink(%s): %s", path, uv_strerror(ret));
+ ctx_fs_error(ctx);
}
uv_fs_req_cleanup(&req);
- ++ctx->stats.datafile_deletions;
+ __atomic_add_fetch(&ctx->stats.datafile_deletions, 1, __ATOMIC_RELAXED);
return ret;
}
-int destroy_data_file(struct rrdengine_datafile *datafile)
+int destroy_data_file_unsafe(struct rrdengine_datafile *datafile)
{
struct rrdengine_instance *ctx = datafile->ctx;
uv_fs_t req;
@@ -108,29 +222,26 @@ int destroy_data_file(struct rrdengine_datafile *datafile)
ret = uv_fs_ftruncate(NULL, &req, datafile->file, 0, NULL);
if (ret < 0) {
- error("uv_fs_ftruncate(%s): %s", path, uv_strerror(ret));
- ++ctx->stats.fs_errors;
- rrd_stat_atomic_add(&global_fs_errors, 1);
+ error("DBENGINE: uv_fs_ftruncate(%s): %s", path, uv_strerror(ret));
+ ctx_fs_error(ctx);
}
uv_fs_req_cleanup(&req);
ret = uv_fs_close(NULL, &req, datafile->file, NULL);
if (ret < 0) {
- error("uv_fs_close(%s): %s", path, uv_strerror(ret));
- ++ctx->stats.fs_errors;
- rrd_stat_atomic_add(&global_fs_errors, 1);
+ error("DBENGINE: uv_fs_close(%s): %s", path, uv_strerror(ret));
+ ctx_fs_error(ctx);
}
uv_fs_req_cleanup(&req);
ret = uv_fs_unlink(NULL, &req, path, NULL);
if (ret < 0) {
- error("uv_fs_fsunlink(%s): %s", path, uv_strerror(ret));
- ++ctx->stats.fs_errors;
- rrd_stat_atomic_add(&global_fs_errors, 1);
+ error("DBENGINE: uv_fs_fsunlink(%s): %s", path, uv_strerror(ret));
+ ctx_fs_error(ctx);
}
uv_fs_req_cleanup(&req);
- ++ctx->stats.datafile_deletions;
+ __atomic_add_fetch(&ctx->stats.datafile_deletions, 1, __ATOMIC_RELAXED);
return ret;
}
@@ -146,18 +257,17 @@ int create_data_file(struct rrdengine_datafile *datafile)
char path[RRDENG_PATH_MAX];
generate_datafilepath(datafile, path, sizeof(path));
- fd = open_file_direct_io(path, O_CREAT | O_RDWR | O_TRUNC, &file);
+ fd = open_file_for_io(path, O_CREAT | O_RDWR | O_TRUNC, &file, use_direct_io);
if (fd < 0) {
- ++ctx->stats.fs_errors;
- rrd_stat_atomic_add(&global_fs_errors, 1);
+ ctx_fs_error(ctx);
return fd;
}
datafile->file = file;
- ++ctx->stats.datafile_creations;
+ __atomic_add_fetch(&ctx->stats.datafile_creations, 1, __ATOMIC_RELAXED);
ret = posix_memalign((void *)&superblock, RRDFILE_ALIGNMENT, sizeof(*superblock));
if (unlikely(ret)) {
- fatal("posix_memalign:%s", strerror(ret));
+ fatal("DBENGINE: posix_memalign:%s", strerror(ret));
}
memset(superblock, 0, sizeof(*superblock));
(void) strncpy(superblock->magic_number, RRDENG_DF_MAGIC, RRDENG_MAGIC_SZ);
@@ -169,20 +279,18 @@ int create_data_file(struct rrdengine_datafile *datafile)
ret = uv_fs_write(NULL, &req, file, &iov, 1, 0, NULL);
if (ret < 0) {
fatal_assert(req.result < 0);
- error("uv_fs_write: %s", uv_strerror(ret));
- ++ctx->stats.io_errors;
- rrd_stat_atomic_add(&global_io_errors, 1);
+ error("DBENGINE: uv_fs_write: %s", uv_strerror(ret));
+ ctx_io_error(ctx);
}
uv_fs_req_cleanup(&req);
posix_memfree(superblock);
if (ret < 0) {
- destroy_data_file(datafile);
+ destroy_data_file_unsafe(datafile);
return ret;
}
datafile->pos = sizeof(*superblock);
- ctx->stats.io_write_bytes += sizeof(*superblock);
- ++ctx->stats.io_write_requests;
+ ctx_io_write_op_bytes(ctx, sizeof(*superblock));
return 0;
}
@@ -196,13 +304,13 @@ static int check_data_file_superblock(uv_file file)
ret = posix_memalign((void *)&superblock, RRDFILE_ALIGNMENT, sizeof(*superblock));
if (unlikely(ret)) {
- fatal("posix_memalign:%s", strerror(ret));
+ fatal("DBENGINE: posix_memalign:%s", strerror(ret));
}
iov = uv_buf_init((void *)superblock, sizeof(*superblock));
ret = uv_fs_read(NULL, &req, file, &iov, 1, 0, NULL);
if (ret < 0) {
- error("uv_fs_read: %s", uv_strerror(ret));
+ error("DBENGINE: uv_fs_read: %s", uv_strerror(ret));
uv_fs_req_cleanup(&req);
goto error;
}
@@ -212,7 +320,7 @@ static int check_data_file_superblock(uv_file file)
if (strncmp(superblock->magic_number, RRDENG_DF_MAGIC, RRDENG_MAGIC_SZ) ||
strncmp(superblock->version, RRDENG_DF_VER, RRDENG_VER_SZ) ||
superblock->tier != 1) {
- error("File has invalid superblock.");
+ error("DBENGINE: file has invalid superblock.");
ret = UV_EINVAL;
} else {
ret = 0;
@@ -232,13 +340,12 @@ static int load_data_file(struct rrdengine_datafile *datafile)
char path[RRDENG_PATH_MAX];
generate_datafilepath(datafile, path, sizeof(path));
- fd = open_file_direct_io(path, O_RDWR, &file);
+ fd = open_file_for_io(path, O_RDWR, &file, use_direct_io);
if (fd < 0) {
- ++ctx->stats.fs_errors;
- rrd_stat_atomic_add(&global_fs_errors, 1);
+ ctx_fs_error(ctx);
return fd;
}
- info("Initializing data file \"%s\".", path);
+ info("DBENGINE: initializing data file \"%s\".", path);
ret = check_file_properties(file, &file_size, sizeof(struct rrdeng_df_sb));
if (ret)
@@ -248,22 +355,21 @@ static int load_data_file(struct rrdengine_datafile *datafile)
ret = check_data_file_superblock(file);
if (ret)
goto error;
- ctx->stats.io_read_bytes += sizeof(struct rrdeng_df_sb);
- ++ctx->stats.io_read_requests;
+
+ ctx_io_read_op_bytes(ctx, sizeof(struct rrdeng_df_sb));
datafile->file = file;
datafile->pos = file_size;
- info("Data file \"%s\" initialized (size:%"PRIu64").", path, file_size);
+ info("DBENGINE: data file \"%s\" initialized (size:%"PRIu64").", path, file_size);
return 0;
error:
error = ret;
ret = uv_fs_close(NULL, &req, file, NULL);
if (ret < 0) {
- error("uv_fs_close(%s): %s", path, uv_strerror(ret));
- ++ctx->stats.fs_errors;
- rrd_stat_atomic_add(&global_fs_errors, 1);
+ error("DBENGINE: uv_fs_close(%s): %s", path, uv_strerror(ret));
+ ctx_fs_error(ctx);
}
uv_fs_req_cleanup(&req);
return error;
@@ -286,30 +392,26 @@ static int scan_data_files(struct rrdengine_instance *ctx)
{
int ret;
unsigned tier, no, matched_files, i,failed_to_load;
- static uv_fs_t req;
+ uv_fs_t req;
uv_dirent_t dent;
struct rrdengine_datafile **datafiles, *datafile;
struct rrdengine_journalfile *journalfile;
- ret = uv_fs_scandir(NULL, &req, ctx->dbfiles_path, 0, NULL);
+ ret = uv_fs_scandir(NULL, &req, ctx->config.dbfiles_path, 0, NULL);
if (ret < 0) {
fatal_assert(req.result < 0);
uv_fs_req_cleanup(&req);
- error("uv_fs_scandir(%s): %s", ctx->dbfiles_path, uv_strerror(ret));
- ++ctx->stats.fs_errors;
- rrd_stat_atomic_add(&global_fs_errors, 1);
+ error("DBENGINE: uv_fs_scandir(%s): %s", ctx->config.dbfiles_path, uv_strerror(ret));
+ ctx_fs_error(ctx);
return ret;
}
- info("Found %d files in path %s", ret, ctx->dbfiles_path);
+ info("DBENGINE: found %d files in path %s", ret, ctx->config.dbfiles_path);
datafiles = callocz(MIN(ret, MAX_DATAFILES), sizeof(*datafiles));
for (matched_files = 0 ; UV_EOF != uv_fs_scandir_next(&req, &dent) && matched_files < MAX_DATAFILES ; ) {
- info("Scanning file \"%s/%s\"", ctx->dbfiles_path, dent.name);
ret = sscanf(dent.name, DATAFILE_PREFIX RRDENG_FILE_NUMBER_SCAN_TMPL DATAFILE_EXTENSION, &tier, &no);
if (2 == ret) {
- info("Matched file \"%s/%s\"", ctx->dbfiles_path, dent.name);
- datafile = mallocz(sizeof(*datafile));
- datafile_init(datafile, ctx, tier, no);
+ datafile = datafile_alloc_and_init(ctx, tier, no);
datafiles[matched_files++] = datafile;
}
}
@@ -320,11 +422,11 @@ static int scan_data_files(struct rrdengine_instance *ctx)
return 0;
}
if (matched_files == MAX_DATAFILES) {
- error("Warning: hit maximum database engine file limit of %d files", MAX_DATAFILES);
+ error("DBENGINE: warning: hit maximum database engine file limit of %d files", MAX_DATAFILES);
}
qsort(datafiles, matched_files, sizeof(*datafiles), scan_data_files_cmp);
/* TODO: change this when tiering is implemented */
- ctx->last_fileno = datafiles[matched_files - 1]->fileno;
+ ctx->atomic.last_fileno = datafiles[matched_files - 1]->fileno;
for (failed_to_load = 0, i = 0 ; i < matched_files ; ++i) {
uint8_t must_delete_pair = 0;
@@ -334,10 +436,8 @@ static int scan_data_files(struct rrdengine_instance *ctx)
if (0 != ret) {
must_delete_pair = 1;
}
- journalfile = mallocz(sizeof(*journalfile));
- datafile->journalfile = journalfile;
- journalfile_init(journalfile, datafile);
- ret = load_journal_file(ctx, journalfile, datafile);
+ journalfile = journalfile_alloc_and_init(datafile);
+ ret = journalfile_load(ctx, journalfile, datafile);
if (0 != ret) {
if (!must_delete_pair) /* If datafile is still open close it */
close_data_file(datafile);
@@ -346,16 +446,16 @@ static int scan_data_files(struct rrdengine_instance *ctx)
if (must_delete_pair) {
char path[RRDENG_PATH_MAX];
- error("Deleting invalid data and journal file pair.");
- ret = unlink_journal_file(journalfile);
+ error("DBENGINE: deleting invalid data and journal file pair.");
+ ret = journalfile_unlink(journalfile);
if (!ret) {
- generate_journalfilepath(datafile, path, sizeof(path));
- info("Deleted journal file \"%s\".", path);
+ journalfile_v1_generate_path(datafile, path, sizeof(path));
+ info("DBENGINE: deleted journal file \"%s\".", path);
}
ret = unlink_data_file(datafile);
if (!ret) {
generate_datafilepath(datafile, path, sizeof(path));
- info("Deleted data file \"%s\".", path);
+ info("DBENGINE: deleted data file \"%s\".", path);
}
freez(journalfile);
freez(datafile);
@@ -363,8 +463,8 @@ static int scan_data_files(struct rrdengine_instance *ctx)
continue;
}
+ ctx_current_disk_space_increase(ctx, datafile->pos + journalfile->unsafe.pos);
datafile_list_insert(ctx, datafile);
- ctx->disk_space += datafile->pos + journalfile->pos;
}
matched_files -= failed_to_load;
freez(datafiles);
@@ -373,42 +473,43 @@ static int scan_data_files(struct rrdengine_instance *ctx)
}
/* Creates a datafile and a journalfile pair */
-int create_new_datafile_pair(struct rrdengine_instance *ctx, unsigned tier, unsigned fileno)
+int create_new_datafile_pair(struct rrdengine_instance *ctx)
{
+ __atomic_add_fetch(&rrdeng_cache_efficiency_stats.datafile_creation_started, 1, __ATOMIC_RELAXED);
+
struct rrdengine_datafile *datafile;
struct rrdengine_journalfile *journalfile;
+ unsigned fileno = ctx_last_fileno_get(ctx) + 1;
int ret;
char path[RRDENG_PATH_MAX];
- info("Creating new data and journal files in path %s", ctx->dbfiles_path);
- datafile = mallocz(sizeof(*datafile));
- datafile_init(datafile, ctx, tier, fileno);
+ info("DBENGINE: creating new data and journal files in path %s", ctx->config.dbfiles_path);
+ datafile = datafile_alloc_and_init(ctx, 1, fileno);
ret = create_data_file(datafile);
- if (!ret) {
- generate_datafilepath(datafile, path, sizeof(path));
- info("Created data file \"%s\".", path);
- } else {
+ if(ret)
goto error_after_datafile;
- }
- journalfile = mallocz(sizeof(*journalfile));
- datafile->journalfile = journalfile;
- journalfile_init(journalfile, datafile);
- ret = create_journal_file(journalfile, datafile);
- if (!ret) {
- generate_journalfilepath(datafile, path, sizeof(path));
- info("Created journal file \"%s\".", path);
- } else {
+ generate_datafilepath(datafile, path, sizeof(path));
+ info("DBENGINE: created data file \"%s\".", path);
+
+ journalfile = journalfile_alloc_and_init(datafile);
+ ret = journalfile_create(journalfile, datafile);
+ if (ret)
goto error_after_journalfile;
- }
+
+ journalfile_v1_generate_path(datafile, path, sizeof(path));
+ info("DBENGINE: created journal file \"%s\".", path);
+
+ ctx_current_disk_space_increase(ctx, datafile->pos + journalfile->unsafe.pos);
datafile_list_insert(ctx, datafile);
- ctx->disk_space += datafile->pos + journalfile->pos;
+ ctx_last_fileno_increment(ctx);
return 0;
error_after_journalfile:
- destroy_data_file(datafile);
+ destroy_data_file_unsafe(datafile);
freez(journalfile);
+
error_after_datafile:
freez(datafile);
return ret;
@@ -421,40 +522,86 @@ int init_data_files(struct rrdengine_instance *ctx)
{
int ret;
+ fatal_assert(0 == uv_rwlock_init(&ctx->datafiles.rwlock));
ret = scan_data_files(ctx);
if (ret < 0) {
- error("Failed to scan path \"%s\".", ctx->dbfiles_path);
+ error("DBENGINE: failed to scan path \"%s\".", ctx->config.dbfiles_path);
return ret;
} else if (0 == ret) {
- info("Data files not found, creating in path \"%s\".", ctx->dbfiles_path);
- ret = create_new_datafile_pair(ctx, 1, 1);
+ info("DBENGINE: data files not found, creating in path \"%s\".", ctx->config.dbfiles_path);
+ ctx->atomic.last_fileno = 0;
+ ret = create_new_datafile_pair(ctx);
if (ret) {
- error("Failed to create data and journal files in path \"%s\".", ctx->dbfiles_path);
+ error("DBENGINE: failed to create data and journal files in path \"%s\".", ctx->config.dbfiles_path);
return ret;
}
- ctx->last_fileno = 1;
+ }
+ else {
+ if (ctx->loading.create_new_datafile_pair)
+ create_new_datafile_pair(ctx);
+
+ while(rrdeng_ctx_exceeded_disk_quota(ctx))
+ datafile_delete(ctx, ctx->datafiles.first, false, false);
}
+ pgc_reset_hot_max(open_cache);
+ ctx->loading.create_new_datafile_pair = false;
return 0;
}
void finalize_data_files(struct rrdengine_instance *ctx)
{
- struct rrdengine_datafile *datafile, *next_datafile;
- struct rrdengine_journalfile *journalfile;
- struct extent_info *extent, *next_extent;
+ bool logged = false;
+
+ logged = false;
+ while(__atomic_load_n(&ctx->atomic.extents_currently_being_flushed, __ATOMIC_RELAXED)) {
+ if(!logged) {
+ info("Waiting for inflight flush to finish on tier %d...", ctx->config.tier);
+ logged = true;
+ }
+ sleep_usec(100 * USEC_PER_MS);
+ }
- for (datafile = ctx->datafiles.first ; datafile != NULL ; datafile = next_datafile) {
- journalfile = datafile->journalfile;
- next_datafile = datafile->next;
+ do {
+ struct rrdengine_datafile *datafile = ctx->datafiles.first;
+ struct rrdengine_journalfile *journalfile = datafile->journalfile;
- for (extent = datafile->extents.first ; extent != NULL ; extent = next_extent) {
- next_extent = extent->next;
- freez(extent);
+ logged = false;
+ size_t iterations = 100;
+ while(!datafile_acquire_for_deletion(datafile) && datafile != ctx->datafiles.first->prev && --iterations > 0) {
+ if(!logged) {
+ info("Waiting to acquire data file %u of tier %d to close it...", datafile->fileno, ctx->config.tier);
+ logged = true;
+ }
+ sleep_usec(100 * USEC_PER_MS);
}
- close_journal_file(journalfile, datafile);
+
+ logged = false;
+ bool available = false;
+ do {
+ uv_rwlock_wrlock(&ctx->datafiles.rwlock);
+ netdata_spinlock_lock(&datafile->writers.spinlock);
+ available = (datafile->writers.running || datafile->writers.flushed_to_open_running) ? false : true;
+
+ if(!available) {
+ netdata_spinlock_unlock(&datafile->writers.spinlock);
+ uv_rwlock_wrunlock(&ctx->datafiles.rwlock);
+ if(!logged) {
+ info("Waiting for writers to data file %u of tier %d to finish...", datafile->fileno, ctx->config.tier);
+ logged = true;
+ }
+ sleep_usec(100 * USEC_PER_MS);
+ }
+ } while(!available);
+
+ journalfile_close(journalfile, datafile);
close_data_file(datafile);
+ datafile_list_delete_unsafe(ctx, datafile);
+ netdata_spinlock_unlock(&datafile->writers.spinlock);
+ uv_rwlock_wrunlock(&ctx->datafiles.rwlock);
+
freez(journalfile);
freez(datafile);
- }
+
+ } while(ctx->datafiles.first);
}
diff --git a/database/engine/datafile.h b/database/engine/datafile.h
index 1cf256af..274add91 100644
--- a/database/engine/datafile.h
+++ b/database/engine/datafile.h
@@ -13,27 +13,25 @@ struct rrdengine_instance;
#define DATAFILE_PREFIX "datafile-"
#define DATAFILE_EXTENSION ".ndf"
-#define MAX_DATAFILE_SIZE (1073741824LU)
-#define MIN_DATAFILE_SIZE (4194304LU)
+#ifndef MAX_DATAFILE_SIZE
+#define MAX_DATAFILE_SIZE (512LU * 1024LU * 1024LU)
+#endif
+#if MIN_DATAFILE_SIZE > MAX_DATAFILE_SIZE
+#error MIN_DATAFILE_SIZE > MAX_DATAFILE_SIZE
+#endif
+
+#define MIN_DATAFILE_SIZE (4LU * 1024LU * 1024LU)
#define MAX_DATAFILES (65536) /* Supports up to 64TiB for now */
-#define TARGET_DATAFILES (20)
+#define TARGET_DATAFILES (50)
-#define DATAFILE_IDEAL_IO_SIZE (1048576U)
+typedef enum __attribute__ ((__packed__)) {
+ DATAFILE_ACQUIRE_OPEN_CACHE = 0,
+ DATAFILE_ACQUIRE_PAGE_DETAILS,
+ DATAFILE_ACQUIRE_RETENTION,
-struct extent_info {
- uint64_t offset;
- uint32_t size;
- uint8_t number_of_pages;
- struct rrdengine_datafile *datafile;
- struct extent_info *next;
- struct rrdeng_page_descr *pages[];
-};
-
-struct rrdengine_df_extents {
- /* the extent list is sorted based on disk offset */
- struct extent_info *first;
- struct extent_info *last;
-};
+ // terminator
+ DATAFILE_ACQUIRE_MAX,
+} DATAFILE_ACQUIRE_REASONS;
/* only one event loop is supported for now */
struct rrdengine_datafile {
@@ -41,26 +39,50 @@ struct rrdengine_datafile {
unsigned fileno;
uv_file file;
uint64_t pos;
+ uv_rwlock_t extent_rwlock;
struct rrdengine_instance *ctx;
- struct rrdengine_df_extents extents;
struct rrdengine_journalfile *journalfile;
+ struct rrdengine_datafile *prev;
struct rrdengine_datafile *next;
-};
-struct rrdengine_datafile_list {
- struct rrdengine_datafile *first; /* oldest */
- struct rrdengine_datafile *last; /* newest */
+ struct {
+ SPINLOCK spinlock;
+ bool populated;
+ } populate_mrg;
+
+ struct {
+ SPINLOCK spinlock;
+ size_t running;
+ size_t flushed_to_open_running;
+ } writers;
+
+ struct {
+ SPINLOCK spinlock;
+ unsigned lockers;
+ unsigned lockers_by_reason[DATAFILE_ACQUIRE_MAX];
+ bool available;
+ time_t time_to_evict;
+ } users;
+
+ struct {
+ SPINLOCK spinlock;
+ Pvoid_t pending_epdl_by_extent_offset_judyL;
+ } extent_queries;
};
-void df_extent_insert(struct extent_info *extent);
+void datafile_acquire_dup(struct rrdengine_datafile *df);
+bool datafile_acquire(struct rrdengine_datafile *df, DATAFILE_ACQUIRE_REASONS reason);
+void datafile_release(struct rrdengine_datafile *df, DATAFILE_ACQUIRE_REASONS reason);
+bool datafile_acquire_for_deletion(struct rrdengine_datafile *df);
+
void datafile_list_insert(struct rrdengine_instance *ctx, struct rrdengine_datafile *datafile);
-void datafile_list_delete(struct rrdengine_instance *ctx, struct rrdengine_datafile *datafile);
+void datafile_list_delete_unsafe(struct rrdengine_instance *ctx, struct rrdengine_datafile *datafile);
void generate_datafilepath(struct rrdengine_datafile *datafile, char *str, size_t maxlen);
int close_data_file(struct rrdengine_datafile *datafile);
int unlink_data_file(struct rrdengine_datafile *datafile);
-int destroy_data_file(struct rrdengine_datafile *datafile);
+int destroy_data_file_unsafe(struct rrdengine_datafile *datafile);
int create_data_file(struct rrdengine_datafile *datafile);
-int create_new_datafile_pair(struct rrdengine_instance *ctx, unsigned tier, unsigned fileno);
+int create_new_datafile_pair(struct rrdengine_instance *ctx);
int init_data_files(struct rrdengine_instance *ctx);
void finalize_data_files(struct rrdengine_instance *ctx);
diff --git a/database/engine/datafile.ksy b/database/engine/datafile.ksy
new file mode 100644
index 00000000..28d4b393
--- /dev/null
+++ b/database/engine/datafile.ksy
@@ -0,0 +1,74 @@
+meta:
+ id: netdata_datafile
+ endian: le
+
+seq:
+ - id: hdr
+ type: header
+ size: 4096
+ - id: extents
+ type: extent
+ repeat: eos
+
+types:
+ header:
+ seq:
+ - id: magic
+ contents: "netdata-data-file"
+ - id: reserved
+ size: 15
+ - id: version
+ contents: "1.0"
+ - id: reserved1
+ size: 13
+ - id: tier
+ type: u1
+ extent_page_descr:
+ seq:
+ - id: type
+ type: u1
+ enum: page_type
+ - id: uuid
+ size: 16
+ - id: page_len
+ type: u4
+ - id: start_time_ut
+ type: u8
+ - id: end_time_ut
+ type: u8
+ enums:
+ page_type:
+ 0: metrics
+ 1: tier
+ extent_header:
+ seq:
+ - id: payload_length
+ type: u4
+ - id: compression_algorithm
+ type: u1
+ enum: compression_algos
+ - id: number_of_pages
+ type: u1
+ - id: page_descriptors
+ type: extent_page_descr
+ repeat: expr
+ repeat-expr: number_of_pages
+ enums:
+ compression_algos:
+ 0: rrd_no_compression
+ 1: rrd_lz4
+ extent_trailer:
+ seq:
+ - id: crc32_checksum
+ type: u4
+ extent:
+ seq:
+ - id: header
+ type: extent_header
+ - id: payload
+ size: header.payload_length
+ - id: trailer
+ type: extent_trailer
+ - id: padding
+ size: (((_io.pos + 4095) / 4096) * 4096) - _io.pos
+ # the extent size is made to always be a multiple of 4096
diff --git a/database/engine/dbengine-diagram.xml b/database/engine/dbengine-diagram.xml
new file mode 100644
index 00000000..793e8a35
--- /dev/null
+++ b/database/engine/dbengine-diagram.xml
@@ -0,0 +1 @@
+<mxfile host="app.diagrams.net" modified="2023-01-16T23:29:24.274Z" agent="5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36" etag="IzytOgui5y4srcr9Zrcm" version="20.5.1" type="device"><diagram name="Page-1" id="90a13364-a465-7bf4-72fc-28e22215d7a0">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</diagram></mxfile> \ No newline at end of file
diff --git a/database/engine/journalfile.c b/database/engine/journalfile.c
index 500dd788..de2b909c 100644
--- a/database/engine/journalfile.c
+++ b/database/engine/journalfile.c
@@ -1,132 +1,424 @@
// SPDX-License-Identifier: GPL-3.0-or-later
#include "rrdengine.h"
-static void flush_transaction_buffer_cb(uv_fs_t* req)
+
+// DBENGINE2: Helper
+
+static void update_metric_retention_and_granularity_by_uuid(
+ struct rrdengine_instance *ctx, uuid_t *uuid,
+ time_t first_time_s, time_t last_time_s,
+ time_t update_every_s, time_t now_s)
+{
+ if(unlikely(last_time_s > now_s)) {
+ error_limit_static_global_var(erl, 1, 0);
+ error_limit(&erl, "DBENGINE JV2: wrong last time on-disk (%ld - %ld, now %ld), "
+ "fixing last time to now",
+ first_time_s, last_time_s, now_s);
+ last_time_s = now_s;
+ }
+
+ if (unlikely(first_time_s > last_time_s)) {
+ error_limit_static_global_var(erl, 1, 0);
+ error_limit(&erl, "DBENGINE JV2: wrong first time on-disk (%ld - %ld, now %ld), "
+ "fixing first time to last time",
+ first_time_s, last_time_s, now_s);
+
+ first_time_s = last_time_s;
+ }
+
+ if (unlikely(first_time_s == 0 || last_time_s == 0)) {
+ error_limit_static_global_var(erl, 1, 0);
+ error_limit(&erl, "DBENGINE JV2: zero on-disk timestamps (%ld - %ld, now %ld), "
+ "using them as-is",
+ first_time_s, last_time_s, now_s);
+ }
+
+ bool added = false;
+ METRIC *metric = mrg_metric_get_and_acquire(main_mrg, uuid, (Word_t) ctx);
+ if (!metric) {
+ MRG_ENTRY entry = {
+ .section = (Word_t) ctx,
+ .first_time_s = first_time_s,
+ .last_time_s = last_time_s,
+ .latest_update_every_s = update_every_s
+ };
+ uuid_copy(entry.uuid, *uuid);
+ metric = mrg_metric_add_and_acquire(main_mrg, entry, &added);
+ }
+
+ if (likely(!added))
+ mrg_metric_expand_retention(main_mrg, metric, first_time_s, last_time_s, update_every_s);
+
+ mrg_metric_release(main_mrg, metric);
+}
+
+static void after_extent_write_journalfile_v1_io(uv_fs_t* req)
{
- struct generic_io_descriptor *io_descr = req->data;
- struct rrdengine_worker_config* wc = req->loop->data;
- struct rrdengine_instance *ctx = wc->ctx;
+ worker_is_busy(RRDENG_FLUSH_TRANSACTION_BUFFER_CB);
+
+ WAL *wal = req->data;
+ struct generic_io_descriptor *io_descr = &wal->io_descr;
+ struct rrdengine_instance *ctx = io_descr->ctx;
debug(D_RRDENGINE, "%s: Journal block was written to disk.", __func__);
if (req->result < 0) {
- ++ctx->stats.io_errors;
- rrd_stat_atomic_add(&global_io_errors, 1);
- error("%s: uv_fs_write: %s", __func__, uv_strerror((int)req->result));
+ ctx_io_error(ctx);
+ error("DBENGINE: %s: uv_fs_write: %s", __func__, uv_strerror((int)req->result));
} else {
debug(D_RRDENGINE, "%s: Journal block was written to disk.", __func__);
}
uv_fs_req_cleanup(req);
- posix_memfree(io_descr->buf);
- freez(io_descr);
+ wal_release(wal);
+
+ __atomic_sub_fetch(&ctx->atomic.extents_currently_being_flushed, 1, __ATOMIC_RELAXED);
+
+ worker_is_idle();
}
/* Careful to always call this before creating a new journal file */
-void wal_flush_transaction_buffer(struct rrdengine_worker_config* wc)
+void journalfile_v1_extent_write(struct rrdengine_instance *ctx, struct rrdengine_datafile *datafile, WAL *wal, uv_loop_t *loop)
{
- struct rrdengine_instance *ctx = wc->ctx;
int ret;
struct generic_io_descriptor *io_descr;
- unsigned pos, size;
- struct rrdengine_journalfile *journalfile;
+ struct rrdengine_journalfile *journalfile = datafile->journalfile;
- if (unlikely(NULL == ctx->commit_log.buf || 0 == ctx->commit_log.buf_pos)) {
- return;
- }
- /* care with outstanding transactions when switching journal files */
- journalfile = ctx->datafiles.last->journalfile;
-
- io_descr = mallocz(sizeof(*io_descr));
- pos = ctx->commit_log.buf_pos;
- size = ctx->commit_log.buf_size;
- if (pos < size) {
+ io_descr = &wal->io_descr;
+ io_descr->ctx = ctx;
+ if (wal->size < wal->buf_size) {
/* simulate an empty transaction to skip the rest of the block */
- *(uint8_t *) (ctx->commit_log.buf + pos) = STORE_PADDING;
+ *(uint8_t *) (wal->buf + wal->size) = STORE_PADDING;
}
- io_descr->buf = ctx->commit_log.buf;
- io_descr->bytes = size;
- io_descr->pos = journalfile->pos;
- io_descr->req.data = io_descr;
+ io_descr->buf = wal->buf;
+ io_descr->bytes = wal->buf_size;
+
+ netdata_spinlock_lock(&journalfile->unsafe.spinlock);
+ io_descr->pos = journalfile->unsafe.pos;
+ journalfile->unsafe.pos += wal->buf_size;
+ netdata_spinlock_unlock(&journalfile->unsafe.spinlock);
+
+ io_descr->req.data = wal;
+ io_descr->data = journalfile;
io_descr->completion = NULL;
- io_descr->iov = uv_buf_init((void *)io_descr->buf, size);
- ret = uv_fs_write(wc->loop, &io_descr->req, journalfile->file, &io_descr->iov, 1,
- journalfile->pos, flush_transaction_buffer_cb);
+ io_descr->iov = uv_buf_init((void *)io_descr->buf, wal->buf_size);
+ ret = uv_fs_write(loop, &io_descr->req, journalfile->file, &io_descr->iov, 1,
+ (int64_t)io_descr->pos, after_extent_write_journalfile_v1_io);
fatal_assert(-1 != ret);
- journalfile->pos += RRDENG_BLOCK_SIZE;
- ctx->disk_space += RRDENG_BLOCK_SIZE;
- ctx->commit_log.buf = NULL;
- ctx->stats.io_write_bytes += RRDENG_BLOCK_SIZE;
- ++ctx->stats.io_write_requests;
+
+ ctx_current_disk_space_increase(ctx, wal->buf_size);
+ ctx_io_write_op_bytes(ctx, wal->buf_size);
}
-void * wal_get_transaction_buffer(struct rrdengine_worker_config* wc, unsigned size)
+void journalfile_v2_generate_path(struct rrdengine_datafile *datafile, char *str, size_t maxlen)
{
- struct rrdengine_instance *ctx = wc->ctx;
- int ret;
- unsigned buf_pos = 0, buf_size;
-
- fatal_assert(size);
- if (ctx->commit_log.buf) {
- unsigned remaining;
-
- buf_pos = ctx->commit_log.buf_pos;
- buf_size = ctx->commit_log.buf_size;
- remaining = buf_size - buf_pos;
- if (size > remaining) {
- /* we need a new buffer */
- wal_flush_transaction_buffer(wc);
+ (void) snprintfz(str, maxlen, "%s/" WALFILE_PREFIX RRDENG_FILE_NUMBER_PRINT_TMPL WALFILE_EXTENSION_V2,
+ datafile->ctx->config.dbfiles_path, datafile->tier, datafile->fileno);
+}
+
+void journalfile_v1_generate_path(struct rrdengine_datafile *datafile, char *str, size_t maxlen)
+{
+ (void) snprintfz(str, maxlen, "%s/" WALFILE_PREFIX RRDENG_FILE_NUMBER_PRINT_TMPL WALFILE_EXTENSION,
+ datafile->ctx->config.dbfiles_path, datafile->tier, datafile->fileno);
+}
+
+static struct journal_v2_header *journalfile_v2_mounted_data_get(struct rrdengine_journalfile *journalfile, size_t *data_size) {
+ struct journal_v2_header *j2_header = NULL;
+
+ netdata_spinlock_lock(&journalfile->mmap.spinlock);
+
+ if(!journalfile->mmap.data) {
+ journalfile->mmap.data = mmap(NULL, journalfile->mmap.size, PROT_READ, MAP_SHARED, journalfile->mmap.fd, 0);
+ if (journalfile->mmap.data == MAP_FAILED) {
+ internal_fatal(true, "DBENGINE: failed to re-mmap() journal file v2");
+ close(journalfile->mmap.fd);
+ journalfile->mmap.fd = -1;
+ journalfile->mmap.data = NULL;
+ journalfile->mmap.size = 0;
+
+ netdata_spinlock_lock(&journalfile->v2.spinlock);
+ journalfile->v2.flags &= ~(JOURNALFILE_FLAG_IS_AVAILABLE | JOURNALFILE_FLAG_IS_MOUNTED);
+ netdata_spinlock_unlock(&journalfile->v2.spinlock);
+
+ ctx_fs_error(journalfile->datafile->ctx);
+ }
+ else {
+ __atomic_add_fetch(&rrdeng_cache_efficiency_stats.journal_v2_mapped, 1, __ATOMIC_RELAXED);
+
+ madvise_dontfork(journalfile->mmap.data, journalfile->mmap.size);
+ madvise_dontdump(journalfile->mmap.data, journalfile->mmap.size);
+ madvise_random(journalfile->mmap.data, journalfile->mmap.size);
+ madvise_dontneed(journalfile->mmap.data, journalfile->mmap.size);
+
+ netdata_spinlock_lock(&journalfile->v2.spinlock);
+ journalfile->v2.flags |= JOURNALFILE_FLAG_IS_AVAILABLE | JOURNALFILE_FLAG_IS_MOUNTED;
+ netdata_spinlock_unlock(&journalfile->v2.spinlock);
+ }
+ }
+
+ if(journalfile->mmap.data) {
+ j2_header = journalfile->mmap.data;
+
+ if (data_size)
+ *data_size = journalfile->mmap.size;
+ }
+
+ netdata_spinlock_unlock(&journalfile->mmap.spinlock);
+
+ return j2_header;
+}
+
+static bool journalfile_v2_mounted_data_unmount(struct rrdengine_journalfile *journalfile, bool have_locks, bool wait) {
+ bool unmounted = false;
+
+ if(!have_locks) {
+ if(!wait) {
+ if (!netdata_spinlock_trylock(&journalfile->mmap.spinlock))
+ return false;
}
+ else
+ netdata_spinlock_lock(&journalfile->mmap.spinlock);
+
+ if(!wait) {
+ if(!netdata_spinlock_trylock(&journalfile->v2.spinlock)) {
+ netdata_spinlock_unlock(&journalfile->mmap.spinlock);
+ return false;
+ }
+ }
+ else
+ netdata_spinlock_lock(&journalfile->v2.spinlock);
}
- if (NULL == ctx->commit_log.buf) {
- buf_size = ALIGN_BYTES_CEILING(size);
- ret = posix_memalign((void *)&ctx->commit_log.buf, RRDFILE_ALIGNMENT, buf_size);
- if (unlikely(ret)) {
- fatal("posix_memalign:%s", strerror(ret));
+
+ if(!journalfile->v2.refcount) {
+ if(journalfile->mmap.data) {
+ if (munmap(journalfile->mmap.data, journalfile->mmap.size)) {
+ char path[RRDENG_PATH_MAX];
+ journalfile_v2_generate_path(journalfile->datafile, path, sizeof(path));
+ error("DBENGINE: failed to unmap index file '%s'", path);
+ internal_fatal(true, "DBENGINE: failed to unmap file '%s'", path);
+ ctx_fs_error(journalfile->datafile->ctx);
+ }
+ else {
+ __atomic_add_fetch(&rrdeng_cache_efficiency_stats.journal_v2_unmapped, 1, __ATOMIC_RELAXED);
+ journalfile->mmap.data = NULL;
+ journalfile->v2.flags &= ~JOURNALFILE_FLAG_IS_MOUNTED;
+ }
}
- memset(ctx->commit_log.buf, 0, buf_size);
- buf_pos = ctx->commit_log.buf_pos = 0;
- ctx->commit_log.buf_size = buf_size;
+
+ unmounted = true;
}
- ctx->commit_log.buf_pos += size;
- return ctx->commit_log.buf + buf_pos;
+ if(!have_locks) {
+ netdata_spinlock_unlock(&journalfile->v2.spinlock);
+ netdata_spinlock_unlock(&journalfile->mmap.spinlock);
+ }
+
+ return unmounted;
}
-void generate_journalfilepath(struct rrdengine_datafile *datafile, char *str, size_t maxlen)
-{
- (void) snprintfz(str, maxlen, "%s/" WALFILE_PREFIX RRDENG_FILE_NUMBER_PRINT_TMPL WALFILE_EXTENSION,
- datafile->ctx->dbfiles_path, datafile->tier, datafile->fileno);
+void journalfile_v2_data_unmount_cleanup(time_t now_s) {
+ // DO NOT WAIT ON ANY LOCK!!!
+
+ for(size_t tier = 0; tier < (size_t)storage_tiers ;tier++) {
+ struct rrdengine_instance *ctx = multidb_ctx[tier];
+ if(!ctx) continue;
+
+ struct rrdengine_datafile *datafile;
+ if(uv_rwlock_tryrdlock(&ctx->datafiles.rwlock) != 0)
+ continue;
+
+ for (datafile = ctx->datafiles.first; datafile; datafile = datafile->next) {
+ struct rrdengine_journalfile *journalfile = datafile->journalfile;
+
+ if(!netdata_spinlock_trylock(&journalfile->v2.spinlock))
+ continue;
+
+ bool unmount = false;
+ if (!journalfile->v2.refcount && (journalfile->v2.flags & JOURNALFILE_FLAG_IS_MOUNTED)) {
+ // this journal has no references and it is mounted
+
+ if (!journalfile->v2.not_needed_since_s)
+ journalfile->v2.not_needed_since_s = now_s;
+
+ else if (now_s - journalfile->v2.not_needed_since_s >= 120)
+ // 2 minutes have passed since last use
+ unmount = true;
+ }
+ netdata_spinlock_unlock(&journalfile->v2.spinlock);
+
+ if (unmount)
+ journalfile_v2_mounted_data_unmount(journalfile, false, false);
+ }
+ uv_rwlock_rdunlock(&ctx->datafiles.rwlock);
+ }
+}
+
+struct journal_v2_header *journalfile_v2_data_acquire(struct rrdengine_journalfile *journalfile, size_t *data_size, time_t wanted_first_time_s, time_t wanted_last_time_s) {
+ netdata_spinlock_lock(&journalfile->v2.spinlock);
+
+ bool has_data = (journalfile->v2.flags & JOURNALFILE_FLAG_IS_AVAILABLE);
+ bool is_mounted = (journalfile->v2.flags & JOURNALFILE_FLAG_IS_MOUNTED);
+ bool do_we_need_it = false;
+
+ if(has_data) {
+ if (!wanted_first_time_s || !wanted_last_time_s ||
+ is_page_in_time_range(journalfile->v2.first_time_s, journalfile->v2.last_time_s,
+ wanted_first_time_s, wanted_last_time_s) == PAGE_IS_IN_RANGE) {
+
+ journalfile->v2.refcount++;
+
+ do_we_need_it = true;
+
+ if (!wanted_first_time_s && !wanted_last_time_s && !is_mounted)
+ journalfile->v2.flags |= JOURNALFILE_FLAG_MOUNTED_FOR_RETENTION;
+ else
+ journalfile->v2.flags &= ~JOURNALFILE_FLAG_MOUNTED_FOR_RETENTION;
+
+ }
+ }
+ netdata_spinlock_unlock(&journalfile->v2.spinlock);
+
+ if(do_we_need_it)
+ return journalfile_v2_mounted_data_get(journalfile, data_size);
+
+ return NULL;
+}
+
+void journalfile_v2_data_release(struct rrdengine_journalfile *journalfile) {
+ netdata_spinlock_lock(&journalfile->v2.spinlock);
+
+ internal_fatal(!journalfile->mmap.data, "trying to release a journalfile without data");
+ internal_fatal(journalfile->v2.refcount < 1, "trying to release a non-acquired journalfile");
+
+ bool unmount = false;
+
+ journalfile->v2.refcount--;
+
+ if(journalfile->v2.refcount == 0) {
+ journalfile->v2.not_needed_since_s = 0;
+
+ if(journalfile->v2.flags & JOURNALFILE_FLAG_MOUNTED_FOR_RETENTION)
+ unmount = true;
+ }
+ netdata_spinlock_unlock(&journalfile->v2.spinlock);
+
+ if(unmount)
+ journalfile_v2_mounted_data_unmount(journalfile, false, true);
+}
+
+bool journalfile_v2_data_available(struct rrdengine_journalfile *journalfile) {
+
+ netdata_spinlock_lock(&journalfile->v2.spinlock);
+ bool has_data = (journalfile->v2.flags & JOURNALFILE_FLAG_IS_AVAILABLE);
+ netdata_spinlock_unlock(&journalfile->v2.spinlock);
+
+ return has_data;
+}
+
+size_t journalfile_v2_data_size_get(struct rrdengine_journalfile *journalfile) {
+
+ netdata_spinlock_lock(&journalfile->mmap.spinlock);
+ size_t data_size = journalfile->mmap.size;
+ netdata_spinlock_unlock(&journalfile->mmap.spinlock);
+
+ return data_size;
+}
+
+void journalfile_v2_data_set(struct rrdengine_journalfile *journalfile, int fd, void *journal_data, uint32_t journal_data_size) {
+ netdata_spinlock_lock(&journalfile->mmap.spinlock);
+ netdata_spinlock_lock(&journalfile->v2.spinlock);
+
+ internal_fatal(journalfile->mmap.fd != -1, "DBENGINE JOURNALFILE: trying to re-set journal fd");
+ internal_fatal(journalfile->mmap.data, "DBENGINE JOURNALFILE: trying to re-set journal_data");
+ internal_fatal(journalfile->v2.refcount, "DBENGINE JOURNALFILE: trying to re-set journal_data of referenced journalfile");
+
+ journalfile->mmap.fd = fd;
+ journalfile->mmap.data = journal_data;
+ journalfile->mmap.size = journal_data_size;
+ journalfile->v2.not_needed_since_s = now_monotonic_sec();
+ journalfile->v2.flags |= JOURNALFILE_FLAG_IS_AVAILABLE | JOURNALFILE_FLAG_IS_MOUNTED;
+
+ struct journal_v2_header *j2_header = journalfile->mmap.data;
+ journalfile->v2.first_time_s = (time_t)(j2_header->start_time_ut / USEC_PER_SEC);
+ journalfile->v2.last_time_s = (time_t)(j2_header->end_time_ut / USEC_PER_SEC);
+
+ journalfile_v2_mounted_data_unmount(journalfile, true, true);
+
+ netdata_spinlock_unlock(&journalfile->v2.spinlock);
+ netdata_spinlock_unlock(&journalfile->mmap.spinlock);
+}
+
+static void journalfile_v2_data_unmap_permanently(struct rrdengine_journalfile *journalfile) {
+ bool has_references = false;
+
+ do {
+ if (has_references)
+ sleep_usec(10 * USEC_PER_MS);
+
+ netdata_spinlock_lock(&journalfile->mmap.spinlock);
+ netdata_spinlock_lock(&journalfile->v2.spinlock);
+
+ if(journalfile_v2_mounted_data_unmount(journalfile, true, true)) {
+ if(journalfile->mmap.fd != -1)
+ close(journalfile->mmap.fd);
+
+ journalfile->mmap.fd = -1;
+ journalfile->mmap.data = NULL;
+ journalfile->mmap.size = 0;
+ journalfile->v2.first_time_s = 0;
+ journalfile->v2.last_time_s = 0;
+ journalfile->v2.flags = 0;
+ }
+ else {
+ has_references = true;
+ internal_error(true, "DBENGINE JOURNALFILE: waiting for journalfile to be available to unmap...");
+ }
+
+ netdata_spinlock_unlock(&journalfile->v2.spinlock);
+ netdata_spinlock_unlock(&journalfile->mmap.spinlock);
+
+ } while(has_references);
}
-void journalfile_init(struct rrdengine_journalfile *journalfile, struct rrdengine_datafile *datafile)
+struct rrdengine_journalfile *journalfile_alloc_and_init(struct rrdengine_datafile *datafile)
{
- journalfile->file = (uv_file)0;
- journalfile->pos = 0;
+ struct rrdengine_journalfile *journalfile = callocz(1, sizeof(struct rrdengine_journalfile));
journalfile->datafile = datafile;
+ netdata_spinlock_init(&journalfile->mmap.spinlock);
+ netdata_spinlock_init(&journalfile->v2.spinlock);
+ netdata_spinlock_init(&journalfile->unsafe.spinlock);
+ journalfile->mmap.fd = -1;
+ datafile->journalfile = journalfile;
+ return journalfile;
}
-int close_journal_file(struct rrdengine_journalfile *journalfile, struct rrdengine_datafile *datafile)
+static int close_uv_file(struct rrdengine_datafile *datafile, uv_file file)
{
- struct rrdengine_instance *ctx = datafile->ctx;
- uv_fs_t req;
int ret;
char path[RRDENG_PATH_MAX];
- generate_journalfilepath(datafile, path, sizeof(path));
-
- ret = uv_fs_close(NULL, &req, journalfile->file, NULL);
+ uv_fs_t req;
+ ret = uv_fs_close(NULL, &req, file, NULL);
if (ret < 0) {
- error("uv_fs_close(%s): %s", path, uv_strerror(ret));
- ++ctx->stats.fs_errors;
- rrd_stat_atomic_add(&global_fs_errors, 1);
+ journalfile_v1_generate_path(datafile, path, sizeof(path));
+ error("DBENGINE: uv_fs_close(%s): %s", path, uv_strerror(ret));
+ ctx_fs_error(datafile->ctx);
}
uv_fs_req_cleanup(&req);
-
return ret;
}
-int unlink_journal_file(struct rrdengine_journalfile *journalfile)
+int journalfile_close(struct rrdengine_journalfile *journalfile, struct rrdengine_datafile *datafile)
+{
+ if(journalfile_v2_data_available(journalfile)) {
+ journalfile_v2_data_unmap_permanently(journalfile);
+ return 0;
+ }
+
+ return close_uv_file(datafile, journalfile->file);
+}
+
+int journalfile_unlink(struct rrdengine_journalfile *journalfile)
{
struct rrdengine_datafile *datafile = journalfile->datafile;
struct rrdengine_instance *ctx = datafile->ctx;
@@ -134,60 +426,65 @@ int unlink_journal_file(struct rrdengine_journalfile *journalfile)
int ret;
char path[RRDENG_PATH_MAX];
- generate_journalfilepath(datafile, path, sizeof(path));
+ journalfile_v1_generate_path(datafile, path, sizeof(path));
ret = uv_fs_unlink(NULL, &req, path, NULL);
if (ret < 0) {
- error("uv_fs_fsunlink(%s): %s", path, uv_strerror(ret));
- ++ctx->stats.fs_errors;
- rrd_stat_atomic_add(&global_fs_errors, 1);
+ error("DBENGINE: uv_fs_fsunlink(%s): %s", path, uv_strerror(ret));
+ ctx_fs_error(ctx);
}
uv_fs_req_cleanup(&req);
- ++ctx->stats.journalfile_deletions;
+ __atomic_add_fetch(&ctx->stats.journalfile_deletions, 1, __ATOMIC_RELAXED);
return ret;
}
-int destroy_journal_file(struct rrdengine_journalfile *journalfile, struct rrdengine_datafile *datafile)
+int journalfile_destroy_unsafe(struct rrdengine_journalfile *journalfile, struct rrdengine_datafile *datafile)
{
struct rrdengine_instance *ctx = datafile->ctx;
uv_fs_t req;
int ret;
char path[RRDENG_PATH_MAX];
+ char path_v2[RRDENG_PATH_MAX];
- generate_journalfilepath(datafile, path, sizeof(path));
+ journalfile_v1_generate_path(datafile, path, sizeof(path));
+ journalfile_v2_generate_path(datafile, path_v2, sizeof(path));
+ if (journalfile->file) {
ret = uv_fs_ftruncate(NULL, &req, journalfile->file, 0, NULL);
if (ret < 0) {
- error("uv_fs_ftruncate(%s): %s", path, uv_strerror(ret));
- ++ctx->stats.fs_errors;
- rrd_stat_atomic_add(&global_fs_errors, 1);
+ error("DBENGINE: uv_fs_ftruncate(%s): %s", path, uv_strerror(ret));
+ ctx_fs_error(ctx);
}
uv_fs_req_cleanup(&req);
+ (void) close_uv_file(datafile, journalfile->file);
+ }
- ret = uv_fs_close(NULL, &req, journalfile->file, NULL);
+ // This is the new journal v2 index file
+ ret = uv_fs_unlink(NULL, &req, path_v2, NULL);
if (ret < 0) {
- error("uv_fs_close(%s): %s", path, uv_strerror(ret));
- ++ctx->stats.fs_errors;
- rrd_stat_atomic_add(&global_fs_errors, 1);
+ error("DBENGINE: uv_fs_fsunlink(%s): %s", path, uv_strerror(ret));
+ ctx_fs_error(ctx);
}
uv_fs_req_cleanup(&req);
ret = uv_fs_unlink(NULL, &req, path, NULL);
if (ret < 0) {
- error("uv_fs_fsunlink(%s): %s", path, uv_strerror(ret));
- ++ctx->stats.fs_errors;
- rrd_stat_atomic_add(&global_fs_errors, 1);
+ error("DBENGINE: uv_fs_fsunlink(%s): %s", path, uv_strerror(ret));
+ ctx_fs_error(ctx);
}
uv_fs_req_cleanup(&req);
- ++ctx->stats.journalfile_deletions;
+ __atomic_add_fetch(&ctx->stats.journalfile_deletions, 2, __ATOMIC_RELAXED);
+
+ if(journalfile_v2_data_available(journalfile))
+ journalfile_v2_data_unmap_permanently(journalfile);
return ret;
}
-int create_journal_file(struct rrdengine_journalfile *journalfile, struct rrdengine_datafile *datafile)
+int journalfile_create(struct rrdengine_journalfile *journalfile, struct rrdengine_datafile *datafile)
{
struct rrdengine_instance *ctx = datafile->ctx;
uv_fs_t req;
@@ -197,19 +494,18 @@ int create_journal_file(struct rrdengine_journalfile *journalfile, struct rrdeng
uv_buf_t iov;
char path[RRDENG_PATH_MAX];
- generate_journalfilepath(datafile, path, sizeof(path));
- fd = open_file_direct_io(path, O_CREAT | O_RDWR | O_TRUNC, &file);
+ journalfile_v1_generate_path(datafile, path, sizeof(path));
+ fd = open_file_for_io(path, O_CREAT | O_RDWR | O_TRUNC, &file, use_direct_io);
if (fd < 0) {
- ++ctx->stats.fs_errors;
- rrd_stat_atomic_add(&global_fs_errors, 1);
+ ctx_fs_error(ctx);
return fd;
}
journalfile->file = file;
- ++ctx->stats.journalfile_creations;
+ __atomic_add_fetch(&ctx->stats.journalfile_creations, 1, __ATOMIC_RELAXED);
ret = posix_memalign((void *)&superblock, RRDFILE_ALIGNMENT, sizeof(*superblock));
if (unlikely(ret)) {
- fatal("posix_memalign:%s", strerror(ret));
+ fatal("DBENGINE: posix_memalign:%s", strerror(ret));
}
memset(superblock, 0, sizeof(*superblock));
(void) strncpy(superblock->magic_number, RRDENG_JF_MAGIC, RRDENG_MAGIC_SZ);
@@ -220,25 +516,24 @@ int create_journal_file(struct rrdengine_journalfile *journalfile, struct rrdeng
ret = uv_fs_write(NULL, &req, file, &iov, 1, 0, NULL);
if (ret < 0) {
fatal_assert(req.result < 0);
- error("uv_fs_write: %s", uv_strerror(ret));
- ++ctx->stats.io_errors;
- rrd_stat_atomic_add(&global_io_errors, 1);
+ error("DBENGINE: uv_fs_write: %s", uv_strerror(ret));
+ ctx_io_error(ctx);
}
uv_fs_req_cleanup(&req);
posix_memfree(superblock);
if (ret < 0) {
- destroy_journal_file(journalfile, datafile);
+ journalfile_destroy_unsafe(journalfile, datafile);
return ret;
}
- journalfile->pos = sizeof(*superblock);
- ctx->stats.io_write_bytes += sizeof(*superblock);
- ++ctx->stats.io_write_requests;
+ journalfile->unsafe.pos = sizeof(*superblock);
+
+ ctx_io_write_op_bytes(ctx, sizeof(*superblock));
return 0;
}
-static int check_journal_file_superblock(uv_file file)
+static int journalfile_check_superblock(uv_file file)
{
int ret;
struct rrdeng_jf_sb *superblock;
@@ -247,13 +542,13 @@ static int check_journal_file_superblock(uv_file file)
ret = posix_memalign((void *)&superblock, RRDFILE_ALIGNMENT, sizeof(*superblock));
if (unlikely(ret)) {
- fatal("posix_memalign:%s", strerror(ret));
+ fatal("DBENGINE: posix_memalign:%s", strerror(ret));
}
iov = uv_buf_init((void *)superblock, sizeof(*superblock));
ret = uv_fs_read(NULL, &req, file, &iov, 1, 0, NULL);
if (ret < 0) {
- error("uv_fs_read: %s", uv_strerror(ret));
+ error("DBENGINE: uv_fs_read: %s", uv_strerror(ret));
uv_fs_req_cleanup(&req);
goto error;
}
@@ -262,7 +557,7 @@ static int check_journal_file_superblock(uv_file file)
if (strncmp(superblock->magic_number, RRDENG_JF_MAGIC, RRDENG_MAGIC_SZ) ||
strncmp(superblock->version, RRDENG_JF_VER, RRDENG_VER_SZ)) {
- error("File has invalid superblock.");
+ error("DBENGINE: File has invalid superblock.");
ret = UV_EINVAL;
} else {
ret = 0;
@@ -272,15 +567,10 @@ static int check_journal_file_superblock(uv_file file)
return ret;
}
-static void restore_extent_metadata(struct rrdengine_instance *ctx, struct rrdengine_journalfile *journalfile,
- void *buf, unsigned max_size)
+static void journalfile_restore_extent_metadata(struct rrdengine_instance *ctx, struct rrdengine_journalfile *journalfile, void *buf, unsigned max_size)
{
static BITMAP256 page_error_map;
- struct page_cache *pg_cache = &ctx->pg_cache;
- unsigned i, count, payload_length, descr_size, valid_pages;
- struct rrdeng_page_descr *descr;
- struct extent_info *extent;
- /* persistent structures */
+ unsigned i, count, payload_length, descr_size;
struct rrdeng_jf_store_data *jf_metric_data;
jf_metric_data = buf;
@@ -288,117 +578,65 @@ static void restore_extent_metadata(struct rrdengine_instance *ctx, struct rrden
descr_size = sizeof(*jf_metric_data->descr) * count;
payload_length = sizeof(*jf_metric_data) + descr_size;
if (payload_length > max_size) {
- error("Corrupted transaction payload.");
+ error("DBENGINE: corrupted transaction payload.");
return;
}
- extent = mallocz(sizeof(*extent) + count * sizeof(extent->pages[0]));
- extent->offset = jf_metric_data->extent_offset;
- extent->size = jf_metric_data->extent_size;
- extent->datafile = journalfile->datafile;
- extent->next = NULL;
-
- for (i = 0, valid_pages = 0 ; i < count ; ++i) {
+ time_t now_s = max_acceptable_collected_time();
+ for (i = 0; i < count ; ++i) {
uuid_t *temp_id;
- Pvoid_t *PValue;
- struct pg_cache_page_index *page_index = NULL;
uint8_t page_type = jf_metric_data->descr[i].type;
if (page_type > PAGE_TYPE_MAX) {
if (!bitmap256_get_bit(&page_error_map, page_type)) {
- error("Unknown page type %d encountered.", page_type);
+ error("DBENGINE: unknown page type %d encountered.", page_type);
bitmap256_set_bit(&page_error_map, page_type, 1);
}
continue;
}
- uint64_t start_time_ut = jf_metric_data->descr[i].start_time_ut;
- uint64_t end_time_ut = jf_metric_data->descr[i].end_time_ut;
- size_t entries = jf_metric_data->descr[i].page_length / page_type_size[page_type];
- time_t update_every_s = (entries > 1) ? ((end_time_ut - start_time_ut) / USEC_PER_SEC / (entries - 1)) : 0;
-
- if (unlikely(start_time_ut > end_time_ut)) {
- ctx->load_errors[LOAD_ERRORS_PAGE_FLIPPED_TIME].counter++;
- if(ctx->load_errors[LOAD_ERRORS_PAGE_FLIPPED_TIME].latest_end_time_ut < end_time_ut)
- ctx->load_errors[LOAD_ERRORS_PAGE_FLIPPED_TIME].latest_end_time_ut = end_time_ut;
- continue;
- }
- if (unlikely(start_time_ut == end_time_ut && entries != 1)) {
- ctx->load_errors[LOAD_ERRORS_PAGE_EQUAL_TIME].counter++;
- if(ctx->load_errors[LOAD_ERRORS_PAGE_EQUAL_TIME].latest_end_time_ut < end_time_ut)
- ctx->load_errors[LOAD_ERRORS_PAGE_EQUAL_TIME].latest_end_time_ut = end_time_ut;
- continue;
- }
-
- if (unlikely(!entries)) {
- ctx->load_errors[LOAD_ERRORS_PAGE_ZERO_ENTRIES].counter++;
- if(ctx->load_errors[LOAD_ERRORS_PAGE_ZERO_ENTRIES].latest_end_time_ut < end_time_ut)
- ctx->load_errors[LOAD_ERRORS_PAGE_ZERO_ENTRIES].latest_end_time_ut = end_time_ut;
- continue;
- }
+ temp_id = (uuid_t *)jf_metric_data->descr[i].uuid;
+ METRIC *metric = mrg_metric_get_and_acquire(main_mrg, temp_id, (Word_t) ctx);
- if(entries > 1 && update_every_s == 0) {
- ctx->load_errors[LOAD_ERRORS_PAGE_UPDATE_ZERO].counter++;
- if(ctx->load_errors[LOAD_ERRORS_PAGE_UPDATE_ZERO].latest_end_time_ut < end_time_ut)
- ctx->load_errors[LOAD_ERRORS_PAGE_UPDATE_ZERO].latest_end_time_ut = end_time_ut;
- continue;
- }
+ struct rrdeng_extent_page_descr *descr = &jf_metric_data->descr[i];
+ VALIDATED_PAGE_DESCRIPTOR vd = validate_extent_page_descr(
+ descr, now_s,
+ (metric) ? mrg_metric_get_update_every_s(main_mrg, metric) : 0,
+ false);
- if(start_time_ut + update_every_s * USEC_PER_SEC * (entries - 1) != end_time_ut) {
- ctx->load_errors[LOAD_ERRORS_PAGE_FLEXY_TIME].counter++;
- if(ctx->load_errors[LOAD_ERRORS_PAGE_FLEXY_TIME].latest_end_time_ut < end_time_ut)
- ctx->load_errors[LOAD_ERRORS_PAGE_FLEXY_TIME].latest_end_time_ut = end_time_ut;
+ if(!vd.is_valid) {
+ if(metric)
+ mrg_metric_release(main_mrg, metric);
- // let this be
- // end_time_ut = start_time_ut + update_every_s * USEC_PER_SEC * (entries - 1);
+ continue;
}
- temp_id = (uuid_t *)jf_metric_data->descr[i].uuid;
-
- uv_rwlock_rdlock(&pg_cache->metrics_index.lock);
- PValue = JudyHSGet(pg_cache->metrics_index.JudyHS_array, temp_id, sizeof(uuid_t));
- if (likely(NULL != PValue)) {
- page_index = *PValue;
- }
- uv_rwlock_rdunlock(&pg_cache->metrics_index.lock);
- if (NULL == PValue) {
- /* First time we see the UUID */
- uv_rwlock_wrlock(&pg_cache->metrics_index.lock);
- PValue = JudyHSIns(&pg_cache->metrics_index.JudyHS_array, temp_id, sizeof(uuid_t), PJE0);
- fatal_assert(NULL == *PValue); /* TODO: figure out concurrency model */
- *PValue = page_index = create_page_index(temp_id, ctx);
- page_index->prev = pg_cache->metrics_index.last_page_index;
- pg_cache->metrics_index.last_page_index = page_index;
- uv_rwlock_wrunlock(&pg_cache->metrics_index.lock);
+ bool update_metric_time = true;
+ if (!metric) {
+ MRG_ENTRY entry = {
+ .section = (Word_t)ctx,
+ .first_time_s = vd.start_time_s,
+ .last_time_s = vd.end_time_s,
+ .latest_update_every_s = vd.update_every_s,
+ };
+ uuid_copy(entry.uuid, *temp_id);
+
+ bool added;
+ metric = mrg_metric_add_and_acquire(main_mrg, entry, &added);
+ if(added)
+ update_metric_time = false;
}
+ Word_t metric_id = mrg_metric_id(main_mrg, metric);
- descr = pg_cache_create_descr();
- descr->page_length = jf_metric_data->descr[i].page_length;
- descr->start_time_ut = start_time_ut;
- descr->end_time_ut = end_time_ut;
- descr->update_every_s = (update_every_s > 0) ? (uint32_t)update_every_s : (page_index->latest_update_every_s);
- descr->id = &page_index->id;
- descr->extent = extent;
- descr->type = page_type;
- extent->pages[valid_pages++] = descr;
- pg_cache_insert(ctx, page_index, descr);
+ if (update_metric_time)
+ mrg_metric_expand_retention(main_mrg, metric, vd.start_time_s, vd.end_time_s, vd.update_every_s);
- if(page_index->latest_time_ut == descr->end_time_ut)
- page_index->latest_update_every_s = descr->update_every_s;
+ pgc_open_add_hot_page(
+ (Word_t)ctx, metric_id, vd.start_time_s, vd.end_time_s, vd.update_every_s,
+ journalfile->datafile,
+ jf_metric_data->extent_offset, jf_metric_data->extent_size, jf_metric_data->descr[i].page_length);
- if(descr->update_every_s == 0)
- fatal(
- "DBENGINE: page descriptor update every is zero, end_time_ut = %llu, start_time_ut = %llu, entries = %zu",
- (unsigned long long)end_time_ut, (unsigned long long)start_time_ut, entries);
- }
-
- extent->number_of_pages = valid_pages;
-
- if (likely(valid_pages))
- df_extent_insert(extent);
- else {
- freez(extent);
- ctx->load_errors[LOAD_ERRORS_DROPPED_EXTENT].counter++;
+ mrg_metric_release(main_mrg, metric);
}
}
@@ -407,8 +645,8 @@ static void restore_extent_metadata(struct rrdengine_instance *ctx, struct rrden
* Sets id to the current transaction id or to 0 if unknown.
* Returns size of transaction record or 0 for unknown size.
*/
-static unsigned replay_transaction(struct rrdengine_instance *ctx, struct rrdengine_journalfile *journalfile,
- void *buf, uint64_t *id, unsigned max_size)
+static unsigned journalfile_replay_transaction(struct rrdengine_instance *ctx, struct rrdengine_journalfile *journalfile,
+ void *buf, uint64_t *id, unsigned max_size)
{
unsigned payload_length, size_bytes;
int ret;
@@ -424,14 +662,14 @@ static unsigned replay_transaction(struct rrdengine_instance *ctx, struct rrdeng
return 0;
}
if (sizeof(*jf_header) > max_size) {
- error("Corrupted transaction record, skipping.");
+ error("DBENGINE: corrupted transaction record, skipping.");
return 0;
}
*id = jf_header->id;
payload_length = jf_header->payload_length;
size_bytes = sizeof(*jf_header) + payload_length + sizeof(*jf_trailer);
if (size_bytes > max_size) {
- error("Corrupted transaction record, skipping.");
+ error("DBENGINE: corrupted transaction record, skipping.");
return 0;
}
jf_trailer = buf + sizeof(*jf_header) + payload_length;
@@ -440,16 +678,16 @@ static unsigned replay_transaction(struct rrdengine_instance *ctx, struct rrdeng
ret = crc32cmp(jf_trailer->checksum, crc);
debug(D_RRDENGINE, "Transaction %"PRIu64" was read from disk. CRC32 check: %s", *id, ret ? "FAILED" : "SUCCEEDED");
if (unlikely(ret)) {
- error("Transaction %"PRIu64" was read from disk. CRC32 check: FAILED", *id);
+ error("DBENGINE: transaction %"PRIu64" was read from disk. CRC32 check: FAILED", *id);
return size_bytes;
}
switch (jf_header->type) {
case STORE_DATA:
debug(D_RRDENGINE, "Replaying transaction %"PRIu64"", jf_header->id);
- restore_extent_metadata(ctx, journalfile, buf + sizeof(*jf_header), payload_length);
+ journalfile_restore_extent_metadata(ctx, journalfile, buf + sizeof(*jf_header), payload_length);
break;
default:
- error("Unknown transaction type. Skipping record.");
+ error("DBENGINE: unknown transaction type, skipping record.");
break;
}
@@ -463,10 +701,10 @@ static unsigned replay_transaction(struct rrdengine_instance *ctx, struct rrdeng
* Page cache must already be initialized.
* Returns the maximum transaction id it discovered.
*/
-static uint64_t iterate_transactions(struct rrdengine_instance *ctx, struct rrdengine_journalfile *journalfile)
+static uint64_t journalfile_iterate_transactions(struct rrdengine_instance *ctx, struct rrdengine_journalfile *journalfile)
{
uv_file file;
- uint64_t file_size;//, data_file_size;
+ uint64_t file_size;
int ret;
uint64_t pos, pos_i, max_id, id;
unsigned size_bytes;
@@ -475,39 +713,31 @@ static uint64_t iterate_transactions(struct rrdengine_instance *ctx, struct rrde
uv_fs_t req;
file = journalfile->file;
- file_size = journalfile->pos;
- //data_file_size = journalfile->datafile->pos; TODO: utilize this?
+ file_size = journalfile->unsafe.pos;
max_id = 1;
- bool journal_is_mmapped = (journalfile->data != NULL);
- if (unlikely(!journal_is_mmapped)) {
- ret = posix_memalign((void *)&buf, RRDFILE_ALIGNMENT, READAHEAD_BYTES);
- if (unlikely(ret))
- fatal("posix_memalign:%s", strerror(ret));
- }
- else
- buf = journalfile->data + sizeof(struct rrdeng_jf_sb);
- for (pos = sizeof(struct rrdeng_jf_sb) ; pos < file_size ; pos += READAHEAD_BYTES) {
+ ret = posix_memalign((void *)&buf, RRDFILE_ALIGNMENT, READAHEAD_BYTES);
+ if (unlikely(ret))
+ fatal("DBENGINE: posix_memalign:%s", strerror(ret));
+
+ for (pos = sizeof(struct rrdeng_jf_sb); pos < file_size; pos += READAHEAD_BYTES) {
size_bytes = MIN(READAHEAD_BYTES, file_size - pos);
- if (unlikely(!journal_is_mmapped)) {
- iov = uv_buf_init(buf, size_bytes);
- ret = uv_fs_read(NULL, &req, file, &iov, 1, pos, NULL);
- if (ret < 0) {
- error("uv_fs_read: pos=%" PRIu64 ", %s", pos, uv_strerror(ret));
- uv_fs_req_cleanup(&req);
- goto skip_file;
- }
- fatal_assert(req.result >= 0);
+ iov = uv_buf_init(buf, size_bytes);
+ ret = uv_fs_read(NULL, &req, file, &iov, 1, pos, NULL);
+ if (ret < 0) {
+ error("DBENGINE: uv_fs_read: pos=%" PRIu64 ", %s", pos, uv_strerror(ret));
uv_fs_req_cleanup(&req);
- ++ctx->stats.io_read_requests;
- ctx->stats.io_read_bytes += size_bytes;
+ goto skip_file;
}
+ fatal_assert(req.result >= 0);
+ uv_fs_req_cleanup(&req);
+ ctx_io_read_op_bytes(ctx, size_bytes);
- for (pos_i = 0 ; pos_i < size_bytes ; ) {
+ for (pos_i = 0; pos_i < size_bytes;) {
unsigned max_size;
max_size = pos + size_bytes - pos_i;
- ret = replay_transaction(ctx, journalfile, buf + pos_i, &id, max_size);
+ ret = journalfile_replay_transaction(ctx, journalfile, buf + pos_i, &id, max_size);
if (!ret) /* TODO: support transactions bigger than 4K */
/* unknown transaction size, move on to the next block */
pos_i = ALIGN_BYTES_FLOOR(pos_i + RRDENG_BLOCK_SIZE);
@@ -515,73 +745,722 @@ static uint64_t iterate_transactions(struct rrdengine_instance *ctx, struct rrde
pos_i += ret;
max_id = MAX(max_id, id);
}
- if (likely(journal_is_mmapped))
- buf += size_bytes;
}
skip_file:
- if (unlikely(!journal_is_mmapped))
- posix_memfree(buf);
+ posix_memfree(buf);
return max_id;
}
-int load_journal_file(struct rrdengine_instance *ctx, struct rrdengine_journalfile *journalfile,
- struct rrdengine_datafile *datafile)
+// Checks that the extent list checksum is valid
+static int journalfile_check_v2_extent_list (void *data_start, size_t file_size)
+{
+ UNUSED(file_size);
+ uLong crc;
+
+ struct journal_v2_header *j2_header = (void *) data_start;
+ struct journal_v2_block_trailer *journal_v2_trailer;
+
+ journal_v2_trailer = (struct journal_v2_block_trailer *) ((uint8_t *) data_start + j2_header->extent_trailer_offset);
+ crc = crc32(0L, Z_NULL, 0);
+ crc = crc32(crc, (uint8_t *) data_start + j2_header->extent_offset, j2_header->extent_count * sizeof(struct journal_extent_list));
+ if (unlikely(crc32cmp(journal_v2_trailer->checksum, crc))) {
+ error("DBENGINE: extent list CRC32 check: FAILED");
+ return 1;
+ }
+
+ return 0;
+}
+
+// Checks that the metric list (UUIDs) checksum is valid
+static int journalfile_check_v2_metric_list(void *data_start, size_t file_size)
+{
+ UNUSED(file_size);
+ uLong crc;
+
+ struct journal_v2_header *j2_header = (void *) data_start;
+ struct journal_v2_block_trailer *journal_v2_trailer;
+
+ journal_v2_trailer = (struct journal_v2_block_trailer *) ((uint8_t *) data_start + j2_header->metric_trailer_offset);
+ crc = crc32(0L, Z_NULL, 0);
+ crc = crc32(crc, (uint8_t *) data_start + j2_header->metric_offset, j2_header->metric_count * sizeof(struct journal_metric_list));
+ if (unlikely(crc32cmp(journal_v2_trailer->checksum, crc))) {
+ error("DBENGINE: metric list CRC32 check: FAILED");
+ return 1;
+ }
+ return 0;
+}
+
+//
+// Return
+// 0 Ok
+// 1 Invalid
+// 2 Force rebuild
+// 3 skip
+
+static int journalfile_v2_validate(void *data_start, size_t journal_v2_file_size, size_t journal_v1_file_size)
+{
+ int rc;
+ uLong crc;
+
+ struct journal_v2_header *j2_header = (void *) data_start;
+ struct journal_v2_block_trailer *journal_v2_trailer;
+
+ if (j2_header->magic == JOURVAL_V2_REBUILD_MAGIC)
+ return 2;
+
+ if (j2_header->magic == JOURVAL_V2_SKIP_MAGIC)
+ return 3;
+
+ // Magic failure
+ if (j2_header->magic != JOURVAL_V2_MAGIC)
+ return 1;
+
+ if (j2_header->journal_v2_file_size != journal_v2_file_size)
+ return 1;
+
+ if (journal_v1_file_size && j2_header->journal_v1_file_size != journal_v1_file_size)
+ return 1;
+
+ journal_v2_trailer = (struct journal_v2_block_trailer *) ((uint8_t *) data_start + journal_v2_file_size - sizeof(*journal_v2_trailer));
+
+ crc = crc32(0L, Z_NULL, 0);
+ crc = crc32(crc, (void *) j2_header, sizeof(*j2_header));
+
+ rc = crc32cmp(journal_v2_trailer->checksum, crc);
+ if (unlikely(rc)) {
+ error("DBENGINE: file CRC32 check: FAILED");
+ return 1;
+ }
+
+ rc = journalfile_check_v2_extent_list(data_start, journal_v2_file_size);
+ if (rc) return 1;
+
+ rc = journalfile_check_v2_metric_list(data_start, journal_v2_file_size);
+ if (rc) return 1;
+
+ if (!db_engine_journal_check)
+ return 0;
+
+ // Verify complete UUID chain
+
+ struct journal_metric_list *metric = (void *) (data_start + j2_header->metric_offset);
+
+ unsigned verified = 0;
+ unsigned entries;
+ unsigned total_pages = 0;
+
+ info("DBENGINE: checking %u metrics that exist in the journal", j2_header->metric_count);
+ for (entries = 0; entries < j2_header->metric_count; entries++) {
+
+ char uuid_str[UUID_STR_LEN];
+ uuid_unparse_lower(metric->uuid, uuid_str);
+ struct journal_page_header *metric_list_header = (void *) (data_start + metric->page_offset);
+ struct journal_page_header local_metric_list_header = *metric_list_header;
+
+ local_metric_list_header.crc = JOURVAL_V2_MAGIC;
+
+ crc = crc32(0L, Z_NULL, 0);
+ crc = crc32(crc, (void *) &local_metric_list_header, sizeof(local_metric_list_header));
+ rc = crc32cmp(metric_list_header->checksum, crc);
+
+ if (!rc) {
+ struct journal_v2_block_trailer *journal_trailer =
+ (void *) data_start + metric->page_offset + sizeof(struct journal_page_header) + (metric_list_header->entries * sizeof(struct journal_page_list));
+
+ crc = crc32(0L, Z_NULL, 0);
+ crc = crc32(crc, (uint8_t *) metric_list_header + sizeof(struct journal_page_header), metric_list_header->entries * sizeof(struct journal_page_list));
+ rc = crc32cmp(journal_trailer->checksum, crc);
+ internal_error(rc, "DBENGINE: index %u : %s entries %u at offset %u verified, DATA CRC computed %lu, stored %u", entries, uuid_str, metric->entries, metric->page_offset,
+ crc, metric_list_header->crc);
+ if (!rc) {
+ total_pages += metric_list_header->entries;
+ verified++;
+ }
+ }
+
+ metric++;
+ if ((uint32_t)((uint8_t *) metric - (uint8_t *) data_start) > (uint32_t) journal_v2_file_size) {
+ info("DBENGINE: verification failed EOF reached -- total entries %u, verified %u", entries, verified);
+ return 1;
+ }
+ }
+
+ if (entries != verified) {
+ info("DBENGINE: verification failed -- total entries %u, verified %u", entries, verified);
+ return 1;
+ }
+ info("DBENGINE: verification succeeded -- total entries %u, verified %u (%u total pages)", entries, verified, total_pages);
+
+ return 0;
+}
+
+void journalfile_v2_populate_retention_to_mrg(struct rrdengine_instance *ctx, struct rrdengine_journalfile *journalfile) {
+ usec_t started_ut = now_monotonic_usec();
+
+ size_t data_size = 0;
+ struct journal_v2_header *j2_header = journalfile_v2_data_acquire(journalfile, &data_size, 0, 0);
+ if(!j2_header)
+ return;
+
+ uint8_t *data_start = (uint8_t *)j2_header;
+ uint32_t entries = j2_header->metric_count;
+
+ struct journal_metric_list *metric = (struct journal_metric_list *) (data_start + j2_header->metric_offset);
+ time_t header_start_time_s = (time_t) (j2_header->start_time_ut / USEC_PER_SEC);
+ time_t now_s = max_acceptable_collected_time();
+ for (size_t i=0; i < entries; i++) {
+ time_t start_time_s = header_start_time_s + metric->delta_start_s;
+ time_t end_time_s = header_start_time_s + metric->delta_end_s;
+ time_t update_every_s = (metric->entries > 1) ? ((end_time_s - start_time_s) / (entries - 1)) : 0;
+ update_metric_retention_and_granularity_by_uuid(
+ ctx, &metric->uuid, start_time_s, end_time_s, update_every_s, now_s);
+
+#ifdef NETDATA_INTERNAL_CHECKS
+ struct journal_page_header *metric_list_header = (void *) (data_start + metric->page_offset);
+ fatal_assert(uuid_compare(metric_list_header->uuid, metric->uuid) == 0);
+ fatal_assert(metric->entries == metric_list_header->entries);
+#endif
+ metric++;
+ }
+
+ journalfile_v2_data_release(journalfile);
+ usec_t ended_ut = now_monotonic_usec();
+
+ info("DBENGINE: journal v2 of tier %d, datafile %u populated, size: %0.2f MiB, metrics: %0.2f k, %0.2f ms"
+ , ctx->config.tier, journalfile->datafile->fileno
+ , (double)data_size / 1024 / 1024
+ , (double)entries / 1000
+ , ((double)(ended_ut - started_ut) / USEC_PER_MS)
+ );
+}
+
+int journalfile_v2_load(struct rrdengine_instance *ctx, struct rrdengine_journalfile *journalfile, struct rrdengine_datafile *datafile)
+{
+ int ret, fd;
+ char path_v1[RRDENG_PATH_MAX];
+ char path_v2[RRDENG_PATH_MAX];
+ struct stat statbuf;
+ size_t journal_v1_file_size = 0;
+ size_t journal_v2_file_size;
+
+ journalfile_v1_generate_path(datafile, path_v1, sizeof(path_v1));
+ ret = stat(path_v1, &statbuf);
+ if (!ret)
+ journal_v1_file_size = (uint32_t)statbuf.st_size;
+
+ journalfile_v2_generate_path(datafile, path_v2, sizeof(path_v2));
+ fd = open(path_v2, O_RDONLY);
+ if (fd < 0) {
+ if (errno == ENOENT)
+ return 1;
+ ctx_fs_error(ctx);
+ error("DBENGINE: failed to open '%s'", path_v2);
+ return 1;
+ }
+
+ ret = fstat(fd, &statbuf);
+ if (ret) {
+ error("DBENGINE: failed to get file information for '%s'", path_v2);
+ close(fd);
+ return 1;
+ }
+
+ journal_v2_file_size = (size_t)statbuf.st_size;
+
+ if (journal_v2_file_size < sizeof(struct journal_v2_header)) {
+ error_report("Invalid file %s. Not the expected size", path_v2);
+ close(fd);
+ return 1;
+ }
+
+ usec_t mmap_start_ut = now_monotonic_usec();
+ uint8_t *data_start = mmap(NULL, journal_v2_file_size, PROT_READ, MAP_SHARED, fd, 0);
+ if (data_start == MAP_FAILED) {
+ close(fd);
+ return 1;
+ }
+
+ info("DBENGINE: checking integrity of '%s'", path_v2);
+ usec_t validation_start_ut = now_monotonic_usec();
+ int rc = journalfile_v2_validate(data_start, journal_v2_file_size, journal_v1_file_size);
+ if (unlikely(rc)) {
+ if (rc == 2)
+ error_report("File %s needs to be rebuilt", path_v2);
+ else if (rc == 3)
+ error_report("File %s will be skipped", path_v2);
+ else
+ error_report("File %s is invalid and it will be rebuilt", path_v2);
+
+ if (unlikely(munmap(data_start, journal_v2_file_size)))
+ error("DBENGINE: failed to unmap '%s'", path_v2);
+
+ close(fd);
+ return rc;
+ }
+
+ struct journal_v2_header *j2_header = (void *) data_start;
+ uint32_t entries = j2_header->metric_count;
+
+ if (unlikely(!entries)) {
+ if (unlikely(munmap(data_start, journal_v2_file_size)))
+ error("DBENGINE: failed to unmap '%s'", path_v2);
+
+ close(fd);
+ return 1;
+ }
+
+ usec_t finished_ut = now_monotonic_usec();
+
+ info("DBENGINE: journal v2 '%s' loaded, size: %0.2f MiB, metrics: %0.2f k, "
+ "mmap: %0.2f ms, validate: %0.2f ms"
+ , path_v2
+ , (double)journal_v2_file_size / 1024 / 1024
+ , (double)entries / 1000
+ , ((double)(validation_start_ut - mmap_start_ut) / USEC_PER_MS)
+ , ((double)(finished_ut - validation_start_ut) / USEC_PER_MS)
+ );
+
+ // Initialize the journal file to be able to access the data
+ journalfile_v2_data_set(journalfile, fd, data_start, journal_v2_file_size);
+
+ ctx_current_disk_space_increase(ctx, journal_v2_file_size);
+
+ // File is OK load it
+ return 0;
+}
+
+struct journal_metric_list_to_sort {
+ struct jv2_metrics_info *metric_info;
+};
+
+static int journalfile_metric_compare (const void *item1, const void *item2)
+{
+ const struct jv2_metrics_info *metric1 = ((struct journal_metric_list_to_sort *) item1)->metric_info;
+ const struct jv2_metrics_info *metric2 = ((struct journal_metric_list_to_sort *) item2)->metric_info;
+
+ return uuid_compare(*(metric1->uuid), *(metric2->uuid));
+}
+
+
+// Write list of extents for the journalfile
+void *journalfile_v2_write_extent_list(Pvoid_t JudyL_extents_pos, void *data)
+{
+ Pvoid_t *PValue;
+ struct journal_extent_list *j2_extent_base = (void *) data;
+ struct jv2_extents_info *ext_info;
+
+ bool first = true;
+ Word_t pos = 0;
+ size_t count = 0;
+ while ((PValue = JudyLFirstThenNext(JudyL_extents_pos, &pos, &first))) {
+ ext_info = *PValue;
+ size_t index = ext_info->index;
+ j2_extent_base[index].file_index = 0;
+ j2_extent_base[index].datafile_offset = ext_info->pos;
+ j2_extent_base[index].datafile_size = ext_info->bytes;
+ j2_extent_base[index].pages = ext_info->number_of_pages;
+ count++;
+ }
+ return j2_extent_base + count;
+}
+
+static int journalfile_verify_space(struct journal_v2_header *j2_header, void *data, uint32_t bytes)
+{
+ if ((unsigned long)(((uint8_t *) data - (uint8_t *) j2_header->data) + bytes) > (j2_header->journal_v2_file_size - sizeof(struct journal_v2_block_trailer)))
+ return 1;
+
+ return 0;
+}
+
+void *journalfile_v2_write_metric_page(struct journal_v2_header *j2_header, void *data, struct jv2_metrics_info *metric_info, uint32_t pages_offset)
+{
+ struct journal_metric_list *metric = (void *) data;
+
+ if (journalfile_verify_space(j2_header, data, sizeof(*metric)))
+ return NULL;
+
+ uuid_copy(metric->uuid, *metric_info->uuid);
+ metric->entries = metric_info->number_of_pages;
+ metric->page_offset = pages_offset;
+ metric->delta_start_s = (uint32_t)(metric_info->first_time_s - (time_t)(j2_header->start_time_ut / USEC_PER_SEC));
+ metric->delta_end_s = (uint32_t)(metric_info->last_time_s - (time_t)(j2_header->start_time_ut / USEC_PER_SEC));
+
+ return ++metric;
+}
+
+void *journalfile_v2_write_data_page_header(struct journal_v2_header *j2_header __maybe_unused, void *data, struct jv2_metrics_info *metric_info, uint32_t uuid_offset)
+{
+ struct journal_page_header *data_page_header = (void *) data;
+ uLong crc;
+
+ uuid_copy(data_page_header->uuid, *metric_info->uuid);
+ data_page_header->entries = metric_info->number_of_pages;
+ data_page_header->uuid_offset = uuid_offset; // data header OFFSET poings to METRIC in the directory
+ data_page_header->crc = JOURVAL_V2_MAGIC;
+ crc = crc32(0L, Z_NULL, 0);
+ crc = crc32(crc, (void *) data_page_header, sizeof(*data_page_header));
+ crc32set(data_page_header->checksum, crc);
+ return ++data_page_header;
+}
+
+void *journalfile_v2_write_data_page_trailer(struct journal_v2_header *j2_header __maybe_unused, void *data, void *page_header)
+{
+ struct journal_page_header *data_page_header = (void *) page_header;
+ struct journal_v2_block_trailer *journal_trailer = (void *) data;
+ uLong crc;
+
+ crc = crc32(0L, Z_NULL, 0);
+ crc = crc32(crc, (uint8_t *) page_header + sizeof(struct journal_page_header), data_page_header->entries * sizeof(struct journal_page_list));
+ crc32set(journal_trailer->checksum, crc);
+ return ++journal_trailer;
+}
+
+void *journalfile_v2_write_data_page(struct journal_v2_header *j2_header, void *data, struct jv2_page_info *page_info)
+{
+ struct journal_page_list *data_page = data;
+
+ if (journalfile_verify_space(j2_header, data, sizeof(*data_page)))
+ return NULL;
+
+ struct extent_io_data *ei = page_info->custom_data;
+
+ data_page->delta_start_s = (uint32_t) (page_info->start_time_s - (time_t) (j2_header->start_time_ut) / USEC_PER_SEC);
+ data_page->delta_end_s = (uint32_t) (page_info->end_time_s - (time_t) (j2_header->start_time_ut) / USEC_PER_SEC);
+ data_page->extent_index = page_info->extent_index;
+
+ data_page->update_every_s = page_info->update_every_s;
+ data_page->page_length = (uint16_t) (ei ? ei->page_length : page_info->page_length);
+ data_page->type = 0;
+
+ return ++data_page;
+}
+
+// Must be recorded in metric_info->entries
+void *journalfile_v2_write_descriptors(struct journal_v2_header *j2_header, void *data, struct jv2_metrics_info *metric_info)
+{
+ Pvoid_t *PValue;
+
+ struct journal_page_list *data_page = (void *)data;
+ // We need to write all descriptors with index metric_info->min_index_time_s, metric_info->max_index_time_s
+ // that belong to this journal file
+ Pvoid_t JudyL_array = metric_info->JudyL_pages_by_start_time;
+
+ Word_t index_time = 0;
+ bool first = true;
+ struct jv2_page_info *page_info;
+ while ((PValue = JudyLFirstThenNext(JudyL_array, &index_time, &first))) {
+ page_info = *PValue;
+ // Write one descriptor and return the next data page location
+ data_page = journalfile_v2_write_data_page(j2_header, (void *) data_page, page_info);
+ if (NULL == data_page)
+ break;
+ }
+ return data_page;
+}
+
+// Migrate the journalfile pointed by datafile
+// activate : make the new file active immediately
+// journafile data will be set and descriptors (if deleted) will be repopulated as needed
+// startup : if the migration is done during agent startup
+// this will allow us to optimize certain things
+
+void journalfile_migrate_to_v2_callback(Word_t section, unsigned datafile_fileno __maybe_unused, uint8_t type __maybe_unused,
+ Pvoid_t JudyL_metrics, Pvoid_t JudyL_extents_pos,
+ size_t number_of_extents, size_t number_of_metrics, size_t number_of_pages, void *user_data)
+{
+ char path[RRDENG_PATH_MAX];
+ Pvoid_t *PValue;
+ struct rrdengine_instance *ctx = (struct rrdengine_instance *) section;
+ struct rrdengine_journalfile *journalfile = (struct rrdengine_journalfile *) user_data;
+ struct rrdengine_datafile *datafile = journalfile->datafile;
+ time_t min_time_s = LONG_MAX;
+ time_t max_time_s = 0;
+ struct jv2_metrics_info *metric_info;
+
+ journalfile_v2_generate_path(datafile, path, sizeof(path));
+
+ info("DBENGINE: indexing file '%s': extents %zu, metrics %zu, pages %zu",
+ path,
+ number_of_extents,
+ number_of_metrics,
+ number_of_pages);
+
+#ifdef NETDATA_INTERNAL_CHECKS
+ usec_t start_loading = now_monotonic_usec();
+#endif
+
+ size_t total_file_size = 0;
+ total_file_size += (sizeof(struct journal_v2_header) + JOURNAL_V2_HEADER_PADDING_SZ);
+
+ // Extents will start here
+ uint32_t extent_offset = total_file_size;
+ total_file_size += (number_of_extents * sizeof(struct journal_extent_list));
+
+ uint32_t extent_offset_trailer = total_file_size;
+ total_file_size += sizeof(struct journal_v2_block_trailer);
+
+ // UUID list will start here
+ uint32_t metrics_offset = total_file_size;
+ total_file_size += (number_of_metrics * sizeof(struct journal_metric_list));
+
+ // UUID list trailer
+ uint32_t metric_offset_trailer = total_file_size;
+ total_file_size += sizeof(struct journal_v2_block_trailer);
+
+ // descr @ time will start here
+ uint32_t pages_offset = total_file_size;
+ total_file_size += (number_of_pages * (sizeof(struct journal_page_list) + sizeof(struct journal_page_header) + sizeof(struct journal_v2_block_trailer)));
+
+ // File trailer
+ uint32_t trailer_offset = total_file_size;
+ total_file_size += sizeof(struct journal_v2_block_trailer);
+
+ int fd_v2;
+ uint8_t *data_start = netdata_mmap(path, total_file_size, MAP_SHARED, 0, false, &fd_v2);
+ uint8_t *data = data_start;
+
+ memset(data_start, 0, extent_offset);
+
+ // Write header
+ struct journal_v2_header j2_header;
+ memset(&j2_header, 0, sizeof(j2_header));
+
+ j2_header.magic = JOURVAL_V2_MAGIC;
+ j2_header.start_time_ut = 0;
+ j2_header.end_time_ut = 0;
+ j2_header.extent_count = number_of_extents;
+ j2_header.extent_offset = extent_offset;
+ j2_header.metric_count = number_of_metrics;
+ j2_header.metric_offset = metrics_offset;
+ j2_header.page_count = number_of_pages;
+ j2_header.page_offset = pages_offset;
+ j2_header.extent_trailer_offset = extent_offset_trailer;
+ j2_header.metric_trailer_offset = metric_offset_trailer;
+ j2_header.journal_v2_file_size = total_file_size;
+ j2_header.journal_v1_file_size = (uint32_t)journalfile_current_size(journalfile);
+ j2_header.data = data_start; // Used during migration
+
+ struct journal_v2_block_trailer *journal_v2_trailer;
+
+ data = journalfile_v2_write_extent_list(JudyL_extents_pos, data_start + extent_offset);
+ internal_error(true, "DBENGINE: write extent list so far %llu", (now_monotonic_usec() - start_loading) / USEC_PER_MS);
+
+ fatal_assert(data == data_start + extent_offset_trailer);
+
+ // Calculate CRC for extents
+ journal_v2_trailer = (struct journal_v2_block_trailer *) (data_start + extent_offset_trailer);
+ uLong crc;
+ crc = crc32(0L, Z_NULL, 0);
+ crc = crc32(crc, (uint8_t *) data_start + extent_offset, number_of_extents * sizeof(struct journal_extent_list));
+ crc32set(journal_v2_trailer->checksum, crc);
+
+ internal_error(true, "DBENGINE: CALCULATE CRC FOR EXTENT %llu", (now_monotonic_usec() - start_loading) / USEC_PER_MS);
+ // Skip the trailer, point to the metrics off
+ data += sizeof(struct journal_v2_block_trailer);
+
+ // Sanity check -- we must be at the metrics_offset
+ fatal_assert(data == data_start + metrics_offset);
+
+ // Allocate array to sort UUIDs and keep them sorted in the journal because we want to do binary search when we do lookups
+ struct journal_metric_list_to_sort *uuid_list = mallocz(number_of_metrics * sizeof(struct journal_metric_list_to_sort));
+
+ Word_t Index = 0;
+ size_t count = 0;
+ bool first_then_next = true;
+ while ((PValue = JudyLFirstThenNext(JudyL_metrics, &Index, &first_then_next))) {
+ metric_info = *PValue;
+
+ fatal_assert(count < number_of_metrics);
+ uuid_list[count++].metric_info = metric_info;
+ min_time_s = MIN(min_time_s, metric_info->first_time_s);
+ max_time_s = MAX(max_time_s, metric_info->last_time_s);
+ }
+
+ // Store in the header
+ j2_header.start_time_ut = min_time_s * USEC_PER_SEC;
+ j2_header.end_time_ut = max_time_s * USEC_PER_SEC;
+
+ qsort(&uuid_list[0], number_of_metrics, sizeof(struct journal_metric_list_to_sort), journalfile_metric_compare);
+ internal_error(true, "DBENGINE: traverse and qsort UUID %llu", (now_monotonic_usec() - start_loading) / USEC_PER_MS);
+
+ uint32_t resize_file_to = total_file_size;
+
+ for (Index = 0; Index < number_of_metrics; Index++) {
+ metric_info = uuid_list[Index].metric_info;
+
+ // Calculate current UUID offset from start of file. We will store this in the data page header
+ uint32_t uuid_offset = data - data_start;
+
+ // Write the UUID we are processing
+ data = (void *) journalfile_v2_write_metric_page(&j2_header, data, metric_info, pages_offset);
+ if (unlikely(!data))
+ break;
+
+ // Next we will write
+ // Header
+ // Detailed entries (descr @ time)
+ // Trailer (checksum)
+
+ // Keep the page_list_header, to be used for migration when where agent is running
+ metric_info->page_list_header = pages_offset;
+ // Write page header
+ void *metric_page = journalfile_v2_write_data_page_header(&j2_header, data_start + pages_offset, metric_info,
+ uuid_offset);
+
+ // Start writing descr @ time
+ void *page_trailer = journalfile_v2_write_descriptors(&j2_header, metric_page, metric_info);
+ if (unlikely(!page_trailer))
+ break;
+
+ // Trailer (checksum)
+ uint8_t *next_page_address = journalfile_v2_write_data_page_trailer(&j2_header, page_trailer,
+ data_start + pages_offset);
+
+ // Calculate start of the pages start for next descriptor
+ pages_offset += (metric_info->number_of_pages * (sizeof(struct journal_page_list)) + sizeof(struct journal_page_header) + sizeof(struct journal_v2_block_trailer));
+ // Verify we are at the right location
+ if (pages_offset != (uint32_t)(next_page_address - data_start)) {
+ // make sure checks fail so that we abort
+ data = data_start;
+ break;
+ }
+ }
+
+ if (data == data_start + metric_offset_trailer) {
+ internal_error(true, "DBENGINE: WRITE METRICS AND PAGES %llu", (now_monotonic_usec() - start_loading) / USEC_PER_MS);
+
+ // Calculate CRC for metrics
+ journal_v2_trailer = (struct journal_v2_block_trailer *)(data_start + metric_offset_trailer);
+ crc = crc32(0L, Z_NULL, 0);
+ crc =
+ crc32(crc, (uint8_t *)data_start + metrics_offset, number_of_metrics * sizeof(struct journal_metric_list));
+ crc32set(journal_v2_trailer->checksum, crc);
+ internal_error(true, "DBENGINE: CALCULATE CRC FOR UUIDs %llu", (now_monotonic_usec() - start_loading) / USEC_PER_MS);
+
+ // Prepare to write checksum for the file
+ j2_header.data = NULL;
+ journal_v2_trailer = (struct journal_v2_block_trailer *)(data_start + trailer_offset);
+ crc = crc32(0L, Z_NULL, 0);
+ crc = crc32(crc, (void *)&j2_header, sizeof(j2_header));
+ crc32set(journal_v2_trailer->checksum, crc);
+
+ // Write header to the file
+ memcpy(data_start, &j2_header, sizeof(j2_header));
+
+ internal_error(true, "DBENGINE: FILE COMPLETED --------> %llu", (now_monotonic_usec() - start_loading) / USEC_PER_MS);
+
+ info("DBENGINE: migrated journal file '%s', file size %zu", path, total_file_size);
+
+ // msync(data_start, total_file_size, MS_SYNC);
+ journalfile_v2_data_set(journalfile, fd_v2, data_start, total_file_size);
+
+ internal_error(true, "DBENGINE: ACTIVATING NEW INDEX JNL %llu", (now_monotonic_usec() - start_loading) / USEC_PER_MS);
+ ctx_current_disk_space_increase(ctx, total_file_size);
+ freez(uuid_list);
+ return;
+ }
+ else {
+ info("DBENGINE: failed to build index '%s', file will be skipped", path);
+ j2_header.data = NULL;
+ j2_header.magic = JOURVAL_V2_SKIP_MAGIC;
+ memcpy(data_start, &j2_header, sizeof(j2_header));
+ resize_file_to = sizeof(j2_header);
+ }
+
+ netdata_munmap(data_start, total_file_size);
+ freez(uuid_list);
+
+ if (likely(resize_file_to == total_file_size))
+ return;
+
+ int ret = truncate(path, (long) resize_file_to);
+ if (ret < 0) {
+ ctx_current_disk_space_increase(ctx, total_file_size);
+ ctx_fs_error(ctx);
+ error("DBENGINE: failed to resize file '%s'", path);
+ }
+ else
+ ctx_current_disk_space_increase(ctx, resize_file_to);
+}
+
+int journalfile_load(struct rrdengine_instance *ctx, struct rrdengine_journalfile *journalfile,
+ struct rrdengine_datafile *datafile)
{
uv_fs_t req;
uv_file file;
int ret, fd, error;
uint64_t file_size, max_id;
char path[RRDENG_PATH_MAX];
+ bool loaded_v2 = false;
+
+ // Do not try to load jv2 of the latest file
+ if (datafile->fileno != ctx_last_fileno_get(ctx))
+ loaded_v2 = journalfile_v2_load(ctx, journalfile, datafile) == 0;
- generate_journalfilepath(datafile, path, sizeof(path));
- fd = open_file_direct_io(path, O_RDWR, &file);
+ journalfile_v1_generate_path(datafile, path, sizeof(path));
+
+ fd = open_file_for_io(path, O_RDWR, &file, use_direct_io);
if (fd < 0) {
- ++ctx->stats.fs_errors;
- rrd_stat_atomic_add(&global_fs_errors, 1);
+ ctx_fs_error(ctx);
+
+ if(loaded_v2)
+ return 0;
+
return fd;
}
- info("Loading journal file \"%s\".", path);
ret = check_file_properties(file, &file_size, sizeof(struct rrdeng_df_sb));
- if (ret)
- goto error;
- file_size = ALIGN_BYTES_FLOOR(file_size);
+ if (ret) {
+ error = ret;
+ goto cleanup;
+ }
- ret = check_journal_file_superblock(file);
- if (ret)
- goto error;
- ctx->stats.io_read_bytes += sizeof(struct rrdeng_jf_sb);
- ++ctx->stats.io_read_requests;
+ if(loaded_v2) {
+ journalfile->unsafe.pos = file_size;
+ error = 0;
+ goto cleanup;
+ }
+ file_size = ALIGN_BYTES_FLOOR(file_size);
+ journalfile->unsafe.pos = file_size;
journalfile->file = file;
- journalfile->pos = file_size;
- journalfile->data = netdata_mmap(path, file_size, MAP_SHARED, 0);
- info("Loading journal file \"%s\" using %s.", path, journalfile->data?"MMAP":"uv_fs_read");
- max_id = iterate_transactions(ctx, journalfile);
+ ret = journalfile_check_superblock(file);
+ if (ret) {
+ info("DBENGINE: invalid journal file '%s' ; superblock check failed.", path);
+ error = ret;
+ goto cleanup;
+ }
+ ctx_io_read_op_bytes(ctx, sizeof(struct rrdeng_jf_sb));
+
+ info("DBENGINE: loading journal file '%s'", path);
- ctx->commit_log.transaction_id = MAX(ctx->commit_log.transaction_id, max_id + 1);
+ max_id = journalfile_iterate_transactions(ctx, journalfile);
+
+ __atomic_store_n(&ctx->atomic.transaction_id, MAX(__atomic_load_n(&ctx->atomic.transaction_id, __ATOMIC_RELAXED), max_id + 1), __ATOMIC_RELAXED);
+
+ info("DBENGINE: journal file '%s' loaded (size:%"PRIu64").", path, file_size);
+
+ bool is_last_file = (ctx_last_fileno_get(ctx) == journalfile->datafile->fileno);
+ if (is_last_file && journalfile->datafile->pos <= rrdeng_target_data_file_size(ctx) / 3) {
+ ctx->loading.create_new_datafile_pair = false;
+ return 0;
+ }
+
+ pgc_open_cache_to_journal_v2(open_cache, (Word_t) ctx, (int) datafile->fileno, ctx->config.page_type,
+ journalfile_migrate_to_v2_callback, (void *) datafile->journalfile);
+
+ if (is_last_file)
+ ctx->loading.create_new_datafile_pair = true;
- info("Journal file \"%s\" loaded (size:%"PRIu64").", path, file_size);
- if (likely(journalfile->data))
- netdata_munmap(journalfile->data, file_size);
return 0;
- error:
- error = ret;
+cleanup:
ret = uv_fs_close(NULL, &req, file, NULL);
if (ret < 0) {
- error("uv_fs_close(%s): %s", path, uv_strerror(ret));
- ++ctx->stats.fs_errors;
- rrd_stat_atomic_add(&global_fs_errors, 1);
+ error("DBENGINE: uv_fs_close(%s): %s", path, uv_strerror(ret));
+ ctx_fs_error(ctx);
}
uv_fs_req_cleanup(&req);
return error;
}
-
-void init_commit_log(struct rrdengine_instance *ctx)
-{
- ctx->commit_log.buf = NULL;
- ctx->commit_log.buf_pos = 0;
- ctx->commit_log.transaction_id = 1;
-}
diff --git a/database/engine/journalfile.h b/database/engine/journalfile.h
index 011c5065..5fbcc90f 100644
--- a/database/engine/journalfile.h
+++ b/database/engine/journalfile.h
@@ -13,37 +13,147 @@ struct rrdengine_journalfile;
#define WALFILE_PREFIX "journalfile-"
#define WALFILE_EXTENSION ".njf"
+#define WALFILE_EXTENSION_V2 ".njfv2"
+#define is_descr_journal_v2(descr) ((descr)->extent_entry != NULL)
+
+typedef enum __attribute__ ((__packed__)) {
+ JOURNALFILE_FLAG_IS_AVAILABLE = (1 << 0),
+ JOURNALFILE_FLAG_IS_MOUNTED = (1 << 1),
+ JOURNALFILE_FLAG_MOUNTED_FOR_RETENTION = (1 << 2),
+} JOURNALFILE_FLAGS;
/* only one event loop is supported for now */
struct rrdengine_journalfile {
+ struct {
+ SPINLOCK spinlock;
+ void *data; // MMAPed file of journal v2
+ uint32_t size; // Total file size mapped
+ int fd;
+ } mmap;
+
+ struct {
+ SPINLOCK spinlock;
+ JOURNALFILE_FLAGS flags;
+ int32_t refcount;
+ time_t first_time_s;
+ time_t last_time_s;
+ time_t not_needed_since_s;
+ } v2;
+
+ struct {
+ SPINLOCK spinlock;
+ uint64_t pos;
+ } unsafe;
+
uv_file file;
- uint64_t pos;
- void *data;
struct rrdengine_datafile *datafile;
};
-/* only one event loop is supported for now */
-struct transaction_commit_log {
- uint64_t transaction_id;
+static inline uint64_t journalfile_current_size(struct rrdengine_journalfile *journalfile) {
+ netdata_spinlock_lock(&journalfile->unsafe.spinlock);
+ uint64_t size = journalfile->unsafe.pos;
+ netdata_spinlock_unlock(&journalfile->unsafe.spinlock);
+ return size;
+}
+
+// Journal v2 structures
+
+#define JOURVAL_V2_MAGIC (0x01221019)
+#define JOURVAL_V2_REBUILD_MAGIC (0x00221019)
+#define JOURVAL_V2_SKIP_MAGIC (0x02221019)
+
+struct journal_v2_block_trailer {
+ union {
+ uint8_t checksum[CHECKSUM_SZ]; /* CRC32 */
+ uint32_t crc;
+ };
+};
+
+// Journal V2
+// 28 bytes
+struct journal_page_header {
+ union {
+ uint8_t checksum[CHECKSUM_SZ]; // CRC check
+ uint32_t crc;
+ };
+ uint32_t uuid_offset; // Points back to the UUID list which should point here (UUIDs should much)
+ uint32_t entries; // Entries
+ uuid_t uuid; // Which UUID this is
+};
- /* outstanding transaction buffer */
- void *buf;
- unsigned buf_pos;
- unsigned buf_size;
+// 20 bytes
+struct journal_page_list {
+ uint32_t delta_start_s; // relative to the start time of journal
+ uint32_t delta_end_s; // relative to delta_start
+ uint32_t extent_index; // Index to the extent (extent list) (bytes from BASE)
+ uint32_t update_every_s;
+ uint16_t page_length;
+ uint8_t type;
};
-void generate_journalfilepath(struct rrdengine_datafile *datafile, char *str, size_t maxlen);
-void journalfile_init(struct rrdengine_journalfile *journalfile, struct rrdengine_datafile *datafile);
-void *wal_get_transaction_buffer(struct rrdengine_worker_config* wc, unsigned size);
-void wal_flush_transaction_buffer(struct rrdengine_worker_config* wc);
-int close_journal_file(struct rrdengine_journalfile *journalfile, struct rrdengine_datafile *datafile);
-int unlink_journal_file(struct rrdengine_journalfile *journalfile);
-int destroy_journal_file(struct rrdengine_journalfile *journalfile, struct rrdengine_datafile *datafile);
-int create_journal_file(struct rrdengine_journalfile *journalfile, struct rrdengine_datafile *datafile);
-int load_journal_file(struct rrdengine_instance *ctx, struct rrdengine_journalfile *journalfile,
- struct rrdengine_datafile *datafile);
-void init_commit_log(struct rrdengine_instance *ctx);
+// UUID_LIST
+// 32 bytes
+struct journal_metric_list {
+ uuid_t uuid;
+ uint32_t entries; // Number of entries
+ uint32_t page_offset; // OFFSET that contains entries * struct( journal_page_list )
+ uint32_t delta_start_s; // Min time of metric
+ uint32_t delta_end_s; // Max time of metric (to be used to populate page_index)
+};
+
+// 16 bytes
+struct journal_extent_list {
+ uint64_t datafile_offset; // Datafile offset to find the extent
+ uint32_t datafile_size; // Size of the extent
+ uint16_t file_index; // which file index is this datafile[index]
+ uint8_t pages; // number of pages (not all are necesssarily valid)
+};
+
+// 72 bytes
+struct journal_v2_header {
+ uint32_t magic;
+ usec_t start_time_ut; // Min start time of journal
+ usec_t end_time_ut; // Maximum end time of journal
+ uint32_t extent_count; // Count of extents
+ uint32_t extent_offset;
+ uint32_t metric_count; // Count of metrics (unique UUIDS)
+ uint32_t metric_offset;
+ uint32_t page_count; // Total count of pages (descriptors @ time)
+ uint32_t page_offset;
+ uint32_t extent_trailer_offset; // CRC for entent list
+ uint32_t metric_trailer_offset; // CRC for metric list
+ uint32_t journal_v1_file_size; // This is the original journal file
+ uint32_t journal_v2_file_size; // This is the total file size
+ void *data; // Used when building the index
+};
+
+#define JOURNAL_V2_HEADER_PADDING_SZ (RRDENG_BLOCK_SIZE - (sizeof(struct journal_v2_header)))
+
+struct wal;
+
+void journalfile_v1_generate_path(struct rrdengine_datafile *datafile, char *str, size_t maxlen);
+void journalfile_v2_generate_path(struct rrdengine_datafile *datafile, char *str, size_t maxlen);
+struct rrdengine_journalfile *journalfile_alloc_and_init(struct rrdengine_datafile *datafile);
+void journalfile_v1_extent_write(struct rrdengine_instance *ctx, struct rrdengine_datafile *datafile, struct wal *wal, uv_loop_t *loop);
+int journalfile_close(struct rrdengine_journalfile *journalfile, struct rrdengine_datafile *datafile);
+int journalfile_unlink(struct rrdengine_journalfile *journalfile);
+int journalfile_destroy_unsafe(struct rrdengine_journalfile *journalfile, struct rrdengine_datafile *datafile);
+int journalfile_create(struct rrdengine_journalfile *journalfile, struct rrdengine_datafile *datafile);
+int journalfile_load(struct rrdengine_instance *ctx, struct rrdengine_journalfile *journalfile,
+ struct rrdengine_datafile *datafile);
+void journalfile_v2_populate_retention_to_mrg(struct rrdengine_instance *ctx, struct rrdengine_journalfile *journalfile);
+
+void journalfile_migrate_to_v2_callback(Word_t section, unsigned datafile_fileno __maybe_unused, uint8_t type __maybe_unused,
+ Pvoid_t JudyL_metrics, Pvoid_t JudyL_extents_pos,
+ size_t number_of_extents, size_t number_of_metrics, size_t number_of_pages, void *user_data);
+
+bool journalfile_v2_data_available(struct rrdengine_journalfile *journalfile);
+size_t journalfile_v2_data_size_get(struct rrdengine_journalfile *journalfile);
+void journalfile_v2_data_set(struct rrdengine_journalfile *journalfile, int fd, void *journal_data, uint32_t journal_data_size);
+struct journal_v2_header *journalfile_v2_data_acquire(struct rrdengine_journalfile *journalfile, size_t *data_size, time_t wanted_first_time_s, time_t wanted_last_time_s);
+void journalfile_v2_data_release(struct rrdengine_journalfile *journalfile);
+void journalfile_v2_data_unmount_cleanup(time_t now_s);
#endif /* NETDATA_JOURNALFILE_H */ \ No newline at end of file
diff --git a/database/engine/journalfile.ksy b/database/engine/journalfile.ksy
new file mode 100644
index 00000000..858db83d
--- /dev/null
+++ b/database/engine/journalfile.ksy
@@ -0,0 +1,144 @@
+meta:
+ id: netdata_journalfile_v2
+ endian: le
+
+seq:
+ - id: journal_v2_header
+ type: journal_v2_header
+ size: 4096
+ - id: extent_list
+ type: journal_v2_extent_list
+ repeat: expr
+ repeat-expr: journal_v2_header.extent_count
+ - id: extent_trailer
+ type: journal_v2_block_trailer
+ - id: metric_list
+ type: journal_v2_metric_list
+ repeat: expr
+ repeat-expr: journal_v2_header.metric_count
+ - id: metric_trailer
+ type: journal_v2_block_trailer
+ - id: page_blocs
+ type: jounral_v2_page_blocs
+ size: _root._io.size - _root._io.pos - 4
+ - id: journal_file_trailer
+ type: journal_v2_block_trailer
+
+
+types:
+ journal_v2_metric_list:
+ seq:
+ - id: uuid
+ size: 16
+ - id: entries
+ type: u4
+ - id: page_offset
+ type: u4
+ - id: delta_start_s
+ type: u4
+ - id: delta_end_s
+ type: u4
+ instances:
+ page_block:
+ type: journal_v2_page_block
+ io: _root._io
+ pos: page_offset
+ journal_v2_page_hdr:
+ seq:
+ - id: crc
+ type: u4
+ - id: uuid_offset
+ type: u4
+ - id: entries
+ type: u4
+ - id: uuid
+ size: 16
+ journal_v2_page_list:
+ seq:
+ - id: delta_start_s
+ type: u4
+ - id: delta_end_s
+ type: u4
+ - id: extent_idx
+ type: u4
+ - id: update_every_s
+ type: u4
+ - id: page_len
+ type: u2
+ - id: type
+ type: u1
+ - id: reserved
+ type: u1
+ instances:
+ extent:
+ io: _root._io
+ type: journal_v2_extent_list
+ pos: _root.journal_v2_header.extent_offset + (extent_idx * 16)
+ journal_v2_header:
+ seq:
+ - id: magic
+ contents: [ 0x19, 0x10, 0x22, 0x01 ] #0x01221019
+ - id: reserved
+ type: u4
+ - id: start_time_ut
+ type: u8
+ - id: end_time_ut
+ type: u8
+ - id: extent_count
+ type: u4
+ - id: extent_offset
+ type: u4
+ - id: metric_count
+ type: u4
+ - id: metric_offset
+ type: u4
+ - id: page_count
+ type: u4
+ - id: page_offset
+ type: u4
+ - id: extent_trailer_offset
+ type: u4
+ - id: metric_trailer_offset
+ type: u4
+ - id: original_file_size
+ type: u4
+ - id: total_file_size
+ type: u4
+ - id: data
+ type: u8
+ instances:
+ trailer:
+ io: _root._io
+ type: journal_v2_block_trailer
+ pos: _root._io.size - 4
+ journal_v2_block_trailer:
+ seq:
+ - id: checksum
+ type: u4
+ journal_v2_extent_list:
+ seq:
+ - id: datafile_offset
+ type: u8
+ - id: datafile_size
+ type: u4
+ - id: file_idx
+ type: u2
+ - id: page_cnt
+ type: u1
+ - id: padding
+ type: u1
+ journal_v2_page_block:
+ seq:
+ - id: hdr
+ type: journal_v2_page_hdr
+ - id: page_list
+ type: journal_v2_page_list
+ repeat: expr
+ repeat-expr: hdr.entries
+ - id: block_trailer
+ type: journal_v2_block_trailer
+ jounral_v2_page_blocs:
+ seq:
+ - id: blocs
+ type: journal_v2_page_block
+ repeat: eos
diff --git a/database/engine/metric.c b/database/engine/metric.c
new file mode 100644
index 00000000..9dc9d9eb
--- /dev/null
+++ b/database/engine/metric.c
@@ -0,0 +1,875 @@
+#include "metric.h"
+
+typedef int32_t REFCOUNT;
+#define REFCOUNT_DELETING (-100)
+
+typedef enum __attribute__ ((__packed__)) {
+ METRIC_FLAG_HAS_RETENTION = (1 << 0),
+} METRIC_FLAGS;
+
+struct metric {
+ uuid_t uuid; // never changes
+ Word_t section; // never changes
+
+ time_t first_time_s; //
+ time_t latest_time_s_clean; // archived pages latest time
+ time_t latest_time_s_hot; // latest time of the currently collected page
+ uint32_t latest_update_every_s; //
+ pid_t writer;
+ METRIC_FLAGS flags;
+ REFCOUNT refcount;
+ SPINLOCK spinlock; // protects all variable members
+
+ // THIS IS allocated with malloc()
+ // YOU HAVE TO INITIALIZE IT YOURSELF !
+};
+
+static struct aral_statistics mrg_aral_statistics;
+
+struct mrg {
+ ARAL *aral[MRG_PARTITIONS];
+
+ struct pgc_index {
+ netdata_rwlock_t rwlock;
+ Pvoid_t uuid_judy; // each UUID has a JudyL of sections (tiers)
+ } index[MRG_PARTITIONS];
+
+ struct mrg_statistics stats;
+
+ size_t entries_per_partition[MRG_PARTITIONS];
+};
+
+static inline void MRG_STATS_DUPLICATE_ADD(MRG *mrg) {
+ __atomic_add_fetch(&mrg->stats.additions_duplicate, 1, __ATOMIC_RELAXED);
+}
+
+static inline void MRG_STATS_ADDED_METRIC(MRG *mrg, size_t partition) {
+ __atomic_add_fetch(&mrg->stats.entries, 1, __ATOMIC_RELAXED);
+ __atomic_add_fetch(&mrg->stats.additions, 1, __ATOMIC_RELAXED);
+ __atomic_add_fetch(&mrg->stats.size, sizeof(METRIC), __ATOMIC_RELAXED);
+
+ __atomic_add_fetch(&mrg->entries_per_partition[partition], 1, __ATOMIC_RELAXED);
+}
+
+static inline void MRG_STATS_DELETED_METRIC(MRG *mrg, size_t partition) {
+ __atomic_sub_fetch(&mrg->stats.entries, 1, __ATOMIC_RELAXED);
+ __atomic_sub_fetch(&mrg->stats.size, sizeof(METRIC), __ATOMIC_RELAXED);
+ __atomic_add_fetch(&mrg->stats.deletions, 1, __ATOMIC_RELAXED);
+
+ __atomic_sub_fetch(&mrg->entries_per_partition[partition], 1, __ATOMIC_RELAXED);
+}
+
+static inline void MRG_STATS_SEARCH_HIT(MRG *mrg) {
+ __atomic_add_fetch(&mrg->stats.search_hits, 1, __ATOMIC_RELAXED);
+}
+
+static inline void MRG_STATS_SEARCH_MISS(MRG *mrg) {
+ __atomic_add_fetch(&mrg->stats.search_misses, 1, __ATOMIC_RELAXED);
+}
+
+static inline void MRG_STATS_DELETE_MISS(MRG *mrg) {
+ __atomic_add_fetch(&mrg->stats.delete_misses, 1, __ATOMIC_RELAXED);
+}
+
+static inline void mrg_index_read_lock(MRG *mrg, size_t partition) {
+ netdata_rwlock_rdlock(&mrg->index[partition].rwlock);
+}
+static inline void mrg_index_read_unlock(MRG *mrg, size_t partition) {
+ netdata_rwlock_unlock(&mrg->index[partition].rwlock);
+}
+static inline void mrg_index_write_lock(MRG *mrg, size_t partition) {
+ netdata_rwlock_wrlock(&mrg->index[partition].rwlock);
+}
+static inline void mrg_index_write_unlock(MRG *mrg, size_t partition) {
+ netdata_rwlock_unlock(&mrg->index[partition].rwlock);
+}
+
+static inline void mrg_stats_size_judyl_change(MRG *mrg, size_t mem_before_judyl, size_t mem_after_judyl) {
+ if(mem_after_judyl > mem_before_judyl)
+ __atomic_add_fetch(&mrg->stats.size, mem_after_judyl - mem_before_judyl, __ATOMIC_RELAXED);
+ else if(mem_after_judyl < mem_before_judyl)
+ __atomic_sub_fetch(&mrg->stats.size, mem_before_judyl - mem_after_judyl, __ATOMIC_RELAXED);
+}
+
+static inline void mrg_stats_size_judyhs_added_uuid(MRG *mrg) {
+ __atomic_add_fetch(&mrg->stats.size, JUDYHS_INDEX_SIZE_ESTIMATE(sizeof(uuid_t)), __ATOMIC_RELAXED);
+}
+
+static inline void mrg_stats_size_judyhs_removed_uuid(MRG *mrg) {
+ __atomic_sub_fetch(&mrg->stats.size, JUDYHS_INDEX_SIZE_ESTIMATE(sizeof(uuid_t)), __ATOMIC_RELAXED);
+}
+
+static inline size_t uuid_partition(MRG *mrg __maybe_unused, uuid_t *uuid) {
+ uint8_t *u = (uint8_t *)uuid;
+ return u[UUID_SZ - 1] % MRG_PARTITIONS;
+}
+
+static inline bool metric_has_retention_unsafe(MRG *mrg __maybe_unused, METRIC *metric) {
+ bool has_retention = (metric->first_time_s || metric->latest_time_s_clean || metric->latest_time_s_hot);
+
+ if(has_retention && !(metric->flags & METRIC_FLAG_HAS_RETENTION)) {
+ metric->flags |= METRIC_FLAG_HAS_RETENTION;
+ __atomic_add_fetch(&mrg->stats.entries_with_retention, 1, __ATOMIC_RELAXED);
+ }
+ else if(!has_retention && (metric->flags & METRIC_FLAG_HAS_RETENTION)) {
+ metric->flags &= ~METRIC_FLAG_HAS_RETENTION;
+ __atomic_sub_fetch(&mrg->stats.entries_with_retention, 1, __ATOMIC_RELAXED);
+ }
+
+ return has_retention;
+}
+
+static inline REFCOUNT metric_acquire(MRG *mrg __maybe_unused, METRIC *metric, bool having_spinlock) {
+ REFCOUNT refcount;
+
+ if(!having_spinlock)
+ netdata_spinlock_lock(&metric->spinlock);
+
+ if(unlikely(metric->refcount < 0))
+ fatal("METRIC: refcount is %d (negative) during acquire", metric->refcount);
+
+ refcount = ++metric->refcount;
+
+ // update its retention flags
+ metric_has_retention_unsafe(mrg, metric);
+
+ if(!having_spinlock)
+ netdata_spinlock_unlock(&metric->spinlock);
+
+ if(refcount == 1)
+ __atomic_add_fetch(&mrg->stats.entries_referenced, 1, __ATOMIC_RELAXED);
+
+ __atomic_add_fetch(&mrg->stats.current_references, 1, __ATOMIC_RELAXED);
+
+ return refcount;
+}
+
+static inline bool metric_release_and_can_be_deleted(MRG *mrg __maybe_unused, METRIC *metric) {
+ bool ret = true;
+ REFCOUNT refcount;
+
+ netdata_spinlock_lock(&metric->spinlock);
+
+ if(unlikely(metric->refcount <= 0))
+ fatal("METRIC: refcount is %d (zero or negative) during release", metric->refcount);
+
+ refcount = --metric->refcount;
+
+ if(likely(metric_has_retention_unsafe(mrg, metric) || refcount != 0))
+ ret = false;
+
+ netdata_spinlock_unlock(&metric->spinlock);
+
+ if(unlikely(!refcount))
+ __atomic_sub_fetch(&mrg->stats.entries_referenced, 1, __ATOMIC_RELAXED);
+
+ __atomic_sub_fetch(&mrg->stats.current_references, 1, __ATOMIC_RELAXED);
+
+ return ret;
+}
+
+static METRIC *metric_add_and_acquire(MRG *mrg, MRG_ENTRY *entry, bool *ret) {
+ size_t partition = uuid_partition(mrg, &entry->uuid);
+
+ METRIC *allocation = aral_mallocz(mrg->aral[partition]);
+
+ mrg_index_write_lock(mrg, partition);
+
+ size_t mem_before_judyl, mem_after_judyl;
+
+ Pvoid_t *sections_judy_pptr = JudyHSIns(&mrg->index[partition].uuid_judy, &entry->uuid, sizeof(uuid_t), PJE0);
+ if(unlikely(!sections_judy_pptr || sections_judy_pptr == PJERR))
+ fatal("DBENGINE METRIC: corrupted UUIDs JudyHS array");
+
+ if(unlikely(!*sections_judy_pptr))
+ mrg_stats_size_judyhs_added_uuid(mrg);
+
+ mem_before_judyl = JudyLMemUsed(*sections_judy_pptr);
+ Pvoid_t *PValue = JudyLIns(sections_judy_pptr, entry->section, PJE0);
+ mem_after_judyl = JudyLMemUsed(*sections_judy_pptr);
+ mrg_stats_size_judyl_change(mrg, mem_before_judyl, mem_after_judyl);
+
+ if(unlikely(!PValue || PValue == PJERR))
+ fatal("DBENGINE METRIC: corrupted section JudyL array");
+
+ if(unlikely(*PValue != NULL)) {
+ METRIC *metric = *PValue;
+
+ metric_acquire(mrg, metric, false);
+ mrg_index_write_unlock(mrg, partition);
+
+ if(ret)
+ *ret = false;
+
+ aral_freez(mrg->aral[partition], allocation);
+
+ MRG_STATS_DUPLICATE_ADD(mrg);
+ return metric;
+ }
+
+ METRIC *metric = allocation;
+ uuid_copy(metric->uuid, entry->uuid);
+ metric->section = entry->section;
+ metric->first_time_s = entry->first_time_s;
+ metric->latest_time_s_clean = entry->last_time_s;
+ metric->latest_time_s_hot = 0;
+ metric->latest_update_every_s = entry->latest_update_every_s;
+ metric->writer = 0;
+ metric->refcount = 0;
+ metric->flags = 0;
+ netdata_spinlock_init(&metric->spinlock);
+ metric_acquire(mrg, metric, true); // no spinlock use required here
+ *PValue = metric;
+
+ mrg_index_write_unlock(mrg, partition);
+
+ if(ret)
+ *ret = true;
+
+ MRG_STATS_ADDED_METRIC(mrg, partition);
+
+ return metric;
+}
+
+static METRIC *metric_get_and_acquire(MRG *mrg, uuid_t *uuid, Word_t section) {
+ size_t partition = uuid_partition(mrg, uuid);
+
+ mrg_index_read_lock(mrg, partition);
+
+ Pvoid_t *sections_judy_pptr = JudyHSGet(mrg->index[partition].uuid_judy, uuid, sizeof(uuid_t));
+ if(unlikely(!sections_judy_pptr)) {
+ mrg_index_read_unlock(mrg, partition);
+ MRG_STATS_SEARCH_MISS(mrg);
+ return NULL;
+ }
+
+ Pvoid_t *PValue = JudyLGet(*sections_judy_pptr, section, PJE0);
+ if(unlikely(!PValue)) {
+ mrg_index_read_unlock(mrg, partition);
+ MRG_STATS_SEARCH_MISS(mrg);
+ return NULL;
+ }
+
+ METRIC *metric = *PValue;
+
+ metric_acquire(mrg, metric, false);
+
+ mrg_index_read_unlock(mrg, partition);
+
+ MRG_STATS_SEARCH_HIT(mrg);
+ return metric;
+}
+
+static bool acquired_metric_del(MRG *mrg, METRIC *metric) {
+ size_t partition = uuid_partition(mrg, &metric->uuid);
+
+ size_t mem_before_judyl, mem_after_judyl;
+
+ mrg_index_write_lock(mrg, partition);
+
+ if(!metric_release_and_can_be_deleted(mrg, metric)) {
+ mrg_index_write_unlock(mrg, partition);
+ __atomic_add_fetch(&mrg->stats.delete_having_retention_or_referenced, 1, __ATOMIC_RELAXED);
+ return false;
+ }
+
+ Pvoid_t *sections_judy_pptr = JudyHSGet(mrg->index[partition].uuid_judy, &metric->uuid, sizeof(uuid_t));
+ if(unlikely(!sections_judy_pptr || !*sections_judy_pptr)) {
+ mrg_index_write_unlock(mrg, partition);
+ MRG_STATS_DELETE_MISS(mrg);
+ return false;
+ }
+
+ mem_before_judyl = JudyLMemUsed(*sections_judy_pptr);
+ int rc = JudyLDel(sections_judy_pptr, metric->section, PJE0);
+ mem_after_judyl = JudyLMemUsed(*sections_judy_pptr);
+ mrg_stats_size_judyl_change(mrg, mem_before_judyl, mem_after_judyl);
+
+ if(unlikely(!rc)) {
+ mrg_index_write_unlock(mrg, partition);
+ MRG_STATS_DELETE_MISS(mrg);
+ return false;
+ }
+
+ if(!*sections_judy_pptr) {
+ rc = JudyHSDel(&mrg->index[partition].uuid_judy, &metric->uuid, sizeof(uuid_t), PJE0);
+ if(unlikely(!rc))
+ fatal("DBENGINE METRIC: cannot delete UUID from JudyHS");
+ mrg_stats_size_judyhs_removed_uuid(mrg);
+ }
+
+ mrg_index_write_unlock(mrg, partition);
+
+ aral_freez(mrg->aral[partition], metric);
+
+ MRG_STATS_DELETED_METRIC(mrg, partition);
+
+ return true;
+}
+
+// ----------------------------------------------------------------------------
+// public API
+
+MRG *mrg_create(void) {
+ MRG *mrg = callocz(1, sizeof(MRG));
+
+ for(size_t i = 0; i < MRG_PARTITIONS ; i++) {
+ netdata_rwlock_init(&mrg->index[i].rwlock);
+
+ char buf[ARAL_MAX_NAME + 1];
+ snprintfz(buf, ARAL_MAX_NAME, "mrg[%zu]", i);
+
+ mrg->aral[i] = aral_create(buf,
+ sizeof(METRIC),
+ 0,
+ 16384,
+ &mrg_aral_statistics,
+ NULL, NULL, false,
+ false);
+ }
+
+ mrg->stats.size = sizeof(MRG);
+
+ return mrg;
+}
+
+size_t mrg_aral_structures(void) {
+ return aral_structures_from_stats(&mrg_aral_statistics);
+}
+
+size_t mrg_aral_overhead(void) {
+ return aral_overhead_from_stats(&mrg_aral_statistics);
+}
+
+void mrg_destroy(MRG *mrg __maybe_unused) {
+ // no destruction possible
+ // we can't traverse the metrics list
+
+ // to delete entries, the caller needs to keep pointers to them
+ // and delete them one by one
+
+ ;
+}
+
+METRIC *mrg_metric_add_and_acquire(MRG *mrg, MRG_ENTRY entry, bool *ret) {
+// internal_fatal(entry.latest_time_s > max_acceptable_collected_time(),
+// "DBENGINE METRIC: metric latest time is in the future");
+
+ return metric_add_and_acquire(mrg, &entry, ret);
+}
+
+METRIC *mrg_metric_get_and_acquire(MRG *mrg, uuid_t *uuid, Word_t section) {
+ return metric_get_and_acquire(mrg, uuid, section);
+}
+
+bool mrg_metric_release_and_delete(MRG *mrg, METRIC *metric) {
+ return acquired_metric_del(mrg, metric);
+}
+
+METRIC *mrg_metric_dup(MRG *mrg, METRIC *metric) {
+ metric_acquire(mrg, metric, false);
+ return metric;
+}
+
+bool mrg_metric_release(MRG *mrg, METRIC *metric) {
+ return metric_release_and_can_be_deleted(mrg, metric);
+}
+
+Word_t mrg_metric_id(MRG *mrg __maybe_unused, METRIC *metric) {
+ return (Word_t)metric;
+}
+
+uuid_t *mrg_metric_uuid(MRG *mrg __maybe_unused, METRIC *metric) {
+ return &metric->uuid;
+}
+
+Word_t mrg_metric_section(MRG *mrg __maybe_unused, METRIC *metric) {
+ return metric->section;
+}
+
+bool mrg_metric_set_first_time_s(MRG *mrg __maybe_unused, METRIC *metric, time_t first_time_s) {
+ netdata_spinlock_lock(&metric->spinlock);
+ metric->first_time_s = first_time_s;
+ metric_has_retention_unsafe(mrg, metric);
+ netdata_spinlock_unlock(&metric->spinlock);
+
+ return true;
+}
+
+void mrg_metric_expand_retention(MRG *mrg __maybe_unused, METRIC *metric, time_t first_time_s, time_t last_time_s, time_t update_every_s) {
+
+ internal_fatal(first_time_s > max_acceptable_collected_time(),
+ "DBENGINE METRIC: metric first time is in the future");
+ internal_fatal(last_time_s > max_acceptable_collected_time(),
+ "DBENGINE METRIC: metric last time is in the future");
+
+ netdata_spinlock_lock(&metric->spinlock);
+
+ if(unlikely(first_time_s && (!metric->first_time_s || first_time_s < metric->first_time_s)))
+ metric->first_time_s = first_time_s;
+
+ if(likely(last_time_s && (!metric->latest_time_s_clean || last_time_s > metric->latest_time_s_clean))) {
+ metric->latest_time_s_clean = last_time_s;
+
+ if(likely(update_every_s))
+ metric->latest_update_every_s = update_every_s;
+ }
+ else if(unlikely(!metric->latest_update_every_s && update_every_s))
+ metric->latest_update_every_s = update_every_s;
+
+ metric_has_retention_unsafe(mrg, metric);
+ netdata_spinlock_unlock(&metric->spinlock);
+}
+
+bool mrg_metric_set_first_time_s_if_bigger(MRG *mrg __maybe_unused, METRIC *metric, time_t first_time_s) {
+ bool ret = false;
+
+ netdata_spinlock_lock(&metric->spinlock);
+ if(first_time_s > metric->first_time_s) {
+ metric->first_time_s = first_time_s;
+ ret = true;
+ }
+ metric_has_retention_unsafe(mrg, metric);
+ netdata_spinlock_unlock(&metric->spinlock);
+
+ return ret;
+}
+
+time_t mrg_metric_get_first_time_s(MRG *mrg __maybe_unused, METRIC *metric) {
+ time_t first_time_s;
+
+ netdata_spinlock_lock(&metric->spinlock);
+
+ if(unlikely(!metric->first_time_s)) {
+ if(metric->latest_time_s_clean)
+ metric->first_time_s = metric->latest_time_s_clean;
+
+ else if(metric->latest_time_s_hot)
+ metric->first_time_s = metric->latest_time_s_hot;
+ }
+
+ first_time_s = metric->first_time_s;
+
+ netdata_spinlock_unlock(&metric->spinlock);
+
+ return first_time_s;
+}
+
+void mrg_metric_get_retention(MRG *mrg __maybe_unused, METRIC *metric, time_t *first_time_s, time_t *last_time_s, time_t *update_every_s) {
+ netdata_spinlock_lock(&metric->spinlock);
+
+ if(unlikely(!metric->first_time_s)) {
+ if(metric->latest_time_s_clean)
+ metric->first_time_s = metric->latest_time_s_clean;
+
+ else if(metric->latest_time_s_hot)
+ metric->first_time_s = metric->latest_time_s_hot;
+ }
+
+ *first_time_s = metric->first_time_s;
+ *last_time_s = MAX(metric->latest_time_s_clean, metric->latest_time_s_hot);
+ *update_every_s = metric->latest_update_every_s;
+
+ netdata_spinlock_unlock(&metric->spinlock);
+}
+
+bool mrg_metric_set_clean_latest_time_s(MRG *mrg __maybe_unused, METRIC *metric, time_t latest_time_s) {
+ netdata_spinlock_lock(&metric->spinlock);
+
+// internal_fatal(latest_time_s > max_acceptable_collected_time(),
+// "DBENGINE METRIC: metric latest time is in the future");
+
+// internal_fatal(metric->latest_time_s_clean > latest_time_s,
+// "DBENGINE METRIC: metric new clean latest time is older than the previous one");
+
+ metric->latest_time_s_clean = latest_time_s;
+
+ if(unlikely(!metric->first_time_s))
+ metric->first_time_s = latest_time_s;
+
+// if(unlikely(metric->first_time_s > latest_time_s))
+// metric->first_time_s = latest_time_s;
+
+ metric_has_retention_unsafe(mrg, metric);
+ netdata_spinlock_unlock(&metric->spinlock);
+ return true;
+}
+
+// returns true when metric still has retention
+bool mrg_metric_zero_disk_retention(MRG *mrg __maybe_unused, METRIC *metric) {
+ Word_t section = mrg_metric_section(mrg, metric);
+ bool do_again = false;
+ size_t countdown = 5;
+ bool ret = true;
+
+ do {
+ time_t min_first_time_s = LONG_MAX;
+ time_t max_end_time_s = 0;
+ PGC_PAGE *page;
+ PGC_SEARCH method = PGC_SEARCH_FIRST;
+ time_t page_first_time_s = 0;
+ time_t page_end_time_s = 0;
+ while ((page = pgc_page_get_and_acquire(main_cache, section, (Word_t)metric, page_first_time_s, method))) {
+ method = PGC_SEARCH_NEXT;
+
+ bool is_hot = pgc_is_page_hot(page);
+ bool is_dirty = pgc_is_page_dirty(page);
+ page_first_time_s = pgc_page_start_time_s(page);
+ page_end_time_s = pgc_page_end_time_s(page);
+
+ if ((is_hot || is_dirty) && page_first_time_s < min_first_time_s)
+ min_first_time_s = page_first_time_s;
+
+ if (is_dirty && page_end_time_s > max_end_time_s)
+ max_end_time_s = page_end_time_s;
+
+ pgc_page_release(main_cache, page);
+ }
+
+ if (min_first_time_s == LONG_MAX)
+ min_first_time_s = 0;
+
+ netdata_spinlock_lock(&metric->spinlock);
+ if (--countdown && !min_first_time_s && metric->latest_time_s_hot)
+ do_again = true;
+ else {
+ internal_error(!countdown, "METRIC: giving up on updating the retention of metric without disk retention");
+
+ do_again = false;
+ metric->first_time_s = min_first_time_s;
+ metric->latest_time_s_clean = max_end_time_s;
+
+ ret = metric_has_retention_unsafe(mrg, metric);
+ }
+ netdata_spinlock_unlock(&metric->spinlock);
+ } while(do_again);
+
+ return ret;
+}
+
+bool mrg_metric_set_hot_latest_time_s(MRG *mrg __maybe_unused, METRIC *metric, time_t latest_time_s) {
+// internal_fatal(latest_time_s > max_acceptable_collected_time(),
+// "DBENGINE METRIC: metric latest time is in the future");
+
+ netdata_spinlock_lock(&metric->spinlock);
+ metric->latest_time_s_hot = latest_time_s;
+
+ if(unlikely(!metric->first_time_s))
+ metric->first_time_s = latest_time_s;
+
+// if(unlikely(metric->first_time_s > latest_time_s))
+// metric->first_time_s = latest_time_s;
+
+ metric_has_retention_unsafe(mrg, metric);
+ netdata_spinlock_unlock(&metric->spinlock);
+ return true;
+}
+
+time_t mrg_metric_get_latest_time_s(MRG *mrg __maybe_unused, METRIC *metric) {
+ time_t max;
+ netdata_spinlock_lock(&metric->spinlock);
+ max = MAX(metric->latest_time_s_clean, metric->latest_time_s_hot);
+ netdata_spinlock_unlock(&metric->spinlock);
+ return max;
+}
+
+bool mrg_metric_set_update_every(MRG *mrg __maybe_unused, METRIC *metric, time_t update_every_s) {
+ if(!update_every_s)
+ return false;
+
+ netdata_spinlock_lock(&metric->spinlock);
+ metric->latest_update_every_s = update_every_s;
+ netdata_spinlock_unlock(&metric->spinlock);
+
+ return true;
+}
+
+bool mrg_metric_set_update_every_s_if_zero(MRG *mrg __maybe_unused, METRIC *metric, time_t update_every_s) {
+ if(!update_every_s)
+ return false;
+
+ netdata_spinlock_lock(&metric->spinlock);
+ if(!metric->latest_update_every_s)
+ metric->latest_update_every_s = update_every_s;
+ netdata_spinlock_unlock(&metric->spinlock);
+
+ return true;
+}
+
+time_t mrg_metric_get_update_every_s(MRG *mrg __maybe_unused, METRIC *metric) {
+ time_t update_every_s;
+
+ netdata_spinlock_lock(&metric->spinlock);
+ update_every_s = metric->latest_update_every_s;
+ netdata_spinlock_unlock(&metric->spinlock);
+
+ return update_every_s;
+}
+
+bool mrg_metric_set_writer(MRG *mrg, METRIC *metric) {
+ bool done = false;
+ netdata_spinlock_lock(&metric->spinlock);
+ if(!metric->writer) {
+ metric->writer = gettid();
+ __atomic_add_fetch(&mrg->stats.writers, 1, __ATOMIC_RELAXED);
+ done = true;
+ }
+ else
+ __atomic_add_fetch(&mrg->stats.writers_conflicts, 1, __ATOMIC_RELAXED);
+ netdata_spinlock_unlock(&metric->spinlock);
+ return done;
+}
+
+bool mrg_metric_clear_writer(MRG *mrg, METRIC *metric) {
+ bool done = false;
+ netdata_spinlock_lock(&metric->spinlock);
+ if(metric->writer) {
+ metric->writer = 0;
+ __atomic_sub_fetch(&mrg->stats.writers, 1, __ATOMIC_RELAXED);
+ done = true;
+ }
+ netdata_spinlock_unlock(&metric->spinlock);
+ return done;
+}
+
+struct mrg_statistics mrg_get_statistics(MRG *mrg) {
+ // FIXME - use atomics
+ return mrg->stats;
+}
+
+// ----------------------------------------------------------------------------
+// unit test
+
+#ifdef MRG_STRESS_TEST
+
+static void mrg_stress(MRG *mrg, size_t entries, size_t sections) {
+ bool ret;
+
+ info("DBENGINE METRIC: stress testing %zu entries on %zu sections...", entries, sections);
+
+ METRIC *array[entries][sections];
+ for(size_t i = 0; i < entries ; i++) {
+ MRG_ENTRY e = {
+ .first_time_s = (time_t)(i + 1),
+ .latest_time_s = (time_t)(i + 2),
+ .latest_update_every_s = (time_t)(i + 3),
+ };
+ uuid_generate_random(e.uuid);
+
+ for(size_t section = 0; section < sections ;section++) {
+ e.section = section;
+ array[i][section] = mrg_metric_add_and_acquire(mrg, e, &ret);
+ if(!ret)
+ fatal("DBENGINE METRIC: failed to add metric %zu, section %zu", i, section);
+
+ if(mrg_metric_add_and_acquire(mrg, e, &ret) != array[i][section])
+ fatal("DBENGINE METRIC: adding the same metric twice, returns a different metric");
+
+ if(ret)
+ fatal("DBENGINE METRIC: adding the same metric twice, returns success");
+
+ if(mrg_metric_get_and_acquire(mrg, &e.uuid, e.section) != array[i][section])
+ fatal("DBENGINE METRIC: cannot get back the same metric");
+
+ if(uuid_compare(*mrg_metric_uuid(mrg, array[i][section]), e.uuid) != 0)
+ fatal("DBENGINE METRIC: uuids do not match");
+ }
+ }
+
+ for(size_t i = 0; i < entries ; i++) {
+ for (size_t section = 0; section < sections; section++) {
+ uuid_t uuid;
+ uuid_generate_random(uuid);
+
+ if(mrg_metric_get_and_acquire(mrg, &uuid, section))
+ fatal("DBENGINE METRIC: found non-existing uuid");
+
+ if(mrg_metric_id(mrg, array[i][section]) != (Word_t)array[i][section])
+ fatal("DBENGINE METRIC: metric id does not match");
+
+ if(mrg_metric_get_first_time_s(mrg, array[i][section]) != (time_t)(i + 1))
+ fatal("DBENGINE METRIC: wrong first time returned");
+ if(mrg_metric_get_latest_time_s(mrg, array[i][section]) != (time_t)(i + 2))
+ fatal("DBENGINE METRIC: wrong latest time returned");
+ if(mrg_metric_get_update_every_s(mrg, array[i][section]) != (time_t)(i + 3))
+ fatal("DBENGINE METRIC: wrong latest time returned");
+
+ if(!mrg_metric_set_first_time_s(mrg, array[i][section], (time_t)((i + 1) * 2)))
+ fatal("DBENGINE METRIC: cannot set first time");
+ if(!mrg_metric_set_clean_latest_time_s(mrg, array[i][section], (time_t) ((i + 1) * 3)))
+ fatal("DBENGINE METRIC: cannot set latest time");
+ if(!mrg_metric_set_update_every(mrg, array[i][section], (time_t)((i + 1) * 4)))
+ fatal("DBENGINE METRIC: cannot set update every");
+
+ if(mrg_metric_get_first_time_s(mrg, array[i][section]) != (time_t)((i + 1) * 2))
+ fatal("DBENGINE METRIC: wrong first time returned");
+ if(mrg_metric_get_latest_time_s(mrg, array[i][section]) != (time_t)((i + 1) * 3))
+ fatal("DBENGINE METRIC: wrong latest time returned");
+ if(mrg_metric_get_update_every_s(mrg, array[i][section]) != (time_t)((i + 1) * 4))
+ fatal("DBENGINE METRIC: wrong latest time returned");
+ }
+ }
+
+ for(size_t i = 0; i < entries ; i++) {
+ for (size_t section = 0; section < sections; section++) {
+ if(!mrg_metric_release_and_delete(mrg, array[i][section]))
+ fatal("DBENGINE METRIC: failed to delete metric");
+ }
+ }
+}
+
+static void *mrg_stress_test_thread1(void *ptr) {
+ MRG *mrg = ptr;
+
+ for(int i = 0; i < 5 ; i++)
+ mrg_stress(mrg, 10000, 5);
+
+ return ptr;
+}
+
+static void *mrg_stress_test_thread2(void *ptr) {
+ MRG *mrg = ptr;
+
+ for(int i = 0; i < 10 ; i++)
+ mrg_stress(mrg, 500, 50);
+
+ return ptr;
+}
+
+static void *mrg_stress_test_thread3(void *ptr) {
+ MRG *mrg = ptr;
+
+ for(int i = 0; i < 50 ; i++)
+ mrg_stress(mrg, 5000, 1);
+
+ return ptr;
+}
+#endif
+
+int mrg_unittest(void) {
+ MRG *mrg = mrg_create();
+ METRIC *m1_t0, *m2_t0, *m3_t0, *m4_t0;
+ METRIC *m1_t1, *m2_t1, *m3_t1, *m4_t1;
+ bool ret;
+
+ MRG_ENTRY entry = {
+ .section = 0,
+ .first_time_s = 2,
+ .last_time_s = 3,
+ .latest_update_every_s = 4,
+ };
+ uuid_generate(entry.uuid);
+ m1_t0 = mrg_metric_add_and_acquire(mrg, entry, &ret);
+ if(!ret)
+ fatal("DBENGINE METRIC: failed to add metric");
+
+ // add the same metric again
+ m2_t0 = mrg_metric_add_and_acquire(mrg, entry, &ret);
+ if(m2_t0 != m1_t0)
+ fatal("DBENGINE METRIC: adding the same metric twice, does not return the same pointer");
+ if(ret)
+ fatal("DBENGINE METRIC: managed to add the same metric twice");
+
+ m3_t0 = mrg_metric_get_and_acquire(mrg, &entry.uuid, entry.section);
+ if(m3_t0 != m1_t0)
+ fatal("DBENGINE METRIC: cannot find the metric added");
+
+ // add the same metric again
+ m4_t0 = mrg_metric_add_and_acquire(mrg, entry, &ret);
+ if(m4_t0 != m1_t0)
+ fatal("DBENGINE METRIC: adding the same metric twice, does not return the same pointer");
+ if(ret)
+ fatal("DBENGINE METRIC: managed to add the same metric twice");
+
+ // add the same metric in another section
+ entry.section = 1;
+ m1_t1 = mrg_metric_add_and_acquire(mrg, entry, &ret);
+ if(!ret)
+ fatal("DBENGINE METRIC: failed to add metric in section %zu", (size_t)entry.section);
+
+ // add the same metric again
+ m2_t1 = mrg_metric_add_and_acquire(mrg, entry, &ret);
+ if(m2_t1 != m1_t1)
+ fatal("DBENGINE METRIC: adding the same metric twice (section %zu), does not return the same pointer", (size_t)entry.section);
+ if(ret)
+ fatal("DBENGINE METRIC: managed to add the same metric twice in (section 0)");
+
+ m3_t1 = mrg_metric_get_and_acquire(mrg, &entry.uuid, entry.section);
+ if(m3_t1 != m1_t1)
+ fatal("DBENGINE METRIC: cannot find the metric added (section %zu)", (size_t)entry.section);
+
+ // delete the first metric
+ mrg_metric_release(mrg, m2_t0);
+ mrg_metric_release(mrg, m3_t0);
+ mrg_metric_release(mrg, m4_t0);
+ mrg_metric_set_first_time_s(mrg, m1_t0, 0);
+ mrg_metric_set_clean_latest_time_s(mrg, m1_t0, 0);
+ mrg_metric_set_hot_latest_time_s(mrg, m1_t0, 0);
+ if(!mrg_metric_release_and_delete(mrg, m1_t0))
+ fatal("DBENGINE METRIC: cannot delete the first metric");
+
+ m4_t1 = mrg_metric_get_and_acquire(mrg, &entry.uuid, entry.section);
+ if(m4_t1 != m1_t1)
+ fatal("DBENGINE METRIC: cannot find the metric added (section %zu), after deleting the first one", (size_t)entry.section);
+
+ // delete the second metric
+ mrg_metric_release(mrg, m2_t1);
+ mrg_metric_release(mrg, m3_t1);
+ mrg_metric_release(mrg, m4_t1);
+ mrg_metric_set_first_time_s(mrg, m1_t1, 0);
+ mrg_metric_set_clean_latest_time_s(mrg, m1_t1, 0);
+ mrg_metric_set_hot_latest_time_s(mrg, m1_t1, 0);
+ if(!mrg_metric_release_and_delete(mrg, m1_t1))
+ fatal("DBENGINE METRIC: cannot delete the second metric");
+
+ if(mrg->stats.entries != 0)
+ fatal("DBENGINE METRIC: invalid entries counter");
+
+#ifdef MRG_STRESS_TEST
+ usec_t started_ut = now_monotonic_usec();
+ pthread_t thread1;
+ netdata_thread_create(&thread1, "TH1",
+ NETDATA_THREAD_OPTION_JOINABLE | NETDATA_THREAD_OPTION_DONT_LOG,
+ mrg_stress_test_thread1, mrg);
+
+ pthread_t thread2;
+ netdata_thread_create(&thread2, "TH2",
+ NETDATA_THREAD_OPTION_JOINABLE | NETDATA_THREAD_OPTION_DONT_LOG,
+ mrg_stress_test_thread2, mrg);
+
+ pthread_t thread3;
+ netdata_thread_create(&thread3, "TH3",
+ NETDATA_THREAD_OPTION_JOINABLE | NETDATA_THREAD_OPTION_DONT_LOG,
+ mrg_stress_test_thread3, mrg);
+
+
+ sleep_usec(5 * USEC_PER_SEC);
+
+ netdata_thread_cancel(thread1);
+ netdata_thread_cancel(thread2);
+ netdata_thread_cancel(thread3);
+
+ netdata_thread_join(thread1, NULL);
+ netdata_thread_join(thread2, NULL);
+ netdata_thread_join(thread3, NULL);
+ usec_t ended_ut = now_monotonic_usec();
+
+ info("DBENGINE METRIC: did %zu additions, %zu duplicate additions, "
+ "%zu deletions, %zu wrong deletions, "
+ "%zu successful searches, %zu wrong searches, "
+ "%zu successful pointer validations, %zu wrong pointer validations "
+ "in %llu usecs",
+ mrg->stats.additions, mrg->stats.additions_duplicate,
+ mrg->stats.deletions, mrg->stats.delete_misses,
+ mrg->stats.search_hits, mrg->stats.search_misses,
+ mrg->stats.pointer_validation_hits, mrg->stats.pointer_validation_misses,
+ ended_ut - started_ut);
+
+#endif
+
+ mrg_destroy(mrg);
+
+ info("DBENGINE METRIC: all tests passed!");
+
+ return 0;
+}
diff --git a/database/engine/metric.h b/database/engine/metric.h
new file mode 100644
index 00000000..82aff903
--- /dev/null
+++ b/database/engine/metric.h
@@ -0,0 +1,79 @@
+#ifndef DBENGINE_METRIC_H
+#define DBENGINE_METRIC_H
+
+#include "../rrd.h"
+
+#define MRG_PARTITIONS 10
+
+typedef struct metric METRIC;
+typedef struct mrg MRG;
+
+typedef struct mrg_entry {
+ uuid_t uuid;
+ Word_t section;
+ time_t first_time_s;
+ time_t last_time_s;
+ uint32_t latest_update_every_s;
+} MRG_ENTRY;
+
+struct mrg_statistics {
+ size_t entries;
+ size_t entries_referenced;
+ size_t entries_with_retention;
+
+ size_t size; // total memory used, with indexing
+
+ size_t current_references;
+
+ size_t additions;
+ size_t additions_duplicate;
+
+ size_t deletions;
+ size_t delete_having_retention_or_referenced;
+ size_t delete_misses;
+
+ size_t search_hits;
+ size_t search_misses;
+
+ size_t writers;
+ size_t writers_conflicts;
+};
+
+MRG *mrg_create(void);
+void mrg_destroy(MRG *mrg);
+
+METRIC *mrg_metric_dup(MRG *mrg, METRIC *metric);
+bool mrg_metric_release(MRG *mrg, METRIC *metric);
+
+METRIC *mrg_metric_add_and_acquire(MRG *mrg, MRG_ENTRY entry, bool *ret);
+METRIC *mrg_metric_get_and_acquire(MRG *mrg, uuid_t *uuid, Word_t section);
+bool mrg_metric_release_and_delete(MRG *mrg, METRIC *metric);
+
+Word_t mrg_metric_id(MRG *mrg, METRIC *metric);
+uuid_t *mrg_metric_uuid(MRG *mrg, METRIC *metric);
+Word_t mrg_metric_section(MRG *mrg, METRIC *metric);
+
+bool mrg_metric_set_first_time_s(MRG *mrg, METRIC *metric, time_t first_time_s);
+bool mrg_metric_set_first_time_s_if_bigger(MRG *mrg, METRIC *metric, time_t first_time_s);
+time_t mrg_metric_get_first_time_s(MRG *mrg, METRIC *metric);
+
+bool mrg_metric_set_clean_latest_time_s(MRG *mrg, METRIC *metric, time_t latest_time_s);
+bool mrg_metric_set_hot_latest_time_s(MRG *mrg, METRIC *metric, time_t latest_time_s);
+time_t mrg_metric_get_latest_time_s(MRG *mrg, METRIC *metric);
+
+bool mrg_metric_set_update_every(MRG *mrg, METRIC *metric, time_t update_every_s);
+bool mrg_metric_set_update_every_s_if_zero(MRG *mrg, METRIC *metric, time_t update_every_s);
+time_t mrg_metric_get_update_every_s(MRG *mrg, METRIC *metric);
+
+void mrg_metric_expand_retention(MRG *mrg, METRIC *metric, time_t first_time_s, time_t last_time_s, time_t update_every_s);
+void mrg_metric_get_retention(MRG *mrg, METRIC *metric, time_t *first_time_s, time_t *last_time_s, time_t *update_every_s);
+bool mrg_metric_zero_disk_retention(MRG *mrg __maybe_unused, METRIC *metric);
+
+bool mrg_metric_set_writer(MRG *mrg, METRIC *metric);
+bool mrg_metric_clear_writer(MRG *mrg, METRIC *metric);
+
+struct mrg_statistics mrg_get_statistics(MRG *mrg);
+size_t mrg_aral_structures(void);
+size_t mrg_aral_overhead(void);
+
+#endif // DBENGINE_METRIC_H
diff --git a/database/engine/pagecache.c b/database/engine/pagecache.c
index 4f5da708..b4902d78 100644
--- a/database/engine/pagecache.c
+++ b/database/engine/pagecache.c
@@ -3,1084 +3,836 @@
#include "rrdengine.h"
-ARAL page_descr_aral = {
- .requested_element_size = sizeof(struct rrdeng_page_descr),
- .initial_elements = 20000,
- .filename = "page_descriptors",
- .cache_dir = &netdata_configured_cache_dir,
- .use_mmap = false,
- .internal.initialized = false
-};
-
-void rrdeng_page_descr_aral_go_singlethreaded(void) {
- page_descr_aral.internal.lockless = true;
-}
-void rrdeng_page_descr_aral_go_multithreaded(void) {
- page_descr_aral.internal.lockless = false;
-}
+MRG *main_mrg = NULL;
+PGC *main_cache = NULL;
+PGC *open_cache = NULL;
+PGC *extent_cache = NULL;
+struct rrdeng_cache_efficiency_stats rrdeng_cache_efficiency_stats = {};
-struct rrdeng_page_descr *rrdeng_page_descr_mallocz(void) {
- struct rrdeng_page_descr *descr;
- descr = arrayalloc_mallocz(&page_descr_aral);
- return descr;
+static void main_cache_free_clean_page_callback(PGC *cache __maybe_unused, PGC_ENTRY entry __maybe_unused)
+{
+ // Release storage associated with the page
+ dbengine_page_free(entry.data, entry.size);
}
+static void main_cache_flush_dirty_page_init_callback(PGC *cache __maybe_unused, Word_t section) {
+ struct rrdengine_instance *ctx = (struct rrdengine_instance *) section;
-void rrdeng_page_descr_freez(struct rrdeng_page_descr *descr) {
- arrayalloc_freez(&page_descr_aral, descr);
+ // mark ctx as having flushing in progress
+ __atomic_add_fetch(&ctx->atomic.extents_currently_being_flushed, 1, __ATOMIC_RELAXED);
}
-void rrdeng_page_descr_use_malloc(void) {
- if(page_descr_aral.internal.initialized)
- error("DBENGINE: cannot change ARAL allocation policy after it has been initialized.");
- else
- page_descr_aral.use_mmap = false;
-}
+static void main_cache_flush_dirty_page_callback(PGC *cache __maybe_unused, PGC_ENTRY *entries_array __maybe_unused, PGC_PAGE **pages_array __maybe_unused, size_t entries __maybe_unused)
+{
+ if(!entries)
+ return;
-void rrdeng_page_descr_use_mmap(void) {
- if(page_descr_aral.internal.initialized)
- error("DBENGINE: cannot change ARAL allocation policy after it has been initialized.");
- else
- page_descr_aral.use_mmap = true;
-}
+ struct rrdengine_instance *ctx = (struct rrdengine_instance *) entries_array[0].section;
-bool rrdeng_page_descr_is_mmap(void) {
- return page_descr_aral.use_mmap;
-}
+ size_t bytes_per_point = CTX_POINT_SIZE_BYTES(ctx);
-/* Forward declarations */
-static int pg_cache_try_evict_one_page_unsafe(struct rrdengine_instance *ctx);
+ struct page_descr_with_data *base = NULL;
-/* always inserts into tail */
-static inline void pg_cache_replaceQ_insert_unsafe(struct rrdengine_instance *ctx,
- struct rrdeng_page_descr *descr)
-{
- struct page_cache *pg_cache = &ctx->pg_cache;
- struct page_cache_descr *pg_cache_descr = descr->pg_cache_descr;
+ for (size_t Index = 0 ; Index < entries; Index++) {
+ time_t start_time_s = entries_array[Index].start_time_s;
+ time_t end_time_s = entries_array[Index].end_time_s;
+ struct page_descr_with_data *descr = page_descriptor_get();
- if (likely(NULL != pg_cache->replaceQ.tail)) {
- pg_cache_descr->prev = pg_cache->replaceQ.tail;
- pg_cache->replaceQ.tail->next = pg_cache_descr;
- }
- if (unlikely(NULL == pg_cache->replaceQ.head)) {
- pg_cache->replaceQ.head = pg_cache_descr;
- }
- pg_cache->replaceQ.tail = pg_cache_descr;
-}
-
-static inline void pg_cache_replaceQ_delete_unsafe(struct rrdengine_instance *ctx,
- struct rrdeng_page_descr *descr)
-{
- struct page_cache *pg_cache = &ctx->pg_cache;
- struct page_cache_descr *pg_cache_descr = descr->pg_cache_descr, *prev, *next;
+ descr->id = mrg_metric_uuid(main_mrg, (METRIC *) entries_array[Index].metric_id);
+ descr->metric_id = entries_array[Index].metric_id;
+ descr->start_time_ut = start_time_s * USEC_PER_SEC;
+ descr->end_time_ut = end_time_s * USEC_PER_SEC;
+ descr->update_every_s = entries_array[Index].update_every_s;
+ descr->type = ctx->config.page_type;
- prev = pg_cache_descr->prev;
- next = pg_cache_descr->next;
+ descr->page_length = (end_time_s - (start_time_s - descr->update_every_s)) / descr->update_every_s * bytes_per_point;
- if (likely(NULL != prev)) {
- prev->next = next;
- }
- if (likely(NULL != next)) {
- next->prev = prev;
- }
- if (unlikely(pg_cache_descr == pg_cache->replaceQ.head)) {
- pg_cache->replaceQ.head = next;
- }
- if (unlikely(pg_cache_descr == pg_cache->replaceQ.tail)) {
- pg_cache->replaceQ.tail = prev;
- }
- pg_cache_descr->prev = pg_cache_descr->next = NULL;
-}
+ if(descr->page_length > entries_array[Index].size) {
+ descr->page_length = entries_array[Index].size;
-void pg_cache_replaceQ_insert(struct rrdengine_instance *ctx,
- struct rrdeng_page_descr *descr)
-{
- struct page_cache *pg_cache = &ctx->pg_cache;
-
- uv_rwlock_wrlock(&pg_cache->replaceQ.lock);
- pg_cache_replaceQ_insert_unsafe(ctx, descr);
- uv_rwlock_wrunlock(&pg_cache->replaceQ.lock);
-}
+ error_limit_static_global_var(erl, 1, 0);
+ error_limit(&erl, "DBENGINE: page exceeds the maximum size, adjusting it to max.");
+ }
-void pg_cache_replaceQ_delete(struct rrdengine_instance *ctx,
- struct rrdeng_page_descr *descr)
-{
- struct page_cache *pg_cache = &ctx->pg_cache;
+ descr->page = pgc_page_data(pages_array[Index]);
+ DOUBLE_LINKED_LIST_APPEND_ITEM_UNSAFE(base, descr, link.prev, link.next);
- uv_rwlock_wrlock(&pg_cache->replaceQ.lock);
- pg_cache_replaceQ_delete_unsafe(ctx, descr);
- uv_rwlock_wrunlock(&pg_cache->replaceQ.lock);
-}
-void pg_cache_replaceQ_set_hot(struct rrdengine_instance *ctx,
- struct rrdeng_page_descr *descr)
-{
- struct page_cache *pg_cache = &ctx->pg_cache;
+ internal_fatal(descr->page_length > RRDENG_BLOCK_SIZE, "DBENGINE: faulty page length calculation");
+ }
- uv_rwlock_wrlock(&pg_cache->replaceQ.lock);
- pg_cache_replaceQ_delete_unsafe(ctx, descr);
- pg_cache_replaceQ_insert_unsafe(ctx, descr);
- uv_rwlock_wrunlock(&pg_cache->replaceQ.lock);
+ struct completion completion;
+ completion_init(&completion);
+ rrdeng_enq_cmd(ctx, RRDENG_OPCODE_EXTENT_WRITE, base, &completion, STORAGE_PRIORITY_INTERNAL_DBENGINE, NULL, NULL);
+ completion_wait_for(&completion);
+ completion_destroy(&completion);
}
-struct rrdeng_page_descr *pg_cache_create_descr(void)
+static void open_cache_free_clean_page_callback(PGC *cache __maybe_unused, PGC_ENTRY entry __maybe_unused)
{
- struct rrdeng_page_descr *descr;
-
- descr = rrdeng_page_descr_mallocz();
- descr->page_length = 0;
- descr->start_time_ut = INVALID_TIME;
- descr->end_time_ut = INVALID_TIME;
- descr->id = NULL;
- descr->extent = NULL;
- descr->pg_cache_descr_state = 0;
- descr->pg_cache_descr = NULL;
- descr->update_every_s = 0;
-
- return descr;
+ struct rrdengine_datafile *datafile = entry.data;
+ datafile_release(datafile, DATAFILE_ACQUIRE_OPEN_CACHE);
}
-/* The caller must hold page descriptor lock. */
-void pg_cache_wake_up_waiters_unsafe(struct rrdeng_page_descr *descr)
+static void open_cache_flush_dirty_page_callback(PGC *cache __maybe_unused, PGC_ENTRY *entries_array __maybe_unused, PGC_PAGE **pages_array __maybe_unused, size_t entries __maybe_unused)
{
- struct page_cache_descr *pg_cache_descr = descr->pg_cache_descr;
- if (pg_cache_descr->waiters)
- uv_cond_broadcast(&pg_cache_descr->cond);
+ ;
}
-void pg_cache_wake_up_waiters(struct rrdengine_instance *ctx, struct rrdeng_page_descr *descr)
+static void extent_cache_free_clean_page_callback(PGC *cache __maybe_unused, PGC_ENTRY entry __maybe_unused)
{
- rrdeng_page_descr_mutex_lock(ctx, descr);
- pg_cache_wake_up_waiters_unsafe(descr);
- rrdeng_page_descr_mutex_unlock(ctx, descr);
+ dbengine_extent_free(entry.data, entry.size);
}
-/*
- * The caller must hold page descriptor lock.
- * The lock will be released and re-acquired. The descriptor is not guaranteed
- * to exist after this function returns.
- */
-void pg_cache_wait_event_unsafe(struct rrdeng_page_descr *descr)
+static void extent_cache_flush_dirty_page_callback(PGC *cache __maybe_unused, PGC_ENTRY *entries_array __maybe_unused, PGC_PAGE **pages_array __maybe_unused, size_t entries __maybe_unused)
{
- struct page_cache_descr *pg_cache_descr = descr->pg_cache_descr;
-
- ++pg_cache_descr->waiters;
- uv_cond_wait(&pg_cache_descr->cond, &pg_cache_descr->mutex);
- --pg_cache_descr->waiters;
+ ;
}
-/*
- * The caller must hold page descriptor lock.
- * The lock will be released and re-acquired. The descriptor is not guaranteed
- * to exist after this function returns.
- * Returns UV_ETIMEDOUT if timeout_sec seconds pass.
- */
-int pg_cache_timedwait_event_unsafe(struct rrdeng_page_descr *descr, uint64_t timeout_sec)
-{
- int ret;
- struct page_cache_descr *pg_cache_descr = descr->pg_cache_descr;
+inline TIME_RANGE_COMPARE is_page_in_time_range(time_t page_first_time_s, time_t page_last_time_s, time_t wanted_start_time_s, time_t wanted_end_time_s) {
+ // page_first_time_s <= wanted_end_time_s && page_last_time_s >= wanted_start_time_s
+
+ if(page_last_time_s < wanted_start_time_s)
+ return PAGE_IS_IN_THE_PAST;
- ++pg_cache_descr->waiters;
- ret = uv_cond_timedwait(&pg_cache_descr->cond, &pg_cache_descr->mutex, timeout_sec * NSEC_PER_SEC);
- --pg_cache_descr->waiters;
+ if(page_first_time_s > wanted_end_time_s)
+ return PAGE_IS_IN_THE_FUTURE;
- return ret;
+ return PAGE_IS_IN_RANGE;
}
-/*
- * Returns page flags.
- * The lock will be released and re-acquired. The descriptor is not guaranteed
- * to exist after this function returns.
- */
-unsigned long pg_cache_wait_event(struct rrdengine_instance *ctx, struct rrdeng_page_descr *descr)
+static int journal_metric_uuid_compare(const void *key, const void *metric)
{
- struct page_cache_descr *pg_cache_descr = descr->pg_cache_descr;
- unsigned long flags;
-
- rrdeng_page_descr_mutex_lock(ctx, descr);
- pg_cache_wait_event_unsafe(descr);
- flags = pg_cache_descr->flags;
- rrdeng_page_descr_mutex_unlock(ctx, descr);
-
- return flags;
+ return uuid_compare(*(uuid_t *) key, ((struct journal_metric_list *) metric)->uuid);
}
-/*
- * The caller must hold page descriptor lock.
- */
-int pg_cache_can_get_unsafe(struct rrdeng_page_descr *descr, int exclusive_access)
-{
- struct page_cache_descr *pg_cache_descr = descr->pg_cache_descr;
+static inline struct page_details *pdc_find_page_for_time(
+ Pcvoid_t PArray,
+ time_t wanted_time_s,
+ size_t *gaps,
+ PDC_PAGE_STATUS mode,
+ PDC_PAGE_STATUS skip_list
+) {
+ Word_t PIndexF = wanted_time_s, PIndexL = wanted_time_s;
+ Pvoid_t *PValueF, *PValueL;
+ struct page_details *pdF = NULL, *pdL = NULL;
+ bool firstF = true, firstL = true;
+
+ PDC_PAGE_STATUS ignore_list = PDC_PAGE_QUERY_GLOBAL_SKIP_LIST | skip_list;
+
+ while ((PValueF = PDCJudyLFirstThenNext(PArray, &PIndexF, &firstF))) {
+ pdF = *PValueF;
+
+ PDC_PAGE_STATUS status = __atomic_load_n(&pdF->status, __ATOMIC_ACQUIRE);
+ if (!(status & (ignore_list | mode)))
+ break;
- if ((pg_cache_descr->flags & (RRD_PAGE_LOCKED | RRD_PAGE_READ_PENDING)) ||
- (exclusive_access && pg_cache_descr->refcnt)) {
- return 0;
+ pdF = NULL;
}
- return 1;
-}
+ while ((PValueL = PDCJudyLLastThenPrev(PArray, &PIndexL, &firstL))) {
+ pdL = *PValueL;
-/*
- * The caller must hold page descriptor lock.
- * Gets a reference to the page descriptor.
- * Returns 1 on success and 0 on failure.
- */
-int pg_cache_try_get_unsafe(struct rrdeng_page_descr *descr, int exclusive_access)
-{
- struct page_cache_descr *pg_cache_descr = descr->pg_cache_descr;
+ PDC_PAGE_STATUS status = __atomic_load_n(&pdL->status, __ATOMIC_ACQUIRE);
+ if(status & mode) {
+ // don't go all the way back to the beginning
+ // stop at the last processed
+ pdL = NULL;
+ break;
+ }
- if (!pg_cache_can_get_unsafe(descr, exclusive_access))
- return 0;
+ if (!(status & ignore_list))
+ break;
- if (exclusive_access)
- pg_cache_descr->flags |= RRD_PAGE_LOCKED;
- ++pg_cache_descr->refcnt;
+ pdL = NULL;
+ }
- return 1;
-}
+ TIME_RANGE_COMPARE rcF = (pdF) ? is_page_in_time_range(pdF->first_time_s, pdF->last_time_s, wanted_time_s, wanted_time_s) : PAGE_IS_IN_THE_FUTURE;
+ TIME_RANGE_COMPARE rcL = (pdL) ? is_page_in_time_range(pdL->first_time_s, pdL->last_time_s, wanted_time_s, wanted_time_s) : PAGE_IS_IN_THE_PAST;
-/*
- * The caller must hold the page descriptor lock.
- * This function may block doing cleanup.
- */
-void pg_cache_put_unsafe(struct rrdeng_page_descr *descr)
-{
- struct page_cache_descr *pg_cache_descr = descr->pg_cache_descr;
+ if (!pdF || pdF == pdL) {
+ // F is missing, or they are the same
+ // return L
+ (*gaps) += (rcL == PAGE_IS_IN_RANGE) ? 0 : 1;
+ return pdL;
+ }
- pg_cache_descr->flags &= ~RRD_PAGE_LOCKED;
- if (0 == --pg_cache_descr->refcnt) {
- pg_cache_wake_up_waiters_unsafe(descr);
+ if (!pdL) {
+ // L is missing
+ // return F
+ (*gaps) += (rcF == PAGE_IS_IN_RANGE) ? 0 : 1;
+ return pdF;
}
-}
-/*
- * This function may block doing cleanup.
- */
-void pg_cache_put(struct rrdengine_instance *ctx, struct rrdeng_page_descr *descr)
-{
- rrdeng_page_descr_mutex_lock(ctx, descr);
- pg_cache_put_unsafe(descr);
- rrdeng_page_descr_mutex_unlock(ctx, descr);
-}
+ if (rcF == rcL) {
+ // both are on the same side,
+ // but they are different pages
-/* The caller must hold the page cache lock */
-static void pg_cache_release_pages_unsafe(struct rrdengine_instance *ctx, unsigned number)
-{
- struct page_cache *pg_cache = &ctx->pg_cache;
+ switch (rcF) {
+ case PAGE_IS_IN_RANGE:
+ // pick the higher resolution
+ if (pdF->update_every_s && pdF->update_every_s < pdL->update_every_s)
+ return pdF;
- pg_cache->populated_pages -= number;
-}
+ if (pdL->update_every_s && pdL->update_every_s < pdF->update_every_s)
+ return pdL;
-static void pg_cache_release_pages(struct rrdengine_instance *ctx, unsigned number)
-{
- struct page_cache *pg_cache = &ctx->pg_cache;
+ // same resolution - pick the one that starts earlier
+ if (pdL->first_time_s < pdF->first_time_s)
+ return pdL;
- uv_rwlock_wrlock(&pg_cache->pg_cache_rwlock);
- pg_cache_release_pages_unsafe(ctx, number);
- uv_rwlock_wrunlock(&pg_cache->pg_cache_rwlock);
-}
+ return pdF;
+ break;
-/*
- * This function returns the maximum number of pages allowed in the page cache.
- */
-unsigned long pg_cache_hard_limit(struct rrdengine_instance *ctx)
-{
- return ctx->max_cache_pages + (unsigned long)ctx->metric_API_max_producers;
-}
+ case PAGE_IS_IN_THE_FUTURE:
+ (*gaps)++;
-/*
- * This function returns the low watermark number of pages in the page cache. The page cache should strive to keep the
- * number of pages below that number.
- */
-unsigned long pg_cache_soft_limit(struct rrdengine_instance *ctx)
-{
- return ctx->cache_pages_low_watermark + (unsigned long)ctx->metric_API_max_producers;
-}
+ // pick the one that starts earlier
+ if (pdL->first_time_s < pdF->first_time_s)
+ return pdL;
-/*
- * This function returns the maximum number of dirty pages that are committed to be written to disk allowed in the page
- * cache.
- */
-unsigned long pg_cache_committed_hard_limit(struct rrdengine_instance *ctx)
-{
- /* We remove the active pages of the producers from the calculation and only allow the extra pinned pages */
- return ctx->cache_pages_low_watermark + (unsigned long)ctx->metric_API_max_producers;
-}
+ return pdF;
+ break;
-/*
- * This function will block until it reserves #number populated pages.
- * It will trigger evictions or dirty page flushing if the pg_cache_hard_limit() limit is hit.
- */
-static void pg_cache_reserve_pages(struct rrdengine_instance *ctx, unsigned number)
-{
- struct page_cache *pg_cache = &ctx->pg_cache;
- unsigned failures = 0;
- const unsigned FAILURES_CEILING = 10; /* truncates exponential backoff to (2^FAILURES_CEILING x slot) */
- unsigned long exp_backoff_slot_usec = USEC_PER_MS * 10;
-
- assert(number < ctx->max_cache_pages);
-
- uv_rwlock_wrlock(&pg_cache->pg_cache_rwlock);
- if (pg_cache->populated_pages + number >= pg_cache_hard_limit(ctx) + 1)
- debug(D_RRDENGINE, "==Page cache full. Reserving %u pages.==",
- number);
- while (pg_cache->populated_pages + number >= pg_cache_hard_limit(ctx) + 1) {
-
- if (!pg_cache_try_evict_one_page_unsafe(ctx)) {
- /* failed to evict */
- struct completion compl;
- struct rrdeng_cmd cmd;
-
- ++failures;
- uv_rwlock_wrunlock(&pg_cache->pg_cache_rwlock);
-
- completion_init(&compl);
- cmd.opcode = RRDENG_FLUSH_PAGES;
- cmd.completion = &compl;
- rrdeng_enq_cmd(&ctx->worker_config, &cmd);
- /* wait for some pages to be flushed */
- debug(D_RRDENGINE, "%s: waiting for pages to be written to disk before evicting.", __func__);
- completion_wait_for(&compl);
- completion_destroy(&compl);
-
- if (unlikely(failures > 1)) {
- unsigned long slots, usecs_to_sleep;
- /* exponential backoff */
- slots = random() % (2LU << MIN(failures, FAILURES_CEILING));
- usecs_to_sleep = slots * exp_backoff_slot_usec;
-
- if (usecs_to_sleep >= USEC_PER_SEC)
- error("Page cache is full. Sleeping for %llu second(s).", usecs_to_sleep / USEC_PER_SEC);
-
- (void)sleep_usec(usecs_to_sleep);
- }
- uv_rwlock_wrlock(&pg_cache->pg_cache_rwlock);
+ default:
+ case PAGE_IS_IN_THE_PAST:
+ (*gaps)++;
+ return NULL;
+ break;
}
}
- pg_cache->populated_pages += number;
- uv_rwlock_wrunlock(&pg_cache->pg_cache_rwlock);
-}
-/*
- * This function will attempt to reserve #number populated pages.
- * It may trigger evictions if the pg_cache_soft_limit() limit is hit.
- * Returns 0 on failure and 1 on success.
- */
-static int pg_cache_try_reserve_pages(struct rrdengine_instance *ctx, unsigned number)
-{
- struct page_cache *pg_cache = &ctx->pg_cache;
- unsigned count = 0;
- int ret = 0;
-
- assert(number < ctx->max_cache_pages);
-
- uv_rwlock_wrlock(&pg_cache->pg_cache_rwlock);
- if (pg_cache->populated_pages + number >= pg_cache_soft_limit(ctx) + 1) {
- debug(D_RRDENGINE,
- "==Page cache full. Trying to reserve %u pages.==",
- number);
- do {
- if (!pg_cache_try_evict_one_page_unsafe(ctx))
- break;
- ++count;
- } while (pg_cache->populated_pages + number >= pg_cache_soft_limit(ctx) + 1);
- debug(D_RRDENGINE, "Evicted %u pages.", count);
+ if(rcF == PAGE_IS_IN_RANGE) {
+ // (*gaps) += 0;
+ return pdF;
}
- if (pg_cache->populated_pages + number < pg_cache_hard_limit(ctx) + 1) {
- pg_cache->populated_pages += number;
- ret = 1; /* success */
+ if(rcL == PAGE_IS_IN_RANGE) {
+ // (*gaps) += 0;
+ return pdL;
}
- uv_rwlock_wrunlock(&pg_cache->pg_cache_rwlock);
- return ret;
-}
+ if(rcF == PAGE_IS_IN_THE_FUTURE) {
+ (*gaps)++;
+ return pdF;
+ }
-/* The caller must hold the page cache and the page descriptor locks in that order */
-static void pg_cache_evict_unsafe(struct rrdengine_instance *ctx, struct rrdeng_page_descr *descr)
-{
- struct page_cache_descr *pg_cache_descr = descr->pg_cache_descr;
+ if(rcL == PAGE_IS_IN_THE_FUTURE) {
+ (*gaps)++;
+ return pdL;
+ }
- dbengine_page_free(pg_cache_descr->page);
- pg_cache_descr->page = NULL;
- pg_cache_descr->flags &= ~RRD_PAGE_POPULATED;
- pg_cache_release_pages_unsafe(ctx, 1);
- ++ctx->stats.pg_cache_evictions;
+ // impossible case
+ (*gaps)++;
+ return NULL;
}
-/*
- * The caller must hold the page cache lock.
- * Lock order: page cache -> replaceQ -> page descriptor
- * This function iterates all pages and tries to evict one.
- * If it fails it sets in_flight_descr to the oldest descriptor that has write-back in progress,
- * or it sets it to NULL if no write-back is in progress.
- *
- * Returns 1 on success and 0 on failure.
- */
-static int pg_cache_try_evict_one_page_unsafe(struct rrdengine_instance *ctx)
-{
- struct page_cache *pg_cache = &ctx->pg_cache;
- unsigned long old_flags;
- struct rrdeng_page_descr *descr;
- struct page_cache_descr *pg_cache_descr = NULL;
+static size_t get_page_list_from_pgc(PGC *cache, METRIC *metric, struct rrdengine_instance *ctx,
+ time_t wanted_start_time_s, time_t wanted_end_time_s,
+ Pvoid_t *JudyL_page_array, size_t *cache_gaps,
+ bool open_cache_mode, PDC_PAGE_STATUS tags) {
- uv_rwlock_wrlock(&pg_cache->replaceQ.lock);
- for (pg_cache_descr = pg_cache->replaceQ.head ; NULL != pg_cache_descr ; pg_cache_descr = pg_cache_descr->next) {
- descr = pg_cache_descr->descr;
+ size_t pages_found_in_cache = 0;
+ Word_t metric_id = mrg_metric_id(main_mrg, metric);
- rrdeng_page_descr_mutex_lock(ctx, descr);
- old_flags = pg_cache_descr->flags;
- if ((old_flags & RRD_PAGE_POPULATED) && !(old_flags & RRD_PAGE_DIRTY) && pg_cache_try_get_unsafe(descr, 1)) {
- /* must evict */
- pg_cache_evict_unsafe(ctx, descr);
- pg_cache_put_unsafe(descr);
- pg_cache_replaceQ_delete_unsafe(ctx, descr);
+ time_t now_s = wanted_start_time_s;
+ time_t dt_s = mrg_metric_get_update_every_s(main_mrg, metric);
- rrdeng_page_descr_mutex_unlock(ctx, descr);
- uv_rwlock_wrunlock(&pg_cache->replaceQ.lock);
+ if(!dt_s)
+ dt_s = default_rrd_update_every;
- rrdeng_try_deallocate_pg_cache_descr(ctx, descr);
+ time_t previous_page_end_time_s = now_s - dt_s;
+ bool first = true;
- return 1;
- }
- rrdeng_page_descr_mutex_unlock(ctx, descr);
- }
- uv_rwlock_wrunlock(&pg_cache->replaceQ.lock);
+ do {
+ PGC_PAGE *page = pgc_page_get_and_acquire(
+ cache, (Word_t)ctx, (Word_t)metric_id, now_s,
+ (first) ? PGC_SEARCH_CLOSEST : PGC_SEARCH_NEXT);
- /* failed to evict */
- return 0;
-}
+ first = false;
-/**
- * Deletes a page from the database.
- * Callers of this function need to make sure they're not deleting the same descriptor concurrently.
- * @param ctx is the database instance.
- * @param descr is the page descriptor.
- * @param remove_dirty must be non-zero if the page to be deleted is dirty.
- * @param is_exclusive_holder must be non-zero if the caller holds an exclusive page reference.
- * @param metric_id is set to the metric the page belongs to, if it's safe to delete the metric and metric_id is not
- * NULL. Otherwise, metric_id is not set.
- * @return 1 if it's safe to delete the metric, 0 otherwise.
- */
-uint8_t pg_cache_punch_hole(struct rrdengine_instance *ctx, struct rrdeng_page_descr *descr, uint8_t remove_dirty,
- uint8_t is_exclusive_holder, uuid_t *metric_id)
-{
- struct page_cache *pg_cache = &ctx->pg_cache;
- struct page_cache_descr *pg_cache_descr = NULL;
- Pvoid_t *PValue;
- struct pg_cache_page_index *page_index = NULL;
- int ret;
- uint8_t can_delete_metric = 0;
-
- uv_rwlock_rdlock(&pg_cache->metrics_index.lock);
- PValue = JudyHSGet(pg_cache->metrics_index.JudyHS_array, descr->id, sizeof(uuid_t));
- fatal_assert(NULL != PValue);
- page_index = *PValue;
- uv_rwlock_rdunlock(&pg_cache->metrics_index.lock);
-
- uv_rwlock_wrlock(&page_index->lock);
- ret = JudyLDel(&page_index->JudyL_array, (Word_t)(descr->start_time_ut / USEC_PER_SEC), PJE0);
- if (unlikely(0 == ret)) {
- uv_rwlock_wrunlock(&page_index->lock);
- if (unlikely(debug_flags & D_RRDENGINE)) {
- print_page_descr(descr);
- }
- goto destroy;
- }
- --page_index->page_count;
- if (!page_index->writers && !page_index->page_count) {
- can_delete_metric = 1;
- if (metric_id) {
- memcpy(metric_id, page_index->id, sizeof(uuid_t));
- }
- }
- uv_rwlock_wrunlock(&page_index->lock);
- fatal_assert(1 == ret);
-
- uv_rwlock_wrlock(&pg_cache->pg_cache_rwlock);
- ++ctx->stats.pg_cache_deletions;
- --pg_cache->page_descriptors;
- uv_rwlock_wrunlock(&pg_cache->pg_cache_rwlock);
-
- rrdeng_page_descr_mutex_lock(ctx, descr);
- pg_cache_descr = descr->pg_cache_descr;
- if (!is_exclusive_holder) {
- /* If we don't hold an exclusive page reference get one */
- while (!pg_cache_try_get_unsafe(descr, 1)) {
- debug(D_RRDENGINE, "%s: Waiting for locked page:", __func__);
- if (unlikely(debug_flags & D_RRDENGINE))
- print_page_cache_descr(descr, "", true);
- pg_cache_wait_event_unsafe(descr);
+ if(!page) {
+ if(previous_page_end_time_s < wanted_end_time_s)
+ (*cache_gaps)++;
+
+ break;
}
- }
- if (remove_dirty) {
- pg_cache_descr->flags &= ~RRD_PAGE_DIRTY;
- } else {
- /* even a locked page could be dirty */
- while (unlikely(pg_cache_descr->flags & RRD_PAGE_DIRTY)) {
- debug(D_RRDENGINE, "%s: Found dirty page, waiting for it to be flushed:", __func__);
- if (unlikely(debug_flags & D_RRDENGINE))
- print_page_cache_descr(descr, "", true);
- pg_cache_wait_event_unsafe(descr);
+
+ time_t page_start_time_s = pgc_page_start_time_s(page);
+ time_t page_end_time_s = pgc_page_end_time_s(page);
+ time_t page_update_every_s = pgc_page_update_every_s(page);
+ size_t page_length = pgc_page_data_size(cache, page);
+
+ if(!page_update_every_s)
+ page_update_every_s = dt_s;
+
+ if(is_page_in_time_range(page_start_time_s, page_end_time_s, wanted_start_time_s, wanted_end_time_s) != PAGE_IS_IN_RANGE) {
+ // not a useful page for this query
+ pgc_page_release(cache, page);
+ page = NULL;
+
+ if(previous_page_end_time_s < wanted_end_time_s)
+ (*cache_gaps)++;
+
+ break;
}
- }
- rrdeng_page_descr_mutex_unlock(ctx, descr);
-
- while (unlikely(pg_cache_descr->flags & RRD_PAGE_READ_PENDING)) {
- error_limit_static_global_var(erl, 1, 0);
- error_limit(&erl, "%s: Found page with READ PENDING, waiting for read to complete", __func__);
- if (unlikely(debug_flags & D_RRDENGINE))
- print_page_cache_descr(descr, "", true);
- pg_cache_wait_event_unsafe(descr);
- }
- if (pg_cache_descr->flags & RRD_PAGE_POPULATED) {
- /* only after locking can it be safely deleted from LRU */
- pg_cache_replaceQ_delete(ctx, descr);
+ if (page_start_time_s - previous_page_end_time_s > dt_s)
+ (*cache_gaps)++;
+
+ Pvoid_t *PValue = PDCJudyLIns(JudyL_page_array, (Word_t) page_start_time_s, PJE0);
+ if (!PValue || PValue == PJERR)
+ fatal("DBENGINE: corrupted judy array in %s()", __FUNCTION__ );
+
+ if (unlikely(*PValue)) {
+ struct page_details *pd = *PValue;
+ UNUSED(pd);
+
+// internal_error(
+// pd->first_time_s != page_first_time_s ||
+// pd->last_time_s != page_last_time_s ||
+// pd->update_every_s != page_update_every_s,
+// "DBENGINE: duplicate page with different retention in %s cache "
+// "1st: %ld to %ld, ue %u, size %u "
+// "2nd: %ld to %ld, ue %ld size %zu "
+// "- ignoring the second",
+// cache == open_cache ? "open" : "main",
+// pd->first_time_s, pd->last_time_s, pd->update_every_s, pd->page_length,
+// page_first_time_s, page_last_time_s, page_update_every_s, page_length);
+
+ pgc_page_release(cache, page);
+ }
+ else {
- uv_rwlock_wrlock(&pg_cache->pg_cache_rwlock);
- pg_cache_evict_unsafe(ctx, descr);
- uv_rwlock_wrunlock(&pg_cache->pg_cache_rwlock);
- }
- pg_cache_put(ctx, descr);
- rrdeng_try_deallocate_pg_cache_descr(ctx, descr);
- while (descr->pg_cache_descr_state & PG_CACHE_DESCR_ALLOCATED) {
- rrdeng_try_deallocate_pg_cache_descr(ctx, descr); /* spin */
- (void)sleep_usec(1000); /* 1 msec */
- }
-destroy:
- rrdeng_page_descr_freez(descr);
- pg_cache_update_metric_times(page_index);
+ internal_fatal(pgc_page_metric(page) != metric_id, "Wrong metric id in page found in cache");
+ internal_fatal(pgc_page_section(page) != (Word_t)ctx, "Wrong section in page found in cache");
- return can_delete_metric;
-}
+ struct page_details *pd = page_details_get();
+ pd->metric_id = metric_id;
+ pd->first_time_s = page_start_time_s;
+ pd->last_time_s = page_end_time_s;
+ pd->page_length = page_length;
+ pd->update_every_s = page_update_every_s;
+ pd->page = (open_cache_mode) ? NULL : page;
+ pd->status |= tags;
-static inline int is_page_in_time_range(struct rrdeng_page_descr *descr, usec_t start_time, usec_t end_time)
-{
- usec_t pg_start, pg_end;
+ if((pd->page)) {
+ pd->status |= PDC_PAGE_READY | PDC_PAGE_PRELOADED;
- pg_start = descr->start_time_ut;
- pg_end = descr->end_time_ut;
+ if(pgc_page_data(page) == DBENGINE_EMPTY_PAGE)
+ pd->status |= PDC_PAGE_EMPTY;
+ }
- return (pg_start < start_time && pg_end >= start_time) ||
- (pg_start >= start_time && pg_start <= end_time);
-}
+ if(open_cache_mode) {
+ struct rrdengine_datafile *datafile = pgc_page_data(page);
+ if(datafile_acquire(datafile, DATAFILE_ACQUIRE_PAGE_DETAILS)) { // for pd
+ struct extent_io_data *xio = (struct extent_io_data *) pgc_page_custom_data(cache, page);
+ pd->datafile.ptr = pgc_page_data(page);
+ pd->datafile.file = xio->file;
+ pd->datafile.extent.pos = xio->pos;
+ pd->datafile.extent.bytes = xio->bytes;
+ pd->datafile.fileno = pd->datafile.ptr->fileno;
+ pd->status |= PDC_PAGE_DATAFILE_ACQUIRED | PDC_PAGE_DISK_PENDING;
+ }
+ else {
+ pd->status |= PDC_PAGE_FAILED | PDC_PAGE_FAILED_TO_ACQUIRE_DATAFILE;
+ }
+ pgc_page_release(cache, page);
+ }
-static inline int is_point_in_time_in_page(struct rrdeng_page_descr *descr, usec_t point_in_time)
-{
- return (point_in_time >= descr->start_time_ut && point_in_time <= descr->end_time_ut);
-}
+ *PValue = pd;
-/* The caller must hold the page index lock */
-static inline struct rrdeng_page_descr *
- find_first_page_in_time_range(struct pg_cache_page_index *page_index, usec_t start_time, usec_t end_time)
-{
- struct rrdeng_page_descr *descr = NULL;
- Pvoid_t *PValue;
- Word_t Index;
-
- Index = (Word_t)(start_time / USEC_PER_SEC);
- PValue = JudyLLast(page_index->JudyL_array, &Index, PJE0);
- if (likely(NULL != PValue)) {
- descr = *PValue;
- if (is_page_in_time_range(descr, start_time, end_time)) {
- return descr;
+ pages_found_in_cache++;
}
- }
- Index = (Word_t)(start_time / USEC_PER_SEC);
- PValue = JudyLFirst(page_index->JudyL_array, &Index, PJE0);
- if (likely(NULL != PValue)) {
- descr = *PValue;
- if (is_page_in_time_range(descr, start_time, end_time)) {
- return descr;
- }
- }
+ // prepare for the next iteration
+ previous_page_end_time_s = page_end_time_s;
- return NULL;
-}
+ if(page_update_every_s > 0)
+ dt_s = page_update_every_s;
-/* Update metric oldest and latest timestamps efficiently when adding new values */
-void pg_cache_add_new_metric_time(struct pg_cache_page_index *page_index, struct rrdeng_page_descr *descr)
-{
- usec_t oldest_time = page_index->oldest_time_ut;
- usec_t latest_time = page_index->latest_time_ut;
+ // we are going to as for the NEXT page
+ // so, set this to our first time
+ now_s = page_start_time_s;
- if (unlikely(oldest_time == INVALID_TIME || descr->start_time_ut < oldest_time)) {
- page_index->oldest_time_ut = descr->start_time_ut;
- }
- if (likely(descr->end_time_ut > latest_time || latest_time == INVALID_TIME)) {
- page_index->latest_time_ut = descr->end_time_ut;
- }
+ } while(now_s <= wanted_end_time_s);
+
+ return pages_found_in_cache;
}
-/* Update metric oldest and latest timestamps when removing old values */
-void pg_cache_update_metric_times(struct pg_cache_page_index *page_index)
-{
- Pvoid_t *firstPValue, *lastPValue;
- Word_t firstIndex, lastIndex;
- struct rrdeng_page_descr *descr;
- usec_t oldest_time = INVALID_TIME;
- usec_t latest_time = INVALID_TIME;
-
- uv_rwlock_rdlock(&page_index->lock);
- /* Find first page in range */
- firstIndex = (Word_t)0;
- firstPValue = JudyLFirst(page_index->JudyL_array, &firstIndex, PJE0);
- if (likely(NULL != firstPValue)) {
- descr = *firstPValue;
- oldest_time = descr->start_time_ut;
- }
- lastIndex = (Word_t)-1;
- lastPValue = JudyLLast(page_index->JudyL_array, &lastIndex, PJE0);
- if (likely(NULL != lastPValue)) {
- descr = *lastPValue;
- latest_time = descr->end_time_ut;
- }
- uv_rwlock_rdunlock(&page_index->lock);
+static void pgc_inject_gap(struct rrdengine_instance *ctx, METRIC *metric, time_t start_time_s, time_t end_time_s) {
- if (unlikely(NULL == firstPValue)) {
- fatal_assert(NULL == lastPValue);
- page_index->oldest_time_ut = page_index->latest_time_ut = INVALID_TIME;
+ time_t db_first_time_s, db_last_time_s, db_update_every_s;
+ mrg_metric_get_retention(main_mrg, metric, &db_first_time_s, &db_last_time_s, &db_update_every_s);
+
+ if(is_page_in_time_range(start_time_s, end_time_s, db_first_time_s, db_last_time_s) != PAGE_IS_IN_RANGE)
return;
- }
- page_index->oldest_time_ut = oldest_time;
- page_index->latest_time_ut = latest_time;
+
+ PGC_ENTRY page_entry = {
+ .hot = false,
+ .section = (Word_t)ctx,
+ .metric_id = (Word_t)metric,
+ .start_time_s = MAX(start_time_s, db_first_time_s),
+ .end_time_s = MIN(end_time_s, db_last_time_s),
+ .update_every_s = 0,
+ .size = 0,
+ .data = DBENGINE_EMPTY_PAGE,
+ };
+
+ if(page_entry.start_time_s >= page_entry.end_time_s)
+ return;
+
+ PGC_PAGE *page = pgc_page_add_and_acquire(main_cache, page_entry, NULL);
+ pgc_page_release(main_cache, page);
}
-/* If index is NULL lookup by UUID (descr->id) */
-void pg_cache_insert(struct rrdengine_instance *ctx, struct pg_cache_page_index *index,
- struct rrdeng_page_descr *descr)
-{
- struct page_cache *pg_cache = &ctx->pg_cache;
+static size_t list_has_time_gaps(
+ struct rrdengine_instance *ctx,
+ METRIC *metric,
+ Pvoid_t JudyL_page_array,
+ time_t wanted_start_time_s,
+ time_t wanted_end_time_s,
+ size_t *pages_total,
+ size_t *pages_found_pass4,
+ size_t *pages_pending,
+ size_t *pages_overlapping,
+ time_t *optimal_end_time_s,
+ bool populate_gaps
+) {
+ // we will recalculate these, so zero them
+ *pages_pending = 0;
+ *pages_overlapping = 0;
+ *optimal_end_time_s = 0;
+
+ bool first;
Pvoid_t *PValue;
- struct pg_cache_page_index *page_index;
- unsigned long pg_cache_descr_state = descr->pg_cache_descr_state;
-
- if (0 != pg_cache_descr_state) {
- /* there is page cache descriptor pre-allocated state */
- struct page_cache_descr *pg_cache_descr = descr->pg_cache_descr;
-
- fatal_assert(pg_cache_descr_state & PG_CACHE_DESCR_ALLOCATED);
- if (pg_cache_descr->flags & RRD_PAGE_POPULATED) {
- pg_cache_reserve_pages(ctx, 1);
- if (!(pg_cache_descr->flags & RRD_PAGE_DIRTY))
- pg_cache_replaceQ_insert(ctx, descr);
- }
- }
+ Word_t this_page_start_time;
+ struct page_details *pd;
+
+ size_t gaps = 0;
+ Word_t metric_id = mrg_metric_id(main_mrg, metric);
+
+ // ------------------------------------------------------------------------
+ // PASS 1: remove the preprocessing flags from the pages in PDC
- if (unlikely(NULL == index)) {
- uv_rwlock_rdlock(&pg_cache->metrics_index.lock);
- PValue = JudyHSGet(pg_cache->metrics_index.JudyHS_array, descr->id, sizeof(uuid_t));
- fatal_assert(NULL != PValue);
- page_index = *PValue;
- uv_rwlock_rdunlock(&pg_cache->metrics_index.lock);
- } else {
- page_index = index;
+ first = true;
+ this_page_start_time = 0;
+ while((PValue = PDCJudyLFirstThenNext(JudyL_page_array, &this_page_start_time, &first))) {
+ pd = *PValue;
+ pd->status &= ~(PDC_PAGE_SKIP|PDC_PAGE_PREPROCESSED);
}
- uv_rwlock_wrlock(&page_index->lock);
- PValue = JudyLIns(&page_index->JudyL_array, (Word_t)(descr->start_time_ut / USEC_PER_SEC), PJE0);
- *PValue = descr;
- ++page_index->page_count;
- pg_cache_add_new_metric_time(page_index, descr);
- uv_rwlock_wrunlock(&page_index->lock);
-
- uv_rwlock_wrlock(&pg_cache->pg_cache_rwlock);
- ++ctx->stats.pg_cache_insertions;
- ++pg_cache->page_descriptors;
- uv_rwlock_wrunlock(&pg_cache->pg_cache_rwlock);
-}
+ // ------------------------------------------------------------------------
+ // PASS 2: emulate processing to find the useful pages
-usec_t pg_cache_oldest_time_in_range(struct rrdengine_instance *ctx, uuid_t *id, usec_t start_time_ut, usec_t end_time_ut)
-{
- struct page_cache *pg_cache = &ctx->pg_cache;
- struct rrdeng_page_descr *descr = NULL;
- Pvoid_t *PValue;
- struct pg_cache_page_index *page_index = NULL;
+ time_t now_s = wanted_start_time_s;
+ time_t dt_s = mrg_metric_get_update_every_s(main_mrg, metric);
+ if(!dt_s)
+ dt_s = default_rrd_update_every;
- uv_rwlock_rdlock(&pg_cache->metrics_index.lock);
- PValue = JudyHSGet(pg_cache->metrics_index.JudyHS_array, id, sizeof(uuid_t));
- if (likely(NULL != PValue)) {
- page_index = *PValue;
- }
- uv_rwlock_rdunlock(&pg_cache->metrics_index.lock);
- if (NULL == PValue) {
- return INVALID_TIME;
- }
+ size_t pages_pass2 = 0, pages_pass3 = 0;
+ while((pd = pdc_find_page_for_time(
+ JudyL_page_array, now_s, &gaps,
+ PDC_PAGE_PREPROCESSED, 0))) {
- uv_rwlock_rdlock(&page_index->lock);
- descr = find_first_page_in_time_range(page_index, start_time_ut, end_time_ut);
- if (NULL == descr) {
- uv_rwlock_rdunlock(&page_index->lock);
- return INVALID_TIME;
- }
- uv_rwlock_rdunlock(&page_index->lock);
- return descr->start_time_ut;
-}
+ pd->status |= PDC_PAGE_PREPROCESSED;
+ pages_pass2++;
-/**
- * Return page information for the first page before point_in_time that satisfies the filter.
- * @param ctx DB context
- * @param page_index page index of a metric
- * @param point_in_time_ut the pages that are searched must be older than this timestamp
- * @param filter decides if the page satisfies the caller's criteria
- * @param page_info the result of the search is set in this pointer
- */
-void pg_cache_get_filtered_info_prev(struct rrdengine_instance *ctx, struct pg_cache_page_index *page_index,
- usec_t point_in_time_ut, pg_cache_page_info_filter_t *filter,
- struct rrdeng_page_info *page_info)
-{
- struct page_cache *pg_cache = &ctx->pg_cache;
- struct rrdeng_page_descr *descr = NULL;
- Pvoid_t *PValue;
- Word_t Index;
+ if(pd->update_every_s)
+ dt_s = pd->update_every_s;
- (void)pg_cache;
- fatal_assert(NULL != page_index);
+ if(populate_gaps && pd->first_time_s > now_s)
+ pgc_inject_gap(ctx, metric, now_s, pd->first_time_s);
- Index = (Word_t)(point_in_time_ut / USEC_PER_SEC);
- uv_rwlock_rdlock(&page_index->lock);
- do {
- PValue = JudyLPrev(page_index->JudyL_array, &Index, PJE0);
- descr = unlikely(NULL == PValue) ? NULL : *PValue;
- } while (descr != NULL && !filter(descr));
- if (unlikely(NULL == descr)) {
- page_info->page_length = 0;
- page_info->start_time_ut = INVALID_TIME;
- page_info->end_time_ut = INVALID_TIME;
- } else {
- page_info->page_length = descr->page_length;
- page_info->start_time_ut = descr->start_time_ut;
- page_info->end_time_ut = descr->end_time_ut;
+ now_s = pd->last_time_s + dt_s;
+ if(now_s > wanted_end_time_s) {
+ *optimal_end_time_s = pd->last_time_s;
+ break;
+ }
}
- uv_rwlock_rdunlock(&page_index->lock);
-}
-/**
- * Searches for an unallocated page without triggering disk I/O. Attempts to reserve the page and get a reference.
- * @param ctx DB context
- * @param id lookup by UUID
- * @param start_time_ut exact starting time in usec
- * @param ret_page_indexp Sets the page index pointer (*ret_page_indexp) for the given UUID.
- * @return the page descriptor or NULL on failure. It can fail if:
- * 1. The page is already allocated to the page cache.
- * 2. It did not succeed to get a reference.
- * 3. It did not succeed to reserve a spot in the page cache.
- */
-struct rrdeng_page_descr *pg_cache_lookup_unpopulated_and_lock(struct rrdengine_instance *ctx, uuid_t *id,
- usec_t start_time_ut)
-{
- struct page_cache *pg_cache = &ctx->pg_cache;
- struct rrdeng_page_descr *descr = NULL;
- struct page_cache_descr *pg_cache_descr = NULL;
- unsigned long flags;
- Pvoid_t *PValue;
- struct pg_cache_page_index *page_index = NULL;
- Word_t Index;
+ if(populate_gaps && now_s < wanted_end_time_s)
+ pgc_inject_gap(ctx, metric, now_s, wanted_end_time_s);
- uv_rwlock_rdlock(&pg_cache->metrics_index.lock);
- PValue = JudyHSGet(pg_cache->metrics_index.JudyHS_array, id, sizeof(uuid_t));
- if (likely(NULL != PValue)) {
- page_index = *PValue;
- }
- uv_rwlock_rdunlock(&pg_cache->metrics_index.lock);
+ // ------------------------------------------------------------------------
+ // PASS 3: mark as skipped all the pages not useful
- if ((NULL == PValue) || !pg_cache_try_reserve_pages(ctx, 1)) {
- /* Failed to find page or failed to reserve a spot in the cache */
- return NULL;
- }
+ first = true;
+ this_page_start_time = 0;
+ while((PValue = PDCJudyLFirstThenNext(JudyL_page_array, &this_page_start_time, &first))) {
+ pd = *PValue;
- uv_rwlock_rdlock(&page_index->lock);
- Index = (Word_t)(start_time_ut / USEC_PER_SEC);
- PValue = JudyLGet(page_index->JudyL_array, Index, PJE0);
- if (likely(NULL != PValue)) {
- descr = *PValue;
- }
- if (NULL == PValue || 0 == descr->page_length) {
- /* Failed to find non-empty page */
- uv_rwlock_rdunlock(&page_index->lock);
+ internal_fatal(pd->metric_id != metric_id, "pd has wrong metric_id");
- pg_cache_release_pages(ctx, 1);
- return NULL;
- }
+ if(!(pd->status & PDC_PAGE_PREPROCESSED)) {
+ (*pages_overlapping)++;
+ pd->status |= PDC_PAGE_SKIP;
+ pd->status &= ~(PDC_PAGE_READY | PDC_PAGE_DISK_PENDING);
+ continue;
+ }
- rrdeng_page_descr_mutex_lock(ctx, descr);
- pg_cache_descr = descr->pg_cache_descr;
- flags = pg_cache_descr->flags;
- uv_rwlock_rdunlock(&page_index->lock);
+ pages_pass3++;
- if ((flags & RRD_PAGE_POPULATED) || !pg_cache_try_get_unsafe(descr, 1)) {
- /* Failed to get reference or page is already populated */
- rrdeng_page_descr_mutex_unlock(ctx, descr);
+ if(!pd->page) {
+ pd->page = pgc_page_get_and_acquire(main_cache, (Word_t) ctx, (Word_t) metric_id, pd->first_time_s, PGC_SEARCH_EXACT);
- pg_cache_release_pages(ctx, 1);
- return NULL;
+ if(pd->page) {
+ (*pages_found_pass4)++;
+
+ pd->status &= ~PDC_PAGE_DISK_PENDING;
+ pd->status |= PDC_PAGE_READY | PDC_PAGE_PRELOADED | PDC_PAGE_PRELOADED_PASS4;
+
+ if(pgc_page_data(pd->page) == DBENGINE_EMPTY_PAGE)
+ pd->status |= PDC_PAGE_EMPTY;
+
+ }
+ else if(!(pd->status & PDC_PAGE_FAILED) && (pd->status & PDC_PAGE_DATAFILE_ACQUIRED)) {
+ (*pages_pending)++;
+
+ pd->status |= PDC_PAGE_DISK_PENDING;
+
+ internal_fatal(pd->status & PDC_PAGE_SKIP, "page is disk pending and skipped");
+ internal_fatal(!pd->datafile.ptr, "datafile is NULL");
+ internal_fatal(!pd->datafile.extent.bytes, "datafile.extent.bytes zero");
+ internal_fatal(!pd->datafile.extent.pos, "datafile.extent.pos is zero");
+ internal_fatal(!pd->datafile.fileno, "datafile.fileno is zero");
+ }
+ }
+ else {
+ pd->status &= ~PDC_PAGE_DISK_PENDING;
+ pd->status |= (PDC_PAGE_READY | PDC_PAGE_PRELOADED);
+ }
}
- /* success */
- rrdeng_page_descr_mutex_unlock(ctx, descr);
- rrd_stat_atomic_add(&ctx->stats.pg_cache_misses, 1);
- return descr;
+ internal_fatal(pages_pass2 != pages_pass3,
+ "DBENGINE: page count does not match");
+
+ *pages_total = pages_pass2;
+
+ return gaps;
}
-/**
- * Searches for pages in a time range and triggers disk I/O if necessary and possible.
- * Does not get a reference.
- * @param ctx DB context
- * @param id UUID
- * @param start_time_ut inclusive starting time in usec
- * @param end_time_ut inclusive ending time in usec
- * @param page_info_arrayp It allocates (*page_arrayp) and populates it with information of pages that overlap
- * with the time range [start_time,end_time]. The caller must free (*page_info_arrayp) with freez().
- * If page_info_arrayp is set to NULL nothing was allocated.
- * @param ret_page_indexp Sets the page index pointer (*ret_page_indexp) for the given UUID.
- * @return the number of pages that overlap with the time range [start_time,end_time].
- */
-unsigned pg_cache_preload(struct rrdengine_instance *ctx, uuid_t *id, usec_t start_time_ut, usec_t end_time_ut,
- struct rrdeng_page_info **page_info_arrayp, struct pg_cache_page_index **ret_page_indexp)
-{
- struct page_cache *pg_cache = &ctx->pg_cache;
- struct rrdeng_page_descr *descr = NULL, *preload_array[PAGE_CACHE_MAX_PRELOAD_PAGES];
- struct page_cache_descr *pg_cache_descr = NULL;
- unsigned i, j, k, preload_count, count, page_info_array_max_size;
- unsigned long flags;
- Pvoid_t *PValue;
- struct pg_cache_page_index *page_index = NULL;
- Word_t Index;
- uint8_t failed_to_reserve;
+typedef void (*page_found_callback_t)(PGC_PAGE *page, void *data);
+static size_t get_page_list_from_journal_v2(struct rrdengine_instance *ctx, METRIC *metric, usec_t start_time_ut, usec_t end_time_ut, page_found_callback_t callback, void *callback_data) {
+ uuid_t *uuid = mrg_metric_uuid(main_mrg, metric);
+ Word_t metric_id = mrg_metric_id(main_mrg, metric);
- fatal_assert(NULL != ret_page_indexp);
+ time_t wanted_start_time_s = (time_t)(start_time_ut / USEC_PER_SEC);
+ time_t wanted_end_time_s = (time_t)(end_time_ut / USEC_PER_SEC);
- uv_rwlock_rdlock(&pg_cache->metrics_index.lock);
- PValue = JudyHSGet(pg_cache->metrics_index.JudyHS_array, id, sizeof(uuid_t));
- if (likely(NULL != PValue)) {
- *ret_page_indexp = page_index = *PValue;
- }
- uv_rwlock_rdunlock(&pg_cache->metrics_index.lock);
- if (NULL == PValue) {
- debug(D_RRDENGINE, "%s: No page was found to attempt preload.", __func__);
- *ret_page_indexp = NULL;
- return 0;
- }
+ size_t pages_found = 0;
- uv_rwlock_rdlock(&page_index->lock);
- descr = find_first_page_in_time_range(page_index, start_time_ut, end_time_ut);
- if (NULL == descr) {
- uv_rwlock_rdunlock(&page_index->lock);
- debug(D_RRDENGINE, "%s: No page was found to attempt preload.", __func__);
- *ret_page_indexp = NULL;
- return 0;
- } else {
- Index = (Word_t)(descr->start_time_ut / USEC_PER_SEC);
- }
- if (page_info_arrayp) {
- page_info_array_max_size = PAGE_CACHE_MAX_PRELOAD_PAGES * sizeof(struct rrdeng_page_info);
- *page_info_arrayp = mallocz(page_info_array_max_size);
- }
+ uv_rwlock_rdlock(&ctx->datafiles.rwlock);
+ struct rrdengine_datafile *datafile;
+ for(datafile = ctx->datafiles.first; datafile ; datafile = datafile->next) {
+ struct journal_v2_header *j2_header = journalfile_v2_data_acquire(datafile->journalfile, NULL,
+ wanted_start_time_s,
+ wanted_end_time_s);
+ if (unlikely(!j2_header))
+ continue;
- for (count = 0, preload_count = 0 ;
- descr != NULL && is_page_in_time_range(descr, start_time_ut, end_time_ut) ;
- PValue = JudyLNext(page_index->JudyL_array, &Index, PJE0),
- descr = unlikely(NULL == PValue) ? NULL : *PValue) {
- /* Iterate all pages in range */
+ time_t journal_start_time_s = (time_t)(j2_header->start_time_ut / USEC_PER_SEC);
- if (unlikely(0 == descr->page_length))
+ // the datafile possibly contains useful data for this query
+
+ size_t journal_metric_count = (size_t)j2_header->metric_count;
+ struct journal_metric_list *uuid_list = (struct journal_metric_list *)((uint8_t *) j2_header + j2_header->metric_offset);
+ struct journal_metric_list *uuid_entry = bsearch(uuid,uuid_list,journal_metric_count,sizeof(*uuid_list), journal_metric_uuid_compare);
+
+ if (unlikely(!uuid_entry)) {
+ // our UUID is not in this datafile
+ journalfile_v2_data_release(datafile->journalfile);
continue;
- if (page_info_arrayp) {
- if (unlikely(count >= page_info_array_max_size / sizeof(struct rrdeng_page_info))) {
- page_info_array_max_size += PAGE_CACHE_MAX_PRELOAD_PAGES * sizeof(struct rrdeng_page_info);
- *page_info_arrayp = reallocz(*page_info_arrayp, page_info_array_max_size);
- }
- (*page_info_arrayp)[count].start_time_ut = descr->start_time_ut;
- (*page_info_arrayp)[count].end_time_ut = descr->end_time_ut;
- (*page_info_arrayp)[count].page_length = descr->page_length;
}
- ++count;
-
- rrdeng_page_descr_mutex_lock(ctx, descr);
- pg_cache_descr = descr->pg_cache_descr;
- flags = pg_cache_descr->flags;
- if (pg_cache_can_get_unsafe(descr, 0)) {
- if (flags & RRD_PAGE_POPULATED) {
- /* success */
- rrdeng_page_descr_mutex_unlock(ctx, descr);
- debug(D_RRDENGINE, "%s: Page was found in memory.", __func__);
+
+ struct journal_page_header *page_list_header = (struct journal_page_header *) ((uint8_t *) j2_header + uuid_entry->page_offset);
+ struct journal_page_list *page_list = (struct journal_page_list *)((uint8_t *) page_list_header + sizeof(*page_list_header));
+ struct journal_extent_list *extent_list = (void *)((uint8_t *)j2_header + j2_header->extent_offset);
+ uint32_t uuid_page_entries = page_list_header->entries;
+
+ for (uint32_t index = 0; index < uuid_page_entries; index++) {
+ struct journal_page_list *page_entry_in_journal = &page_list[index];
+
+ time_t page_first_time_s = page_entry_in_journal->delta_start_s + journal_start_time_s;
+ time_t page_last_time_s = page_entry_in_journal->delta_end_s + journal_start_time_s;
+
+ TIME_RANGE_COMPARE prc = is_page_in_time_range(page_first_time_s, page_last_time_s, wanted_start_time_s, wanted_end_time_s);
+ if(prc == PAGE_IS_IN_THE_PAST)
continue;
- }
- }
- if (!(flags & RRD_PAGE_POPULATED) && pg_cache_try_get_unsafe(descr, 1)) {
- preload_array[preload_count++] = descr;
- if (PAGE_CACHE_MAX_PRELOAD_PAGES == preload_count) {
- rrdeng_page_descr_mutex_unlock(ctx, descr);
+
+ if(prc == PAGE_IS_IN_THE_FUTURE)
break;
+
+ time_t page_update_every_s = page_entry_in_journal->update_every_s;
+ size_t page_length = page_entry_in_journal->page_length;
+
+ if(datafile_acquire(datafile, DATAFILE_ACQUIRE_OPEN_CACHE)) { //for open cache item
+ // add this page to open cache
+ bool added = false;
+ struct extent_io_data ei = {
+ .pos = extent_list[page_entry_in_journal->extent_index].datafile_offset,
+ .bytes = extent_list[page_entry_in_journal->extent_index].datafile_size,
+ .page_length = page_length,
+ .file = datafile->file,
+ .fileno = datafile->fileno,
+ };
+
+ PGC_PAGE *page = pgc_page_add_and_acquire(open_cache, (PGC_ENTRY) {
+ .hot = false,
+ .section = (Word_t) ctx,
+ .metric_id = metric_id,
+ .start_time_s = page_first_time_s,
+ .end_time_s = page_last_time_s,
+ .update_every_s = page_update_every_s,
+ .data = datafile,
+ .size = 0,
+ .custom_data = (uint8_t *) &ei,
+ }, &added);
+
+ if(!added)
+ datafile_release(datafile, DATAFILE_ACQUIRE_OPEN_CACHE);
+
+ callback(page, callback_data);
+
+ pgc_page_release(open_cache, page);
+
+ pages_found++;
}
}
- rrdeng_page_descr_mutex_unlock(ctx, descr);
+ journalfile_v2_data_release(datafile->journalfile);
}
- uv_rwlock_rdunlock(&page_index->lock);
+ uv_rwlock_rdunlock(&ctx->datafiles.rwlock);
- failed_to_reserve = 0;
- for (i = 0 ; i < preload_count && !failed_to_reserve ; ++i) {
- struct rrdeng_cmd cmd;
- struct rrdeng_page_descr *next;
+ return pages_found;
+}
- descr = preload_array[i];
- if (NULL == descr) {
- continue;
- }
- if (!pg_cache_try_reserve_pages(ctx, 1)) {
- failed_to_reserve = 1;
- break;
- }
- cmd.opcode = RRDENG_READ_EXTENT;
- cmd.read_extent.page_cache_descr[0] = descr;
- /* don't use this page again */
- preload_array[i] = NULL;
- for (j = 0, k = 1 ; j < preload_count ; ++j) {
- next = preload_array[j];
- if (NULL == next) {
- continue;
- }
- if (descr->extent == next->extent) {
- /* same extent, consolidate */
- if (!pg_cache_try_reserve_pages(ctx, 1)) {
- failed_to_reserve = 1;
- break;
- }
- cmd.read_extent.page_cache_descr[k++] = next;
- /* don't use this page again */
- preload_array[j] = NULL;
- }
- }
- cmd.read_extent.page_count = k;
- rrdeng_enq_cmd(&ctx->worker_config, &cmd);
+void add_page_details_from_journal_v2(PGC_PAGE *page, void *JudyL_pptr) {
+ struct rrdengine_datafile *datafile = pgc_page_data(page);
+
+ if(!datafile_acquire(datafile, DATAFILE_ACQUIRE_PAGE_DETAILS)) // for pd
+ return;
+
+ Pvoid_t *PValue = PDCJudyLIns(JudyL_pptr, pgc_page_start_time_s(page), PJE0);
+ if (!PValue || PValue == PJERR)
+ fatal("DBENGINE: corrupted judy array");
+
+ if (unlikely(*PValue)) {
+ datafile_release(datafile, DATAFILE_ACQUIRE_PAGE_DETAILS);
+ return;
}
- if (failed_to_reserve) {
- debug(D_RRDENGINE, "%s: Failed to reserve enough memory, canceling I/O.", __func__);
- for (i = 0 ; i < preload_count ; ++i) {
- descr = preload_array[i];
- if (NULL == descr) {
- continue;
- }
- pg_cache_put(ctx, descr);
- }
+
+ Word_t metric_id = pgc_page_metric(page);
+
+ // let's add it to the judy
+ struct extent_io_data *ei = pgc_page_custom_data(open_cache, page);
+ struct page_details *pd = page_details_get();
+ *PValue = pd;
+
+ pd->datafile.extent.pos = ei->pos;
+ pd->datafile.extent.bytes = ei->bytes;
+ pd->datafile.file = ei->file;
+ pd->datafile.fileno = ei->fileno;
+ pd->first_time_s = pgc_page_start_time_s(page);
+ pd->last_time_s = pgc_page_end_time_s(page);
+ pd->datafile.ptr = datafile;
+ pd->page_length = ei->page_length;
+ pd->update_every_s = pgc_page_update_every_s(page);
+ pd->metric_id = metric_id;
+ pd->status |= PDC_PAGE_DISK_PENDING | PDC_PAGE_SOURCE_JOURNAL_V2 | PDC_PAGE_DATAFILE_ACQUIRED;
+}
+
+// Return a judyL will all pages that have start_time_ut and end_time_ut
+// Pvalue of the judy will be the end time for that page
+// DBENGINE2:
+#define time_delta(finish, pass) do { if(pass) { usec_t t = pass; (pass) = (finish) - (pass); (finish) = t; } } while(0)
+static Pvoid_t get_page_list(
+ struct rrdengine_instance *ctx,
+ METRIC *metric,
+ usec_t start_time_ut,
+ usec_t end_time_ut,
+ size_t *pages_to_load,
+ time_t *optimal_end_time_s
+) {
+ *optimal_end_time_s = 0;
+
+ Pvoid_t JudyL_page_array = (Pvoid_t) NULL;
+
+ time_t wanted_start_time_s = (time_t)(start_time_ut / USEC_PER_SEC);
+ time_t wanted_end_time_s = (time_t)(end_time_ut / USEC_PER_SEC);
+
+ size_t pages_found_in_main_cache = 0,
+ pages_found_in_open_cache = 0,
+ pages_found_in_journals_v2 = 0,
+ pages_found_pass4 = 0,
+ pages_pending = 0,
+ pages_overlapping = 0,
+ pages_total = 0;
+
+ size_t cache_gaps = 0, query_gaps = 0;
+ bool done_v2 = false, done_open = false;
+
+ usec_t pass1_ut = 0, pass2_ut = 0, pass3_ut = 0, pass4_ut = 0;
+
+ // --------------------------------------------------------------
+ // PASS 1: Check what the main page cache has available
+
+ pass1_ut = now_monotonic_usec();
+ size_t pages_pass1 = get_page_list_from_pgc(main_cache, metric, ctx, wanted_start_time_s, wanted_end_time_s,
+ &JudyL_page_array, &cache_gaps,
+ false, PDC_PAGE_SOURCE_MAIN_CACHE);
+ query_gaps += cache_gaps;
+ pages_found_in_main_cache += pages_pass1;
+ pages_total += pages_pass1;
+
+ if(pages_found_in_main_cache && !cache_gaps) {
+ query_gaps = list_has_time_gaps(ctx, metric, JudyL_page_array, wanted_start_time_s, wanted_end_time_s,
+ &pages_total, &pages_found_pass4, &pages_pending, &pages_overlapping,
+ optimal_end_time_s, false);
+
+ if (pages_total && !query_gaps)
+ goto we_are_done;
}
- if (!preload_count) {
- /* no such page */
- debug(D_RRDENGINE, "%s: No page was eligible to attempt preload.", __func__);
+
+ // --------------------------------------------------------------
+ // PASS 2: Check what the open journal page cache has available
+ // these will be loaded from disk
+
+ pass2_ut = now_monotonic_usec();
+ size_t pages_pass2 = get_page_list_from_pgc(open_cache, metric, ctx, wanted_start_time_s, wanted_end_time_s,
+ &JudyL_page_array, &cache_gaps,
+ true, PDC_PAGE_SOURCE_OPEN_CACHE);
+ query_gaps += cache_gaps;
+ pages_found_in_open_cache += pages_pass2;
+ pages_total += pages_pass2;
+ done_open = true;
+
+ if(pages_found_in_open_cache) {
+ query_gaps = list_has_time_gaps(ctx, metric, JudyL_page_array, wanted_start_time_s, wanted_end_time_s,
+ &pages_total, &pages_found_pass4, &pages_pending, &pages_overlapping,
+ optimal_end_time_s, false);
+
+ if (pages_total && !query_gaps)
+ goto we_are_done;
}
- if (unlikely(0 == count && page_info_arrayp)) {
- freez(*page_info_arrayp);
- *page_info_arrayp = NULL;
+
+ // --------------------------------------------------------------
+ // PASS 3: Check Journal v2 to fill the gaps
+
+ pass3_ut = now_monotonic_usec();
+ size_t pages_pass3 = get_page_list_from_journal_v2(ctx, metric, start_time_ut, end_time_ut,
+ add_page_details_from_journal_v2, &JudyL_page_array);
+ pages_found_in_journals_v2 += pages_pass3;
+ pages_total += pages_pass3;
+ done_v2 = true;
+
+ // --------------------------------------------------------------
+ // PASS 4: Check the cache again
+ // and calculate the time gaps in the query
+ // THIS IS REQUIRED AFTER JOURNAL V2 LOOKUP
+
+ pass4_ut = now_monotonic_usec();
+ query_gaps = list_has_time_gaps(ctx, metric, JudyL_page_array, wanted_start_time_s, wanted_end_time_s,
+ &pages_total, &pages_found_pass4, &pages_pending, &pages_overlapping,
+ optimal_end_time_s, true);
+
+we_are_done:
+
+ if(pages_to_load)
+ *pages_to_load = pages_pending;
+
+ usec_t finish_ut = now_monotonic_usec();
+ time_delta(finish_ut, pass4_ut);
+ time_delta(finish_ut, pass3_ut);
+ time_delta(finish_ut, pass2_ut);
+ time_delta(finish_ut, pass1_ut);
+ __atomic_add_fetch(&rrdeng_cache_efficiency_stats.prep_time_in_main_cache_lookup, pass1_ut, __ATOMIC_RELAXED);
+ __atomic_add_fetch(&rrdeng_cache_efficiency_stats.prep_time_in_open_cache_lookup, pass2_ut, __ATOMIC_RELAXED);
+ __atomic_add_fetch(&rrdeng_cache_efficiency_stats.prep_time_in_journal_v2_lookup, pass3_ut, __ATOMIC_RELAXED);
+ __atomic_add_fetch(&rrdeng_cache_efficiency_stats.prep_time_in_pass4_lookup, pass4_ut, __ATOMIC_RELAXED);
+
+ __atomic_add_fetch(&rrdeng_cache_efficiency_stats.queries, 1, __ATOMIC_RELAXED);
+ __atomic_add_fetch(&rrdeng_cache_efficiency_stats.queries_planned_with_gaps, (query_gaps) ? 1 : 0, __ATOMIC_RELAXED);
+ __atomic_add_fetch(&rrdeng_cache_efficiency_stats.queries_open, done_open ? 1 : 0, __ATOMIC_RELAXED);
+ __atomic_add_fetch(&rrdeng_cache_efficiency_stats.queries_journal_v2, done_v2 ? 1 : 0, __ATOMIC_RELAXED);
+ __atomic_add_fetch(&rrdeng_cache_efficiency_stats.pages_total, pages_total, __ATOMIC_RELAXED);
+ __atomic_add_fetch(&rrdeng_cache_efficiency_stats.pages_meta_source_main_cache, pages_found_in_main_cache, __ATOMIC_RELAXED);
+ __atomic_add_fetch(&rrdeng_cache_efficiency_stats.pages_meta_source_open_cache, pages_found_in_open_cache, __ATOMIC_RELAXED);
+ __atomic_add_fetch(&rrdeng_cache_efficiency_stats.pages_meta_source_journal_v2, pages_found_in_journals_v2, __ATOMIC_RELAXED);
+ __atomic_add_fetch(&rrdeng_cache_efficiency_stats.pages_data_source_main_cache, pages_found_in_main_cache, __ATOMIC_RELAXED);
+ __atomic_add_fetch(&rrdeng_cache_efficiency_stats.pages_data_source_main_cache_at_pass4, pages_found_pass4, __ATOMIC_RELAXED);
+ __atomic_add_fetch(&rrdeng_cache_efficiency_stats.pages_to_load_from_disk, pages_pending, __ATOMIC_RELAXED);
+ __atomic_add_fetch(&rrdeng_cache_efficiency_stats.pages_overlapping_skipped, pages_overlapping, __ATOMIC_RELAXED);
+
+ return JudyL_page_array;
+}
+
+inline void rrdeng_prep_wait(PDC *pdc) {
+ if (unlikely(pdc && !pdc->prep_done)) {
+ usec_t started_ut = now_monotonic_usec();
+ completion_wait_for(&pdc->prep_completion);
+ pdc->prep_done = true;
+ __atomic_add_fetch(&rrdeng_cache_efficiency_stats.query_time_wait_for_prep, now_monotonic_usec() - started_ut, __ATOMIC_RELAXED);
}
- return count;
}
-/*
- * Searches for a page and gets a reference.
- * When point_in_time is INVALID_TIME get any page.
- * If index is NULL lookup by UUID (id).
- */
-struct rrdeng_page_descr *
- pg_cache_lookup(struct rrdengine_instance *ctx, struct pg_cache_page_index *index, uuid_t *id,
- usec_t point_in_time_ut)
-{
- struct page_cache *pg_cache = &ctx->pg_cache;
- struct rrdeng_page_descr *descr = NULL;
- struct page_cache_descr *pg_cache_descr = NULL;
- unsigned long flags;
- Pvoid_t *PValue;
- struct pg_cache_page_index *page_index = NULL;
- Word_t Index;
- uint8_t page_not_in_cache;
-
- if (unlikely(NULL == index)) {
- uv_rwlock_rdlock(&pg_cache->metrics_index.lock);
- PValue = JudyHSGet(pg_cache->metrics_index.JudyHS_array, id, sizeof(uuid_t));
- if (likely(NULL != PValue)) {
- page_index = *PValue;
- }
- uv_rwlock_rdunlock(&pg_cache->metrics_index.lock);
- if (NULL == PValue) {
- return NULL;
- }
- } else {
- page_index = index;
+void rrdeng_prep_query(PDC *pdc) {
+ size_t pages_to_load = 0;
+ pdc->page_list_JudyL = get_page_list(pdc->ctx, pdc->metric,
+ pdc->start_time_s * USEC_PER_SEC,
+ pdc->end_time_s * USEC_PER_SEC,
+ &pages_to_load,
+ &pdc->optimal_end_time_s);
+
+ if (pages_to_load && pdc->page_list_JudyL) {
+ pdc_acquire(pdc); // we get 1 for the 1st worker in the chain: do_read_page_list_work()
+ usec_t start_ut = now_monotonic_usec();
+// if(likely(priority == STORAGE_PRIORITY_BEST_EFFORT))
+// dbengine_load_page_list_directly(ctx, handle->pdc);
+// else
+ pdc_route_asynchronously(pdc->ctx, pdc);
+ __atomic_add_fetch(&rrdeng_cache_efficiency_stats.prep_time_to_route, now_monotonic_usec() - start_ut, __ATOMIC_RELAXED);
}
- pg_cache_reserve_pages(ctx, 1);
-
- page_not_in_cache = 0;
- uv_rwlock_rdlock(&page_index->lock);
- while (1) {
- Index = (Word_t)(point_in_time_ut / USEC_PER_SEC);
- PValue = JudyLLast(page_index->JudyL_array, &Index, PJE0);
- if (likely(NULL != PValue)) {
- descr = *PValue;
- }
- if (NULL == PValue ||
- 0 == descr->page_length ||
- (INVALID_TIME != point_in_time_ut &&
- !is_point_in_time_in_page(descr, point_in_time_ut))) {
- /* non-empty page not found */
- uv_rwlock_rdunlock(&page_index->lock);
-
- pg_cache_release_pages(ctx, 1);
- return NULL;
- }
- rrdeng_page_descr_mutex_lock(ctx, descr);
- pg_cache_descr = descr->pg_cache_descr;
- flags = pg_cache_descr->flags;
- if ((flags & RRD_PAGE_POPULATED) && pg_cache_try_get_unsafe(descr, 0)) {
- /* success */
- rrdeng_page_descr_mutex_unlock(ctx, descr);
- debug(D_RRDENGINE, "%s: Page was found in memory.", __func__);
- break;
- }
- if (!(flags & RRD_PAGE_POPULATED) && pg_cache_try_get_unsafe(descr, 1)) {
- struct rrdeng_cmd cmd;
+ else
+ completion_mark_complete(&pdc->page_completion);
- uv_rwlock_rdunlock(&page_index->lock);
+ completion_mark_complete(&pdc->prep_completion);
- cmd.opcode = RRDENG_READ_PAGE;
- cmd.read_page.page_cache_descr = descr;
- rrdeng_enq_cmd(&ctx->worker_config, &cmd);
+ pdc_release_and_destroy_if_unreferenced(pdc, true, true);
+}
- debug(D_RRDENGINE, "%s: Waiting for page to be asynchronously read from disk:", __func__);
- if(unlikely(debug_flags & D_RRDENGINE))
- print_page_cache_descr(descr, "", true);
- while (!(pg_cache_descr->flags & RRD_PAGE_POPULATED)) {
- pg_cache_wait_event_unsafe(descr);
- }
- /* success */
- /* Downgrade exclusive reference to allow other readers */
- pg_cache_descr->flags &= ~RRD_PAGE_LOCKED;
- pg_cache_wake_up_waiters_unsafe(descr);
- rrdeng_page_descr_mutex_unlock(ctx, descr);
- rrd_stat_atomic_add(&ctx->stats.pg_cache_misses, 1);
- return descr;
- }
- uv_rwlock_rdunlock(&page_index->lock);
- debug(D_RRDENGINE, "%s: Waiting for page to be unlocked:", __func__);
- if(unlikely(debug_flags & D_RRDENGINE))
- print_page_cache_descr(descr, "", true);
- if (!(flags & RRD_PAGE_POPULATED))
- page_not_in_cache = 1;
- pg_cache_wait_event_unsafe(descr);
- rrdeng_page_descr_mutex_unlock(ctx, descr);
-
- /* reset scan to find again */
- uv_rwlock_rdlock(&page_index->lock);
- }
- uv_rwlock_rdunlock(&page_index->lock);
+/**
+ * Searches for pages in a time range and triggers disk I/O if necessary and possible.
+ * @param ctx DB context
+ * @param handle query handle as initialized
+ * @param start_time_ut inclusive starting time in usec
+ * @param end_time_ut inclusive ending time in usec
+ * @return 1 / 0 (pages found or not found)
+ */
+void pg_cache_preload(struct rrdeng_query_handle *handle) {
+ if (unlikely(!handle || !handle->metric))
+ return;
- if (!(flags & RRD_PAGE_DIRTY))
- pg_cache_replaceQ_set_hot(ctx, descr);
- pg_cache_release_pages(ctx, 1);
- if (page_not_in_cache)
- rrd_stat_atomic_add(&ctx->stats.pg_cache_misses, 1);
- else
- rrd_stat_atomic_add(&ctx->stats.pg_cache_hits, 1);
- return descr;
+ __atomic_add_fetch(&handle->ctx->atomic.inflight_queries, 1, __ATOMIC_RELAXED);
+ __atomic_add_fetch(&rrdeng_cache_efficiency_stats.currently_running_queries, 1, __ATOMIC_RELAXED);
+ handle->pdc = pdc_get();
+ handle->pdc->metric = mrg_metric_dup(main_mrg, handle->metric);
+ handle->pdc->start_time_s = handle->start_time_s;
+ handle->pdc->end_time_s = handle->end_time_s;
+ handle->pdc->priority = handle->priority;
+ handle->pdc->optimal_end_time_s = handle->end_time_s;
+ handle->pdc->ctx = handle->ctx;
+ handle->pdc->refcount = 1;
+ netdata_spinlock_init(&handle->pdc->refcount_spinlock);
+ completion_init(&handle->pdc->prep_completion);
+ completion_init(&handle->pdc->page_completion);
+
+ if(ctx_is_available_for_queries(handle->ctx)) {
+ handle->pdc->refcount++; // we get 1 for the query thread and 1 for the prep thread
+ rrdeng_enq_cmd(handle->ctx, RRDENG_OPCODE_QUERY, handle->pdc, NULL, handle->priority, NULL, NULL);
+ }
+ else {
+ completion_mark_complete(&handle->pdc->prep_completion);
+ completion_mark_complete(&handle->pdc->page_completion);
+ }
}
/*
@@ -1088,226 +840,282 @@ struct rrdeng_page_descr *
* start_time and end_time are inclusive.
* If index is NULL lookup by UUID (id).
*/
-struct rrdeng_page_descr *
-pg_cache_lookup_next(struct rrdengine_instance *ctx, struct pg_cache_page_index *index, uuid_t *id,
- usec_t start_time_ut, usec_t end_time_ut)
-{
- struct page_cache *pg_cache = &ctx->pg_cache;
- struct rrdeng_page_descr *descr = NULL;
- struct page_cache_descr *pg_cache_descr = NULL;
- unsigned long flags;
- Pvoid_t *PValue;
- struct pg_cache_page_index *page_index = NULL;
- uint8_t page_not_in_cache;
-
- if (unlikely(NULL == index)) {
- uv_rwlock_rdlock(&pg_cache->metrics_index.lock);
- PValue = JudyHSGet(pg_cache->metrics_index.JudyHS_array, id, sizeof(uuid_t));
- if (likely(NULL != PValue)) {
- page_index = *PValue;
+struct pgc_page *pg_cache_lookup_next(
+ struct rrdengine_instance *ctx,
+ PDC *pdc,
+ time_t now_s,
+ time_t last_update_every_s,
+ size_t *entries
+) {
+ if (unlikely(!pdc))
+ return NULL;
+
+ rrdeng_prep_wait(pdc);
+
+ if (unlikely(!pdc->page_list_JudyL))
+ return NULL;
+
+ usec_t start_ut = now_monotonic_usec();
+ size_t gaps = 0;
+ bool waited = false, preloaded;
+ PGC_PAGE *page = NULL;
+
+ while(!page) {
+ bool page_from_pd = false;
+ preloaded = false;
+ struct page_details *pd = pdc_find_page_for_time(
+ pdc->page_list_JudyL, now_s, &gaps,
+ PDC_PAGE_PROCESSED, PDC_PAGE_EMPTY);
+
+ if (!pd)
+ break;
+
+ page = pd->page;
+ page_from_pd = true;
+ preloaded = pdc_page_status_check(pd, PDC_PAGE_PRELOADED);
+ if(!page) {
+ if(!completion_is_done(&pdc->page_completion)) {
+ page = pgc_page_get_and_acquire(main_cache, (Word_t)ctx,
+ pd->metric_id, pd->first_time_s, PGC_SEARCH_EXACT);
+ page_from_pd = false;
+ preloaded = pdc_page_status_check(pd, PDC_PAGE_PRELOADED);
+ }
+
+ if(!page) {
+ pdc->completed_jobs =
+ completion_wait_for_a_job(&pdc->page_completion, pdc->completed_jobs);
+
+ page = pd->page;
+ page_from_pd = true;
+ preloaded = pdc_page_status_check(pd, PDC_PAGE_PRELOADED);
+ waited = true;
+ }
}
- uv_rwlock_rdunlock(&pg_cache->metrics_index.lock);
- if (NULL == PValue) {
- return NULL;
+
+ if(page && pgc_page_data(page) == DBENGINE_EMPTY_PAGE)
+ pdc_page_status_set(pd, PDC_PAGE_EMPTY);
+
+ if(!page || pdc_page_status_check(pd, PDC_PAGE_QUERY_GLOBAL_SKIP_LIST | PDC_PAGE_EMPTY)) {
+ page = NULL;
+ continue;
}
- } else {
- page_index = index;
- }
- pg_cache_reserve_pages(ctx, 1);
-
- page_not_in_cache = 0;
- uv_rwlock_rdlock(&page_index->lock);
- int retry_count = 0;
- while (1) {
- descr = find_first_page_in_time_range(page_index, start_time_ut, end_time_ut);
- if (NULL == descr || 0 == descr->page_length || retry_count == default_rrdeng_page_fetch_retries) {
- /* non-empty page not found */
- if (retry_count == default_rrdeng_page_fetch_retries)
- error_report("Page cache timeout while waiting for page %p : returning FAIL", descr);
- uv_rwlock_rdunlock(&page_index->lock);
-
- pg_cache_release_pages(ctx, 1);
- return NULL;
+
+ // we now have page and is not empty
+
+ time_t page_start_time_s = pgc_page_start_time_s(page);
+ time_t page_end_time_s = pgc_page_end_time_s(page);
+ time_t page_update_every_s = pgc_page_update_every_s(page);
+ size_t page_length = pgc_page_data_size(main_cache, page);
+
+ if(unlikely(page_start_time_s == INVALID_TIME || page_end_time_s == INVALID_TIME)) {
+ __atomic_add_fetch(&rrdeng_cache_efficiency_stats.pages_zero_time_skipped, 1, __ATOMIC_RELAXED);
+ pgc_page_to_clean_evict_or_release(main_cache, page);
+ pdc_page_status_set(pd, PDC_PAGE_INVALID | PDC_PAGE_RELEASED);
+ pd->page = page = NULL;
+ continue;
}
- rrdeng_page_descr_mutex_lock(ctx, descr);
- pg_cache_descr = descr->pg_cache_descr;
- flags = pg_cache_descr->flags;
- if ((flags & RRD_PAGE_POPULATED) && pg_cache_try_get_unsafe(descr, 0)) {
- /* success */
- rrdeng_page_descr_mutex_unlock(ctx, descr);
- debug(D_RRDENGINE, "%s: Page was found in memory.", __func__);
- break;
+ else if(page_length > RRDENG_BLOCK_SIZE) {
+ __atomic_add_fetch(&rrdeng_cache_efficiency_stats.pages_invalid_size_skipped, 1, __ATOMIC_RELAXED);
+ pgc_page_to_clean_evict_or_release(main_cache, page);
+ pdc_page_status_set(pd, PDC_PAGE_INVALID | PDC_PAGE_RELEASED);
+ pd->page = page = NULL;
+ continue;
}
- if (!(flags & RRD_PAGE_POPULATED) && pg_cache_try_get_unsafe(descr, 1)) {
- struct rrdeng_cmd cmd;
+ else {
+ if (unlikely(page_update_every_s <= 0 || page_update_every_s > 86400)) {
+ __atomic_add_fetch(&rrdeng_cache_efficiency_stats.pages_invalid_update_every_fixed, 1, __ATOMIC_RELAXED);
+ pd->update_every_s = page_update_every_s = pgc_page_fix_update_every(page, last_update_every_s);
+ }
- uv_rwlock_rdunlock(&page_index->lock);
+ size_t entries_by_size = page_entries_by_size(page_length, CTX_POINT_SIZE_BYTES(ctx));
+ size_t entries_by_time = page_entries_by_time(page_start_time_s, page_end_time_s, page_update_every_s);
+ if(unlikely(entries_by_size < entries_by_time)) {
+ time_t fixed_page_end_time_s = (time_t)(page_start_time_s + (entries_by_size - 1) * page_update_every_s);
+ pd->last_time_s = page_end_time_s = pgc_page_fix_end_time_s(page, fixed_page_end_time_s);
+ entries_by_time = (page_end_time_s - (page_start_time_s - page_update_every_s)) / page_update_every_s;
- cmd.opcode = RRDENG_READ_PAGE;
- cmd.read_page.page_cache_descr = descr;
- rrdeng_enq_cmd(&ctx->worker_config, &cmd);
+ internal_fatal(entries_by_size != entries_by_time, "DBENGINE: wrong entries by time again!");
- debug(D_RRDENGINE, "%s: Waiting for page to be asynchronously read from disk:", __func__);
- if(unlikely(debug_flags & D_RRDENGINE))
- print_page_cache_descr(descr, "", true);
- while (!(pg_cache_descr->flags & RRD_PAGE_POPULATED)) {
- pg_cache_wait_event_unsafe(descr);
+ __atomic_add_fetch(&rrdeng_cache_efficiency_stats.pages_invalid_entries_fixed, 1, __ATOMIC_RELAXED);
}
- /* success */
- /* Downgrade exclusive reference to allow other readers */
- pg_cache_descr->flags &= ~RRD_PAGE_LOCKED;
- pg_cache_wake_up_waiters_unsafe(descr);
- rrdeng_page_descr_mutex_unlock(ctx, descr);
- rrd_stat_atomic_add(&ctx->stats.pg_cache_misses, 1);
- return descr;
+ *entries = entries_by_time;
}
- uv_rwlock_rdunlock(&page_index->lock);
- debug(D_RRDENGINE, "%s: Waiting for page to be unlocked:", __func__);
- if(unlikely(debug_flags & D_RRDENGINE))
- print_page_cache_descr(descr, "", true);
- if (!(flags & RRD_PAGE_POPULATED))
- page_not_in_cache = 1;
-
- if (pg_cache_timedwait_event_unsafe(descr, default_rrdeng_page_fetch_timeout) == UV_ETIMEDOUT) {
- error_report("Page cache timeout while waiting for page %p : retry count = %d", descr, retry_count);
- ++retry_count;
+
+ if(unlikely(page_end_time_s < now_s)) {
+ __atomic_add_fetch(&rrdeng_cache_efficiency_stats.pages_past_time_skipped, 1, __ATOMIC_RELAXED);
+ pgc_page_release(main_cache, page);
+ pdc_page_status_set(pd, PDC_PAGE_SKIP | PDC_PAGE_RELEASED);
+ pd->page = page = NULL;
+ continue;
}
- rrdeng_page_descr_mutex_unlock(ctx, descr);
- /* reset scan to find again */
- uv_rwlock_rdlock(&page_index->lock);
+ if(page_from_pd)
+ // PDC_PAGE_RELEASED is for pdc_destroy() to not release the page twice - the caller will release it
+ pdc_page_status_set(pd, PDC_PAGE_RELEASED | PDC_PAGE_PROCESSED);
+ else
+ pdc_page_status_set(pd, PDC_PAGE_PROCESSED);
}
- uv_rwlock_rdunlock(&page_index->lock);
- if (!(flags & RRD_PAGE_DIRTY))
- pg_cache_replaceQ_set_hot(ctx, descr);
- pg_cache_release_pages(ctx, 1);
- if (page_not_in_cache)
- rrd_stat_atomic_add(&ctx->stats.pg_cache_misses, 1);
- else
- rrd_stat_atomic_add(&ctx->stats.pg_cache_hits, 1);
- return descr;
-}
+ if(gaps && !pdc->executed_with_gaps)
+ __atomic_add_fetch(&rrdeng_cache_efficiency_stats.queries_executed_with_gaps, 1, __ATOMIC_RELAXED);
+ pdc->executed_with_gaps = +gaps;
-struct pg_cache_page_index *create_page_index(uuid_t *id, struct rrdengine_instance *ctx)
-{
- struct pg_cache_page_index *page_index;
-
- page_index = mallocz(sizeof(*page_index));
- page_index->JudyL_array = (Pvoid_t) NULL;
- uuid_copy(page_index->id, *id);
- fatal_assert(0 == uv_rwlock_init(&page_index->lock));
- page_index->oldest_time_ut = INVALID_TIME;
- page_index->latest_time_ut = INVALID_TIME;
- page_index->prev = NULL;
- page_index->page_count = 0;
- page_index->refcount = 0;
- page_index->writers = 0;
- page_index->ctx = ctx;
- page_index->latest_update_every_s = default_rrd_update_every;
-
- return page_index;
-}
+ if(page) {
+ if(waited)
+ __atomic_add_fetch(&rrdeng_cache_efficiency_stats.page_next_wait_loaded, 1, __ATOMIC_RELAXED);
+ else
+ __atomic_add_fetch(&rrdeng_cache_efficiency_stats.page_next_nowait_loaded, 1, __ATOMIC_RELAXED);
+ }
+ else {
+ if(waited)
+ __atomic_add_fetch(&rrdeng_cache_efficiency_stats.page_next_wait_failed, 1, __ATOMIC_RELAXED);
+ else
+ __atomic_add_fetch(&rrdeng_cache_efficiency_stats.page_next_nowait_failed, 1, __ATOMIC_RELAXED);
+ }
-static void init_metrics_index(struct rrdengine_instance *ctx)
-{
- struct page_cache *pg_cache = &ctx->pg_cache;
+ if(waited) {
+ if(preloaded)
+ __atomic_add_fetch(&rrdeng_cache_efficiency_stats.query_time_to_slow_preload_next_page, now_monotonic_usec() - start_ut, __ATOMIC_RELAXED);
+ else
+ __atomic_add_fetch(&rrdeng_cache_efficiency_stats.query_time_to_slow_disk_next_page, now_monotonic_usec() - start_ut, __ATOMIC_RELAXED);
+ }
+ else {
+ if(preloaded)
+ __atomic_add_fetch(&rrdeng_cache_efficiency_stats.query_time_to_fast_preload_next_page, now_monotonic_usec() - start_ut, __ATOMIC_RELAXED);
+ else
+ __atomic_add_fetch(&rrdeng_cache_efficiency_stats.query_time_to_fast_disk_next_page, now_monotonic_usec() - start_ut, __ATOMIC_RELAXED);
+ }
- pg_cache->metrics_index.JudyHS_array = (Pvoid_t) NULL;
- pg_cache->metrics_index.last_page_index = NULL;
- fatal_assert(0 == uv_rwlock_init(&pg_cache->metrics_index.lock));
+ return page;
}
-static void init_replaceQ(struct rrdengine_instance *ctx)
-{
- struct page_cache *pg_cache = &ctx->pg_cache;
+void pgc_open_add_hot_page(Word_t section, Word_t metric_id, time_t start_time_s, time_t end_time_s, time_t update_every_s,
+ struct rrdengine_datafile *datafile, uint64_t extent_offset, unsigned extent_size, uint32_t page_length) {
+
+ if(!datafile_acquire(datafile, DATAFILE_ACQUIRE_OPEN_CACHE)) // for open cache item
+ fatal("DBENGINE: cannot acquire datafile to put page in open cache");
+
+ struct extent_io_data ext_io_data = {
+ .file = datafile->file,
+ .fileno = datafile->fileno,
+ .pos = extent_offset,
+ .bytes = extent_size,
+ .page_length = page_length
+ };
+
+ PGC_ENTRY page_entry = {
+ .hot = true,
+ .section = section,
+ .metric_id = metric_id,
+ .start_time_s = start_time_s,
+ .end_time_s = end_time_s,
+ .update_every_s = update_every_s,
+ .size = 0,
+ .data = datafile,
+ .custom_data = (uint8_t *) &ext_io_data,
+ };
+
+ internal_fatal(!datafile->fileno, "DBENGINE: datafile supplied does not have a number");
+
+ bool added = true;
+ PGC_PAGE *page = pgc_page_add_and_acquire(open_cache, page_entry, &added);
+ int tries = 100;
+ while(!added && page_entry.end_time_s > pgc_page_end_time_s(page) && tries--) {
+ pgc_page_to_clean_evict_or_release(open_cache, page);
+ page = pgc_page_add_and_acquire(open_cache, page_entry, &added);
+ }
- pg_cache->replaceQ.head = NULL;
- pg_cache->replaceQ.tail = NULL;
- fatal_assert(0 == uv_rwlock_init(&pg_cache->replaceQ.lock));
-}
+ if(!added) {
+ datafile_release(datafile, DATAFILE_ACQUIRE_OPEN_CACHE);
-static void init_committed_page_index(struct rrdengine_instance *ctx)
-{
- struct page_cache *pg_cache = &ctx->pg_cache;
+ internal_fatal(page_entry.end_time_s > pgc_page_end_time_s(page),
+ "DBENGINE: cannot add longer page to open cache");
+ }
- pg_cache->committed_page_index.JudyL_array = (Pvoid_t) NULL;
- fatal_assert(0 == uv_rwlock_init(&pg_cache->committed_page_index.lock));
- pg_cache->committed_page_index.latest_corr_id = 0;
- pg_cache->committed_page_index.nr_committed_pages = 0;
+ pgc_page_release(open_cache, (PGC_PAGE *)page);
}
-void init_page_cache(struct rrdengine_instance *ctx)
-{
- struct page_cache *pg_cache = &ctx->pg_cache;
+size_t dynamic_open_cache_size(void) {
+ size_t main_cache_size = pgc_get_wanted_cache_size(main_cache);
+ size_t target_size = main_cache_size / 100 * 5;
- pg_cache->page_descriptors = 0;
- pg_cache->populated_pages = 0;
- fatal_assert(0 == uv_rwlock_init(&pg_cache->pg_cache_rwlock));
+ if(target_size < 2 * 1024 * 1024)
+ target_size = 2 * 1024 * 1024;
- init_metrics_index(ctx);
- init_replaceQ(ctx);
- init_committed_page_index(ctx);
+ return target_size;
}
-void free_page_cache(struct rrdengine_instance *ctx)
-{
- struct page_cache *pg_cache = &ctx->pg_cache;
- Pvoid_t *PValue;
- struct pg_cache_page_index *page_index, *prev_page_index;
- Word_t Index;
- struct rrdeng_page_descr *descr;
- struct page_cache_descr *pg_cache_descr;
-
- // if we are exiting, the OS will recover all memory so do not slow down the shutdown process
- // Do the cleanup if we are compiling with NETDATA_INTERNAL_CHECKS
- // This affects the reporting of dbengine statistics which are available in real time
- // via the /api/v1/dbengine_stats endpoint
-#ifndef NETDATA_DBENGINE_FREE
- if (netdata_exit)
- return;
-#endif
- Word_t metrics_index_bytes = 0, pages_index_bytes = 0, pages_dirty_index_bytes = 0;
-
- /* Free committed page index */
- pages_dirty_index_bytes = JudyLFreeArray(&pg_cache->committed_page_index.JudyL_array, PJE0);
- fatal_assert(NULL == pg_cache->committed_page_index.JudyL_array);
-
- for (page_index = pg_cache->metrics_index.last_page_index ;
- page_index != NULL ;
- page_index = prev_page_index) {
+size_t dynamic_extent_cache_size(void) {
+ size_t main_cache_size = pgc_get_wanted_cache_size(main_cache);
+ size_t target_size = main_cache_size / 100 * 5;
- prev_page_index = page_index->prev;
+ if(target_size < 3 * 1024 * 1024)
+ target_size = 3 * 1024 * 1024;
- /* Find first page in range */
- Index = (Word_t) 0;
- PValue = JudyLFirst(page_index->JudyL_array, &Index, PJE0);
- descr = unlikely(NULL == PValue) ? NULL : *PValue;
-
- while (descr != NULL) {
- /* Iterate all page descriptors of this metric */
+ return target_size;
+}
- if (descr->pg_cache_descr_state & PG_CACHE_DESCR_ALLOCATED) {
- /* Check rrdenglocking.c */
- pg_cache_descr = descr->pg_cache_descr;
- if (pg_cache_descr->flags & RRD_PAGE_POPULATED) {
- dbengine_page_free(pg_cache_descr->page);
- }
- rrdeng_destroy_pg_cache_descr(ctx, pg_cache_descr);
- }
- rrdeng_page_descr_freez(descr);
+void pgc_and_mrg_initialize(void)
+{
+ main_mrg = mrg_create();
- PValue = JudyLNext(page_index->JudyL_array, &Index, PJE0);
- descr = unlikely(NULL == PValue) ? NULL : *PValue;
- }
+ size_t target_cache_size = (size_t)default_rrdeng_page_cache_mb * 1024ULL * 1024ULL;
+ size_t main_cache_size = (target_cache_size / 100) * 95;
+ size_t open_cache_size = 0;
+ size_t extent_cache_size = (target_cache_size / 100) * 5;
- /* Free page index */
- pages_index_bytes += JudyLFreeArray(&page_index->JudyL_array, PJE0);
- fatal_assert(NULL == page_index->JudyL_array);
- freez(page_index);
+ if(extent_cache_size < 3 * 1024 * 1024) {
+ extent_cache_size = 3 * 1024 * 1024;
+ main_cache_size = target_cache_size - extent_cache_size;
}
- /* Free metrics index */
- metrics_index_bytes = JudyHSFreeArray(&pg_cache->metrics_index.JudyHS_array, PJE0);
- fatal_assert(NULL == pg_cache->metrics_index.JudyHS_array);
- info("Freed %lu bytes of memory from page cache.", pages_dirty_index_bytes + pages_index_bytes + metrics_index_bytes);
+
+ main_cache = pgc_create(
+ "main_cache",
+ main_cache_size,
+ main_cache_free_clean_page_callback,
+ (size_t) rrdeng_pages_per_extent,
+ main_cache_flush_dirty_page_init_callback,
+ main_cache_flush_dirty_page_callback,
+ 10,
+ 10240, // if there are that many threads, evict so many at once!
+ 1000, //
+ 5, // don't delay too much other threads
+ PGC_OPTIONS_AUTOSCALE, // AUTOSCALE = 2x max hot pages
+ 0, // 0 = as many as the system cpus
+ 0
+ );
+
+ open_cache = pgc_create(
+ "open_cache",
+ open_cache_size, // the default is 1MB
+ open_cache_free_clean_page_callback,
+ 1,
+ NULL,
+ open_cache_flush_dirty_page_callback,
+ 10,
+ 10240, // if there are that many threads, evict that many at once!
+ 1000, //
+ 3, // don't delay too much other threads
+ PGC_OPTIONS_AUTOSCALE | PGC_OPTIONS_EVICT_PAGES_INLINE | PGC_OPTIONS_FLUSH_PAGES_INLINE,
+ 0, // 0 = as many as the system cpus
+ sizeof(struct extent_io_data)
+ );
+ pgc_set_dynamic_target_cache_size_callback(open_cache, dynamic_open_cache_size);
+
+ extent_cache = pgc_create(
+ "extent_cache",
+ extent_cache_size,
+ extent_cache_free_clean_page_callback,
+ 1,
+ NULL,
+ extent_cache_flush_dirty_page_callback,
+ 5,
+ 10, // it will lose up to that extents at once!
+ 100, //
+ 2, // don't delay too much other threads
+ PGC_OPTIONS_AUTOSCALE | PGC_OPTIONS_EVICT_PAGES_INLINE | PGC_OPTIONS_FLUSH_PAGES_INLINE,
+ 0, // 0 = as many as the system cpus
+ 0
+ );
+ pgc_set_dynamic_target_cache_size_callback(extent_cache, dynamic_extent_cache_size);
}
diff --git a/database/engine/pagecache.h b/database/engine/pagecache.h
index 635b0212..9ab7db07 100644
--- a/database/engine/pagecache.h
+++ b/database/engine/pagecache.h
@@ -5,66 +5,34 @@
#include "rrdengine.h"
+extern struct mrg *main_mrg;
+extern struct pgc *main_cache;
+extern struct pgc *open_cache;
+extern struct pgc *extent_cache;
+
/* Forward declarations */
struct rrdengine_instance;
-struct extent_info;
-struct rrdeng_page_descr;
#define INVALID_TIME (0)
#define MAX_PAGE_CACHE_FETCH_RETRIES (3)
#define PAGE_CACHE_FETCH_WAIT_TIMEOUT (3)
-/* Page flags */
-#define RRD_PAGE_DIRTY (1LU << 0)
-#define RRD_PAGE_LOCKED (1LU << 1)
-#define RRD_PAGE_READ_PENDING (1LU << 2)
-#define RRD_PAGE_WRITE_PENDING (1LU << 3)
-#define RRD_PAGE_POPULATED (1LU << 4)
-
-struct page_cache_descr {
- struct rrdeng_page_descr *descr; /* parent descriptor */
- void *page;
- unsigned long flags;
- struct page_cache_descr *prev; /* LRU */
- struct page_cache_descr *next; /* LRU */
-
- unsigned refcnt;
- uv_mutex_t mutex; /* always take it after the page cache lock or after the commit lock */
- uv_cond_t cond;
- unsigned waiters;
-};
-
-/* Page cache descriptor flags, state = 0 means no descriptor */
-#define PG_CACHE_DESCR_ALLOCATED (1LU << 0)
-#define PG_CACHE_DESCR_DESTROY (1LU << 1)
-#define PG_CACHE_DESCR_LOCKED (1LU << 2)
-#define PG_CACHE_DESCR_SHIFT (3)
-#define PG_CACHE_DESCR_USERS_MASK (((unsigned long)-1) << PG_CACHE_DESCR_SHIFT)
-#define PG_CACHE_DESCR_FLAGS_MASK (((unsigned long)-1) >> (BITS_PER_ULONG - PG_CACHE_DESCR_SHIFT))
+extern struct rrdeng_cache_efficiency_stats rrdeng_cache_efficiency_stats;
-/*
- * Page cache descriptor state bits (works for both 32-bit and 64-bit architectures):
- *
- * 63 ... 31 ... 3 | 2 | 1 | 0|
- * -----------------------------+------------+------------+-----------|
- * number of descriptor users | DESTROY | LOCKED | ALLOCATED |
- */
-struct rrdeng_page_descr {
- uuid_t *id; /* never changes */
- struct extent_info *extent;
-
- /* points to ephemeral page cache descriptor if the page resides in the cache */
- struct page_cache_descr *pg_cache_descr;
-
- /* Compare-And-Swap target for page cache descriptor allocation algorithm */
- volatile unsigned long pg_cache_descr_state;
-
- /* page information */
+struct page_descr_with_data {
+ uuid_t *id;
+ Word_t metric_id;
usec_t start_time_ut;
usec_t end_time_ut;
- uint32_t update_every_s:24;
uint8_t type;
+ uint32_t update_every_s;
uint32_t page_length;
+ uint8_t *page;
+
+ struct {
+ struct page_descr_with_data *prev;
+ struct page_descr_with_data *next;
+ } link;
};
#define PAGE_INFO_SCRATCH_SZ (8)
@@ -76,179 +44,21 @@ struct rrdeng_page_info {
uint32_t page_length;
};
-/* returns 1 for success, 0 for failure */
-typedef int pg_cache_page_info_filter_t(struct rrdeng_page_descr *);
-
-#define PAGE_CACHE_MAX_PRELOAD_PAGES (256)
-
struct pg_alignment {
- uint32_t page_length;
+ uint32_t page_position;
uint32_t refcount;
+ uint16_t initial_slots;
};
-/* maps time ranges to pages */
-struct pg_cache_page_index {
- uuid_t id;
- /*
- * care: JudyL_array indices are converted from useconds to seconds to fit in one word in 32-bit architectures
- * TODO: examine if we want to support better granularity than seconds
- */
- Pvoid_t JudyL_array;
- Word_t page_count;
- unsigned short refcount;
- unsigned short writers;
- uv_rwlock_t lock;
-
- /*
- * Only one effective writer, data deletion workqueue.
- * It's also written during the DB loading phase.
- */
- usec_t oldest_time_ut;
-
- /*
- * Only one effective writer, data collection thread.
- * It's also written by the data deletion workqueue when data collection is disabled for this metric.
- */
- usec_t latest_time_ut;
-
- struct rrdengine_instance *ctx;
- uint32_t latest_update_every_s;
-
- struct pg_cache_page_index *prev;
-};
-
-/* maps UUIDs to page indices */
-struct pg_cache_metrics_index {
- uv_rwlock_t lock;
- Pvoid_t JudyHS_array;
- struct pg_cache_page_index *last_page_index;
-};
-
-/* gathers dirty pages to be written on disk */
-struct pg_cache_committed_page_index {
- uv_rwlock_t lock;
-
- Pvoid_t JudyL_array;
-
- /*
- * Dirty page correlation ID is a hint. Dirty pages that are correlated should have
- * a small correlation ID difference. Dirty pages in memory should never have the
- * same ID at the same time for correctness.
- */
- Word_t latest_corr_id;
-
- unsigned nr_committed_pages;
-};
-
-/*
- * Gathers populated pages to be evicted.
- * Relies on page cache descriptors being there as it uses their memory.
- */
-struct pg_cache_replaceQ {
- uv_rwlock_t lock; /* LRU lock */
-
- struct page_cache_descr *head; /* LRU */
- struct page_cache_descr *tail; /* MRU */
-};
-
-struct page_cache { /* TODO: add statistics */
- uv_rwlock_t pg_cache_rwlock; /* page cache lock */
-
- struct pg_cache_metrics_index metrics_index;
- struct pg_cache_committed_page_index committed_page_index;
- struct pg_cache_replaceQ replaceQ;
-
- unsigned page_descriptors;
- unsigned populated_pages;
-};
-
-void pg_cache_wake_up_waiters_unsafe(struct rrdeng_page_descr *descr);
-void pg_cache_wake_up_waiters(struct rrdengine_instance *ctx, struct rrdeng_page_descr *descr);
-void pg_cache_wait_event_unsafe(struct rrdeng_page_descr *descr);
-unsigned long pg_cache_wait_event(struct rrdengine_instance *ctx, struct rrdeng_page_descr *descr);
-void pg_cache_replaceQ_insert(struct rrdengine_instance *ctx,
- struct rrdeng_page_descr *descr);
-void pg_cache_replaceQ_delete(struct rrdengine_instance *ctx,
- struct rrdeng_page_descr *descr);
-void pg_cache_replaceQ_set_hot(struct rrdengine_instance *ctx,
- struct rrdeng_page_descr *descr);
-struct rrdeng_page_descr *pg_cache_create_descr(void);
-int pg_cache_try_get_unsafe(struct rrdeng_page_descr *descr, int exclusive_access);
-void pg_cache_put_unsafe(struct rrdeng_page_descr *descr);
-void pg_cache_put(struct rrdengine_instance *ctx, struct rrdeng_page_descr *descr);
-void pg_cache_insert(struct rrdengine_instance *ctx, struct pg_cache_page_index *index,
- struct rrdeng_page_descr *descr);
-uint8_t pg_cache_punch_hole(struct rrdengine_instance *ctx, struct rrdeng_page_descr *descr,
- uint8_t remove_dirty, uint8_t is_exclusive_holder, uuid_t *metric_id);
-usec_t pg_cache_oldest_time_in_range(struct rrdengine_instance *ctx, uuid_t *id,
- usec_t start_time_ut, usec_t end_time_ut);
-void pg_cache_get_filtered_info_prev(struct rrdengine_instance *ctx, struct pg_cache_page_index *page_index,
- usec_t point_in_time_ut, pg_cache_page_info_filter_t *filter,
- struct rrdeng_page_info *page_info);
-struct rrdeng_page_descr *pg_cache_lookup_unpopulated_and_lock(struct rrdengine_instance *ctx, uuid_t *id,
- usec_t start_time_ut);
-unsigned
- pg_cache_preload(struct rrdengine_instance *ctx, uuid_t *id, usec_t start_time_ut, usec_t end_time_ut,
- struct rrdeng_page_info **page_info_arrayp, struct pg_cache_page_index **ret_page_indexp);
-struct rrdeng_page_descr *
- pg_cache_lookup(struct rrdengine_instance *ctx, struct pg_cache_page_index *index, uuid_t *id,
- usec_t point_in_time_ut);
-struct rrdeng_page_descr *
- pg_cache_lookup_next(struct rrdengine_instance *ctx, struct pg_cache_page_index *index, uuid_t *id,
- usec_t start_time_ut, usec_t end_time_ut);
-struct pg_cache_page_index *create_page_index(uuid_t *id, struct rrdengine_instance *ctx);
-void init_page_cache(struct rrdengine_instance *ctx);
-void free_page_cache(struct rrdengine_instance *ctx);
-void pg_cache_add_new_metric_time(struct pg_cache_page_index *page_index, struct rrdeng_page_descr *descr);
-void pg_cache_update_metric_times(struct pg_cache_page_index *page_index);
-unsigned long pg_cache_hard_limit(struct rrdengine_instance *ctx);
-unsigned long pg_cache_soft_limit(struct rrdengine_instance *ctx);
-unsigned long pg_cache_committed_hard_limit(struct rrdengine_instance *ctx);
-
-void rrdeng_page_descr_aral_go_singlethreaded(void);
-void rrdeng_page_descr_aral_go_multithreaded(void);
-void rrdeng_page_descr_use_malloc(void);
-void rrdeng_page_descr_use_mmap(void);
-bool rrdeng_page_descr_is_mmap(void);
-struct rrdeng_page_descr *rrdeng_page_descr_mallocz(void);
-void rrdeng_page_descr_freez(struct rrdeng_page_descr *descr);
-
-static inline void
- pg_cache_atomic_get_pg_info(struct rrdeng_page_descr *descr, usec_t *end_time_ut_p, uint32_t *page_lengthp)
-{
- usec_t end_time_ut, old_end_time_ut;
- uint32_t page_length;
-
- if (NULL == descr->extent) {
- /* this page is currently being modified, get consistent info locklessly */
- do {
- end_time_ut = descr->end_time_ut;
- __sync_synchronize();
- old_end_time_ut = end_time_ut;
- page_length = descr->page_length;
- __sync_synchronize();
- end_time_ut = descr->end_time_ut;
- __sync_synchronize();
- } while ((end_time_ut != old_end_time_ut || (end_time_ut & 1) != 0));
+struct rrdeng_query_handle;
+struct page_details_control;
- *end_time_ut_p = end_time_ut;
- *page_lengthp = page_length;
- } else {
- *end_time_ut_p = descr->end_time_ut;
- *page_lengthp = descr->page_length;
- }
-}
+void rrdeng_prep_wait(struct page_details_control *pdc);
+void rrdeng_prep_query(struct page_details_control *pdc);
+void pg_cache_preload(struct rrdeng_query_handle *handle);
+struct pgc_page *pg_cache_lookup_next(struct rrdengine_instance *ctx, struct page_details_control *pdc, time_t now_s, time_t last_update_every_s, size_t *entries);
+void pgc_and_mrg_initialize(void);
-/* The caller must hold a reference to the page and must have already set the new data */
-static inline void pg_cache_atomic_set_pg_info(struct rrdeng_page_descr *descr, usec_t end_time_ut, uint32_t page_length)
-{
- fatal_assert(!(end_time_ut & 1));
- __sync_synchronize();
- descr->end_time_ut |= 1; /* mark start of uncertainty period by adding 1 microsecond */
- __sync_synchronize();
- descr->page_length = page_length;
- __sync_synchronize();
- descr->end_time_ut = end_time_ut; /* mark end of uncertainty period */
-}
+void pgc_open_add_hot_page(Word_t section, Word_t metric_id, time_t start_time_s, time_t end_time_s, time_t update_every_s, struct rrdengine_datafile *datafile, uint64_t extent_offset, unsigned extent_size, uint32_t page_length);
#endif /* NETDATA_PAGECACHE_H */
diff --git a/database/engine/pdc.c b/database/engine/pdc.c
new file mode 100644
index 00000000..8b8e7195
--- /dev/null
+++ b/database/engine/pdc.c
@@ -0,0 +1,1282 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+#define NETDATA_RRD_INTERNALS
+#include "pdc.h"
+
+struct extent_page_details_list {
+ uv_file file;
+ uint64_t extent_offset;
+ uint32_t extent_size;
+ unsigned number_of_pages_in_JudyL;
+ Pvoid_t page_details_by_metric_id_JudyL;
+ struct page_details_control *pdc;
+ struct rrdengine_datafile *datafile;
+
+ struct rrdeng_cmd *cmd;
+ bool head_to_datafile_extent_queries_pending_for_extent;
+
+ struct {
+ struct extent_page_details_list *prev;
+ struct extent_page_details_list *next;
+ } query;
+};
+
+typedef struct datafile_extent_offset_list {
+ uv_file file;
+ unsigned fileno;
+ Pvoid_t extent_pd_list_by_extent_offset_JudyL;
+} DEOL;
+
+// ----------------------------------------------------------------------------
+// PDC cache
+
+static struct {
+ struct {
+ ARAL *ar;
+ } pdc;
+
+ struct {
+ ARAL *ar;
+ } pd;
+
+ struct {
+ ARAL *ar;
+ } epdl;
+
+ struct {
+ ARAL *ar;
+ } deol;
+} pdc_globals = {};
+
+void pdc_init(void) {
+ pdc_globals.pdc.ar = aral_create(
+ "dbengine-pdc",
+ sizeof(PDC),
+ 0,
+ 65536,
+ NULL,
+ NULL, NULL, false, false
+ );
+}
+
+PDC *pdc_get(void) {
+ PDC *pdc = aral_mallocz(pdc_globals.pdc.ar);
+ memset(pdc, 0, sizeof(PDC));
+ return pdc;
+}
+
+static void pdc_release(PDC *pdc) {
+ aral_freez(pdc_globals.pdc.ar, pdc);
+}
+
+size_t pdc_cache_size(void) {
+ return aral_overhead(pdc_globals.pdc.ar) + aral_structures(pdc_globals.pdc.ar);
+}
+
+// ----------------------------------------------------------------------------
+// PD cache
+
+void page_details_init(void) {
+ pdc_globals.pd.ar = aral_create(
+ "dbengine-pd",
+ sizeof(struct page_details),
+ 0,
+ 65536,
+ NULL,
+ NULL, NULL, false, false
+ );
+}
+
+struct page_details *page_details_get(void) {
+ struct page_details *pd = aral_mallocz(pdc_globals.pd.ar);
+ memset(pd, 0, sizeof(struct page_details));
+ return pd;
+}
+
+static void page_details_release(struct page_details *pd) {
+ aral_freez(pdc_globals.pd.ar, pd);
+}
+
+size_t pd_cache_size(void) {
+ return aral_overhead(pdc_globals.pd.ar) + aral_structures(pdc_globals.pd.ar);
+}
+
+// ----------------------------------------------------------------------------
+// epdl cache
+
+void epdl_init(void) {
+ pdc_globals.epdl.ar = aral_create(
+ "dbengine-epdl",
+ sizeof(EPDL),
+ 0,
+ 65536,
+ NULL,
+ NULL, NULL, false, false
+ );
+}
+
+static EPDL *epdl_get(void) {
+ EPDL *epdl = aral_mallocz(pdc_globals.epdl.ar);
+ memset(epdl, 0, sizeof(EPDL));
+ return epdl;
+}
+
+static void epdl_release(EPDL *epdl) {
+ aral_freez(pdc_globals.epdl.ar, epdl);
+}
+
+size_t epdl_cache_size(void) {
+ return aral_overhead(pdc_globals.epdl.ar) + aral_structures(pdc_globals.epdl.ar);
+}
+
+// ----------------------------------------------------------------------------
+// deol cache
+
+void deol_init(void) {
+ pdc_globals.deol.ar = aral_create(
+ "dbengine-deol",
+ sizeof(DEOL),
+ 0,
+ 65536,
+ NULL,
+ NULL, NULL, false, false
+ );
+}
+
+static DEOL *deol_get(void) {
+ DEOL *deol = aral_mallocz(pdc_globals.deol.ar);
+ memset(deol, 0, sizeof(DEOL));
+ return deol;
+}
+
+static void deol_release(DEOL *deol) {
+ aral_freez(pdc_globals.deol.ar, deol);
+}
+
+size_t deol_cache_size(void) {
+ return aral_overhead(pdc_globals.deol.ar) + aral_structures(pdc_globals.deol.ar);
+}
+
+// ----------------------------------------------------------------------------
+// extent with buffer cache
+
+static struct {
+ struct {
+ SPINLOCK spinlock;
+ struct extent_buffer *available_items;
+ size_t available;
+ } protected;
+
+ struct {
+ size_t allocated;
+ size_t allocated_bytes;
+ } atomics;
+
+ size_t max_size;
+
+} extent_buffer_globals = {
+ .protected = {
+ .spinlock = NETDATA_SPINLOCK_INITIALIZER,
+ .available_items = NULL,
+ .available = 0,
+ },
+ .atomics = {
+ .allocated = 0,
+ .allocated_bytes = 0,
+ },
+ .max_size = MAX_PAGES_PER_EXTENT * RRDENG_BLOCK_SIZE,
+};
+
+void extent_buffer_init(void) {
+ size_t max_extent_uncompressed = MAX_PAGES_PER_EXTENT * RRDENG_BLOCK_SIZE;
+ size_t max_size = (size_t)LZ4_compressBound(MAX_PAGES_PER_EXTENT * RRDENG_BLOCK_SIZE);
+ if(max_size < max_extent_uncompressed)
+ max_size = max_extent_uncompressed;
+
+ extent_buffer_globals.max_size = max_size;
+}
+
+void extent_buffer_cleanup1(void) {
+ struct extent_buffer *item = NULL;
+
+ if(!netdata_spinlock_trylock(&extent_buffer_globals.protected.spinlock))
+ return;
+
+ if(extent_buffer_globals.protected.available_items && extent_buffer_globals.protected.available > 1) {
+ item = extent_buffer_globals.protected.available_items;
+ DOUBLE_LINKED_LIST_REMOVE_ITEM_UNSAFE(extent_buffer_globals.protected.available_items, item, cache.prev, cache.next);
+ extent_buffer_globals.protected.available--;
+ }
+
+ netdata_spinlock_unlock(&extent_buffer_globals.protected.spinlock);
+
+ if(item) {
+ size_t bytes = sizeof(struct extent_buffer) + item->bytes;
+ freez(item);
+ __atomic_sub_fetch(&extent_buffer_globals.atomics.allocated, 1, __ATOMIC_RELAXED);
+ __atomic_sub_fetch(&extent_buffer_globals.atomics.allocated_bytes, bytes, __ATOMIC_RELAXED);
+ }
+}
+
+struct extent_buffer *extent_buffer_get(size_t size) {
+ internal_fatal(size > extent_buffer_globals.max_size, "DBENGINE: extent size is too big");
+
+ struct extent_buffer *eb = NULL;
+
+ if(size < extent_buffer_globals.max_size)
+ size = extent_buffer_globals.max_size;
+
+ netdata_spinlock_lock(&extent_buffer_globals.protected.spinlock);
+ if(likely(extent_buffer_globals.protected.available_items)) {
+ eb = extent_buffer_globals.protected.available_items;
+ DOUBLE_LINKED_LIST_REMOVE_ITEM_UNSAFE(extent_buffer_globals.protected.available_items, eb, cache.prev, cache.next);
+ extent_buffer_globals.protected.available--;
+ }
+ netdata_spinlock_unlock(&extent_buffer_globals.protected.spinlock);
+
+ if(unlikely(eb && eb->bytes < size)) {
+ size_t bytes = sizeof(struct extent_buffer) + eb->bytes;
+ freez(eb);
+ eb = NULL;
+ __atomic_sub_fetch(&extent_buffer_globals.atomics.allocated, 1, __ATOMIC_RELAXED);
+ __atomic_sub_fetch(&extent_buffer_globals.atomics.allocated_bytes, bytes, __ATOMIC_RELAXED);
+ }
+
+ if(unlikely(!eb)) {
+ size_t bytes = sizeof(struct extent_buffer) + size;
+ eb = mallocz(bytes);
+ eb->bytes = size;
+ __atomic_add_fetch(&extent_buffer_globals.atomics.allocated, 1, __ATOMIC_RELAXED);
+ __atomic_add_fetch(&extent_buffer_globals.atomics.allocated_bytes, bytes, __ATOMIC_RELAXED);
+ }
+
+ return eb;
+}
+
+void extent_buffer_release(struct extent_buffer *eb) {
+ if(unlikely(!eb)) return;
+
+ netdata_spinlock_lock(&extent_buffer_globals.protected.spinlock);
+ DOUBLE_LINKED_LIST_APPEND_ITEM_UNSAFE(extent_buffer_globals.protected.available_items, eb, cache.prev, cache.next);
+ extent_buffer_globals.protected.available++;
+ netdata_spinlock_unlock(&extent_buffer_globals.protected.spinlock);
+}
+
+size_t extent_buffer_cache_size(void) {
+ return __atomic_load_n(&extent_buffer_globals.atomics.allocated_bytes, __ATOMIC_RELAXED);
+}
+
+// ----------------------------------------------------------------------------
+// epdl logic
+
+static void epdl_destroy(EPDL *epdl)
+{
+ Pvoid_t *pd_by_start_time_s_JudyL;
+ Word_t metric_id_index = 0;
+ bool metric_id_first = true;
+ while ((pd_by_start_time_s_JudyL = PDCJudyLFirstThenNext(
+ epdl->page_details_by_metric_id_JudyL,
+ &metric_id_index, &metric_id_first)))
+ PDCJudyLFreeArray(pd_by_start_time_s_JudyL, PJE0);
+
+ PDCJudyLFreeArray(&epdl->page_details_by_metric_id_JudyL, PJE0);
+ epdl_release(epdl);
+}
+
+static void epdl_mark_all_not_loaded_pages_as_failed(EPDL *epdl, PDC_PAGE_STATUS tags, size_t *statistics_counter)
+{
+ size_t pages_matched = 0;
+
+ Word_t metric_id_index = 0;
+ bool metric_id_first = true;
+ Pvoid_t *pd_by_start_time_s_JudyL;
+ while((pd_by_start_time_s_JudyL = PDCJudyLFirstThenNext(epdl->page_details_by_metric_id_JudyL, &metric_id_index, &metric_id_first))) {
+
+ Word_t start_time_index = 0;
+ bool start_time_first = true;
+ Pvoid_t *PValue;
+ while ((PValue = PDCJudyLFirstThenNext(*pd_by_start_time_s_JudyL, &start_time_index, &start_time_first))) {
+ struct page_details *pd = *PValue;
+
+ if(!pd->page && !pdc_page_status_check(pd, PDC_PAGE_FAILED|PDC_PAGE_READY)) {
+ pdc_page_status_set(pd, PDC_PAGE_FAILED | tags);
+ pages_matched++;
+ }
+ }
+ }
+
+ if(pages_matched && statistics_counter)
+ __atomic_add_fetch(statistics_counter, pages_matched, __ATOMIC_RELAXED);
+}
+/*
+static bool epdl_check_if_pages_are_already_in_cache(struct rrdengine_instance *ctx, EPDL *epdl, PDC_PAGE_STATUS tags)
+{
+ size_t count_remaining = 0;
+ size_t found = 0;
+
+ Word_t metric_id_index = 0;
+ bool metric_id_first = true;
+ Pvoid_t *pd_by_start_time_s_JudyL;
+ while((pd_by_start_time_s_JudyL = PDCJudyLFirstThenNext(epdl->page_details_by_metric_id_JudyL, &metric_id_index, &metric_id_first))) {
+
+ Word_t start_time_index = 0;
+ bool start_time_first = true;
+ Pvoid_t *PValue;
+ while ((PValue = PDCJudyLFirstThenNext(*pd_by_start_time_s_JudyL, &start_time_index, &start_time_first))) {
+ struct page_details *pd = *PValue;
+ if (pd->page)
+ continue;
+
+ pd->page = pgc_page_get_and_acquire(main_cache, (Word_t) ctx, pd->metric_id, pd->first_time_s, PGC_SEARCH_EXACT);
+ if (pd->page) {
+ found++;
+ pdc_page_status_set(pd, PDC_PAGE_READY | tags);
+ }
+ else
+ count_remaining++;
+ }
+ }
+
+ if(found) {
+ __atomic_add_fetch(&rrdeng_cache_efficiency_stats.pages_load_ok_preloaded, found, __ATOMIC_RELAXED);
+ __atomic_add_fetch(&rrdeng_cache_efficiency_stats.pages_data_source_main_cache, found, __ATOMIC_RELAXED);
+ }
+
+ return count_remaining == 0;
+}
+*/
+
+// ----------------------------------------------------------------------------
+// PDC logic
+
+static void pdc_destroy(PDC *pdc) {
+ mrg_metric_release(main_mrg, pdc->metric);
+ completion_destroy(&pdc->prep_completion);
+ completion_destroy(&pdc->page_completion);
+
+ Pvoid_t *PValue;
+ struct page_details *pd;
+ Word_t time_index = 0;
+ bool first_then_next = true;
+ size_t unroutable = 0, cancelled = 0;
+ while((PValue = PDCJudyLFirstThenNext(pdc->page_list_JudyL, &time_index, &first_then_next))) {
+ pd = *PValue;
+
+ // no need for atomics here - we are done...
+ PDC_PAGE_STATUS status = pd->status;
+
+ if(status & PDC_PAGE_DATAFILE_ACQUIRED) {
+ datafile_release(pd->datafile.ptr, DATAFILE_ACQUIRE_PAGE_DETAILS);
+ pd->datafile.ptr = NULL;
+ }
+
+ internal_fatal(pd->datafile.ptr, "DBENGINE: page details has a datafile.ptr that is not released.");
+
+ if(!pd->page && !(status & (PDC_PAGE_READY | PDC_PAGE_FAILED | PDC_PAGE_RELEASED | PDC_PAGE_SKIP | PDC_PAGE_INVALID | PDC_PAGE_CANCELLED))) {
+ // pdc_page_status_set(pd, PDC_PAGE_FAILED);
+ unroutable++;
+ }
+ else if(!pd->page && (status & PDC_PAGE_CANCELLED))
+ cancelled++;
+
+ if(pd->page && !(status & PDC_PAGE_RELEASED)) {
+ pgc_page_release(main_cache, pd->page);
+ // pdc_page_status_set(pd, PDC_PAGE_RELEASED);
+ }
+
+ page_details_release(pd);
+ }
+
+ PDCJudyLFreeArray(&pdc->page_list_JudyL, PJE0);
+
+ __atomic_sub_fetch(&rrdeng_cache_efficiency_stats.currently_running_queries, 1, __ATOMIC_RELAXED);
+ __atomic_sub_fetch(&pdc->ctx->atomic.inflight_queries, 1, __ATOMIC_RELAXED);
+ pdc_release(pdc);
+
+ if(unroutable)
+ __atomic_add_fetch(&rrdeng_cache_efficiency_stats.pages_load_fail_unroutable, unroutable, __ATOMIC_RELAXED);
+
+ if(cancelled)
+ __atomic_add_fetch(&rrdeng_cache_efficiency_stats.pages_load_fail_cancelled, cancelled, __ATOMIC_RELAXED);
+}
+
+void pdc_acquire(PDC *pdc) {
+ netdata_spinlock_lock(&pdc->refcount_spinlock);
+
+ if(pdc->refcount < 1)
+ fatal("DBENGINE: pdc is not referenced and cannot be acquired");
+
+ pdc->refcount++;
+ netdata_spinlock_unlock(&pdc->refcount_spinlock);
+}
+
+bool pdc_release_and_destroy_if_unreferenced(PDC *pdc, bool worker, bool router __maybe_unused) {
+ if(unlikely(!pdc))
+ return true;
+
+ netdata_spinlock_lock(&pdc->refcount_spinlock);
+
+ if(pdc->refcount <= 0)
+ fatal("DBENGINE: pdc is not referenced and cannot be released");
+
+ pdc->refcount--;
+
+ if (pdc->refcount <= 1 && worker) {
+ // when 1 refcount is remaining, and we are a worker,
+ // we can mark the job completed:
+ // - if the remaining refcount is from the query caller, we will wake it up
+ // - if the remaining refcount is from another worker, the query thread is already away
+ completion_mark_complete(&pdc->page_completion);
+ }
+
+ if (pdc->refcount == 0) {
+ netdata_spinlock_unlock(&pdc->refcount_spinlock);
+ pdc_destroy(pdc);
+ return true;
+ }
+
+ netdata_spinlock_unlock(&pdc->refcount_spinlock);
+ return false;
+}
+
+void epdl_cmd_queued(void *epdl_ptr, struct rrdeng_cmd *cmd) {
+ EPDL *epdl = epdl_ptr;
+ epdl->cmd = cmd;
+}
+
+void epdl_cmd_dequeued(void *epdl_ptr) {
+ EPDL *epdl = epdl_ptr;
+ epdl->cmd = NULL;
+}
+
+static struct rrdeng_cmd *epdl_get_cmd(void *epdl_ptr) {
+ EPDL *epdl = epdl_ptr;
+ return epdl->cmd;
+}
+
+static bool epdl_pending_add(EPDL *epdl) {
+ bool added_new;
+
+ netdata_spinlock_lock(&epdl->datafile->extent_queries.spinlock);
+ Pvoid_t *PValue = JudyLIns(&epdl->datafile->extent_queries.pending_epdl_by_extent_offset_judyL, epdl->extent_offset, PJE0);
+ internal_fatal(!PValue || PValue == PJERR, "DBENGINE: corrupted pending extent judy");
+
+ EPDL *base = *PValue;
+
+ if(!base) {
+ added_new = true;
+ epdl->head_to_datafile_extent_queries_pending_for_extent = true;
+ }
+ else {
+ added_new = false;
+ epdl->head_to_datafile_extent_queries_pending_for_extent = false;
+ __atomic_add_fetch(&rrdeng_cache_efficiency_stats.pages_load_extent_merged, 1, __ATOMIC_RELAXED);
+
+ if(base->pdc->priority > epdl->pdc->priority)
+ rrdeng_req_cmd(epdl_get_cmd, base, epdl->pdc->priority);
+ }
+
+ DOUBLE_LINKED_LIST_APPEND_ITEM_UNSAFE(base, epdl, query.prev, query.next);
+ *PValue = base;
+
+ netdata_spinlock_unlock(&epdl->datafile->extent_queries.spinlock);
+
+ return added_new;
+}
+
+static void epdl_pending_del(EPDL *epdl) {
+ netdata_spinlock_lock(&epdl->datafile->extent_queries.spinlock);
+ if(epdl->head_to_datafile_extent_queries_pending_for_extent) {
+ epdl->head_to_datafile_extent_queries_pending_for_extent = false;
+ int rc = JudyLDel(&epdl->datafile->extent_queries.pending_epdl_by_extent_offset_judyL, epdl->extent_offset, PJE0);
+ (void) rc;
+ internal_fatal(!rc, "DBENGINE: epdl not found in pending list");
+ }
+ netdata_spinlock_unlock(&epdl->datafile->extent_queries.spinlock);
+}
+
+void pdc_to_epdl_router(struct rrdengine_instance *ctx, PDC *pdc, execute_extent_page_details_list_t exec_first_extent_list, execute_extent_page_details_list_t exec_rest_extent_list)
+{
+ Pvoid_t *PValue;
+ Pvoid_t *PValue1;
+ Pvoid_t *PValue2;
+ Word_t time_index = 0;
+ struct page_details *pd = NULL;
+
+ // this is the entire page list
+ // Lets do some deduplication
+ // 1. Per datafile
+ // 2. Per extent
+ // 3. Pages per extent will be added to the cache either as acquired or not
+
+ Pvoid_t JudyL_datafile_list = NULL;
+
+ DEOL *deol;
+ EPDL *epdl;
+
+ if (pdc->page_list_JudyL) {
+ bool first_then_next = true;
+ while((PValue = PDCJudyLFirstThenNext(pdc->page_list_JudyL, &time_index, &first_then_next))) {
+ pd = *PValue;
+
+ internal_fatal(!pd,
+ "DBENGINE: pdc page list has an empty page details entry");
+
+ if (!(pd->status & PDC_PAGE_DISK_PENDING))
+ continue;
+
+ internal_fatal(!(pd->status & PDC_PAGE_DATAFILE_ACQUIRED),
+ "DBENGINE: page details has not acquired the datafile");
+
+ internal_fatal((pd->status & (PDC_PAGE_READY | PDC_PAGE_FAILED)),
+ "DBENGINE: page details has disk pending flag but it is ready/failed");
+
+ internal_fatal(pd->page,
+ "DBENGINE: page details has a page linked to it, but it is marked for loading");
+
+ PValue1 = PDCJudyLIns(&JudyL_datafile_list, pd->datafile.fileno, PJE0);
+ if (PValue1 && !*PValue1) {
+ *PValue1 = deol = deol_get();
+ deol->extent_pd_list_by_extent_offset_JudyL = NULL;
+ deol->fileno = pd->datafile.fileno;
+ }
+ else
+ deol = *PValue1;
+
+ PValue2 = PDCJudyLIns(&deol->extent_pd_list_by_extent_offset_JudyL, pd->datafile.extent.pos, PJE0);
+ if (PValue2 && !*PValue2) {
+ *PValue2 = epdl = epdl_get();
+ epdl->page_details_by_metric_id_JudyL = NULL;
+ epdl->number_of_pages_in_JudyL = 0;
+ epdl->file = pd->datafile.file;
+ epdl->extent_offset = pd->datafile.extent.pos;
+ epdl->extent_size = pd->datafile.extent.bytes;
+ epdl->datafile = pd->datafile.ptr;
+ }
+ else
+ epdl = *PValue2;
+
+ epdl->number_of_pages_in_JudyL++;
+
+ Pvoid_t *pd_by_first_time_s_judyL = PDCJudyLIns(&epdl->page_details_by_metric_id_JudyL, pd->metric_id, PJE0);
+ Pvoid_t *pd_pptr = PDCJudyLIns(pd_by_first_time_s_judyL, pd->first_time_s, PJE0);
+ *pd_pptr = pd;
+ }
+
+ size_t extent_list_no = 0;
+ Word_t datafile_no = 0;
+ first_then_next = true;
+ while((PValue = PDCJudyLFirstThenNext(JudyL_datafile_list, &datafile_no, &first_then_next))) {
+ deol = *PValue;
+
+ bool first_then_next_extent = true;
+ Word_t pos = 0;
+ while ((PValue = PDCJudyLFirstThenNext(deol->extent_pd_list_by_extent_offset_JudyL, &pos, &first_then_next_extent))) {
+ epdl = *PValue;
+ internal_fatal(!epdl, "DBENGINE: extent_list is not populated properly");
+
+ // The extent page list can be dispatched to a worker
+ // It will need to populate the cache with "acquired" pages that are in the list (pd) only
+ // the rest of the extent pages will be added to the cache butnot acquired
+
+ pdc_acquire(pdc); // we do this for the next worker: do_read_extent_work()
+ epdl->pdc = pdc;
+
+ if(epdl_pending_add(epdl)) {
+ if (extent_list_no++ == 0)
+ exec_first_extent_list(ctx, epdl, pdc->priority);
+ else
+ exec_rest_extent_list(ctx, epdl, pdc->priority);
+ }
+ }
+ PDCJudyLFreeArray(&deol->extent_pd_list_by_extent_offset_JudyL, PJE0);
+ deol_release(deol);
+ }
+ PDCJudyLFreeArray(&JudyL_datafile_list, PJE0);
+ }
+
+ pdc_release_and_destroy_if_unreferenced(pdc, true, true);
+}
+
+void collect_page_flags_to_buffer(BUFFER *wb, RRDENG_COLLECT_PAGE_FLAGS flags) {
+ if(flags & RRDENG_PAGE_PAST_COLLECTION)
+ buffer_strcat(wb, "PAST_COLLECTION ");
+ if(flags & RRDENG_PAGE_REPEATED_COLLECTION)
+ buffer_strcat(wb, "REPEATED_COLLECTION ");
+ if(flags & RRDENG_PAGE_BIG_GAP)
+ buffer_strcat(wb, "BIG_GAP ");
+ if(flags & RRDENG_PAGE_GAP)
+ buffer_strcat(wb, "GAP ");
+ if(flags & RRDENG_PAGE_FUTURE_POINT)
+ buffer_strcat(wb, "FUTURE_POINT ");
+ if(flags & RRDENG_PAGE_CREATED_IN_FUTURE)
+ buffer_strcat(wb, "CREATED_IN_FUTURE ");
+ if(flags & RRDENG_PAGE_COMPLETED_IN_FUTURE)
+ buffer_strcat(wb, "COMPLETED_IN_FUTURE ");
+ if(flags & RRDENG_PAGE_UNALIGNED)
+ buffer_strcat(wb, "UNALIGNED ");
+ if(flags & RRDENG_PAGE_CONFLICT)
+ buffer_strcat(wb, "CONFLICT ");
+ if(flags & RRDENG_PAGE_FULL)
+ buffer_strcat(wb, "PAGE_FULL");
+ if(flags & RRDENG_PAGE_COLLECT_FINALIZE)
+ buffer_strcat(wb, "COLLECT_FINALIZE");
+ if(flags & RRDENG_PAGE_UPDATE_EVERY_CHANGE)
+ buffer_strcat(wb, "UPDATE_EVERY_CHANGE");
+ if(flags & RRDENG_PAGE_STEP_TOO_SMALL)
+ buffer_strcat(wb, "STEP_TOO_SMALL");
+ if(flags & RRDENG_PAGE_STEP_UNALIGNED)
+ buffer_strcat(wb, "STEP_UNALIGNED");
+}
+
+inline VALIDATED_PAGE_DESCRIPTOR validate_extent_page_descr(const struct rrdeng_extent_page_descr *descr, time_t now_s, time_t overwrite_zero_update_every_s, bool have_read_error) {
+ return validate_page(
+ (uuid_t *)descr->uuid,
+ (time_t) (descr->start_time_ut / USEC_PER_SEC),
+ (time_t) (descr->end_time_ut / USEC_PER_SEC),
+ 0,
+ descr->page_length,
+ descr->type,
+ 0,
+ now_s,
+ overwrite_zero_update_every_s,
+ have_read_error,
+ "loaded", 0);
+}
+
+VALIDATED_PAGE_DESCRIPTOR validate_page(
+ uuid_t *uuid,
+ time_t start_time_s,
+ time_t end_time_s,
+ time_t update_every_s, // can be zero, if unknown
+ size_t page_length,
+ uint8_t page_type,
+ size_t entries, // can be zero, if unknown
+ time_t now_s, // can be zero, to disable future timestamp check
+ time_t overwrite_zero_update_every_s, // can be zero, if unknown
+ bool have_read_error,
+ const char *msg,
+ RRDENG_COLLECT_PAGE_FLAGS flags) {
+
+ VALIDATED_PAGE_DESCRIPTOR vd = {
+ .start_time_s = start_time_s,
+ .end_time_s = end_time_s,
+ .update_every_s = update_every_s,
+ .page_length = page_length,
+ .type = page_type,
+ .is_valid = true,
+ };
+
+ // always calculate entries by size
+ vd.point_size = page_type_size[vd.type];
+ vd.entries = page_entries_by_size(vd.page_length, vd.point_size);
+
+ // allow to be called without entries (when loading pages from disk)
+ if(!entries)
+ entries = vd.entries;
+
+ // allow to be called without update every (when loading pages from disk)
+ if(!update_every_s) {
+ vd.update_every_s = (vd.entries > 1) ? ((vd.end_time_s - vd.start_time_s) / (time_t) (vd.entries - 1))
+ : overwrite_zero_update_every_s;
+
+ update_every_s = vd.update_every_s;
+ }
+
+ // another such set of checks exists in
+ // update_metric_retention_and_granularity_by_uuid()
+
+ bool updated = false;
+
+ if( have_read_error ||
+ vd.page_length == 0 ||
+ vd.page_length > RRDENG_BLOCK_SIZE ||
+ vd.start_time_s > vd.end_time_s ||
+ (now_s && vd.end_time_s > now_s) ||
+ vd.start_time_s == 0 ||
+ vd.end_time_s == 0 ||
+ (vd.start_time_s == vd.end_time_s && vd.entries > 1) ||
+ (vd.update_every_s == 0 && vd.entries > 1)
+ )
+ vd.is_valid = false;
+
+ else {
+ if(unlikely(vd.entries != entries || vd.update_every_s != update_every_s))
+ updated = true;
+
+ if (likely(vd.update_every_s)) {
+ size_t entries_by_time = page_entries_by_time(vd.start_time_s, vd.end_time_s, vd.update_every_s);
+
+ if (vd.entries != entries_by_time) {
+ if (overwrite_zero_update_every_s < vd.update_every_s)
+ vd.update_every_s = overwrite_zero_update_every_s;
+
+ time_t new_end_time_s = (time_t)(vd.start_time_s + (vd.entries - 1) * vd.update_every_s);
+
+ if(new_end_time_s <= vd.end_time_s) {
+ // end time is wrong
+ vd.end_time_s = new_end_time_s;
+ }
+ else {
+ // update every is wrong
+ vd.update_every_s = overwrite_zero_update_every_s;
+ vd.end_time_s = (time_t)(vd.start_time_s + (vd.entries - 1) * vd.update_every_s);
+ }
+
+ updated = true;
+ }
+ }
+ else if(overwrite_zero_update_every_s) {
+ vd.update_every_s = overwrite_zero_update_every_s;
+ updated = true;
+ }
+ }
+
+ if(unlikely(!vd.is_valid || updated)) {
+#ifndef NETDATA_INTERNAL_CHECKS
+ error_limit_static_global_var(erl, 1, 0);
+#endif
+ char uuid_str[UUID_STR_LEN + 1];
+ uuid_unparse(*uuid, uuid_str);
+
+ BUFFER *wb = NULL;
+
+ if(flags) {
+ wb = buffer_create(0, NULL);
+ collect_page_flags_to_buffer(wb, flags);
+ }
+
+ if(!vd.is_valid) {
+#ifdef NETDATA_INTERNAL_CHECKS
+ internal_error(true,
+#else
+ error_limit(&erl,
+#endif
+ "DBENGINE: metric '%s' %s invalid page of type %u "
+ "from %ld to %ld (now %ld), update every %ld, page length %zu, entries %zu (flags: %s)",
+ uuid_str, msg, vd.type,
+ vd.start_time_s, vd.end_time_s, now_s, vd.update_every_s, vd.page_length, vd.entries, wb?buffer_tostring(wb):""
+ );
+ }
+ else {
+ const char *err_valid = (vd.is_valid) ? "" : "found invalid, ";
+ const char *err_start = (vd.start_time_s == start_time_s) ? "" : "start time updated, ";
+ const char *err_end = (vd.end_time_s == end_time_s) ? "" : "end time updated, ";
+ const char *err_update = (vd.update_every_s == update_every_s) ? "" : "update every updated, ";
+ const char *err_length = (vd.page_length == page_length) ? "" : "page length updated, ";
+ const char *err_entries = (vd.entries == entries) ? "" : "entries updated, ";
+ const char *err_future = (now_s && vd.end_time_s <= now_s) ? "" : "future end time, ";
+
+#ifdef NETDATA_INTERNAL_CHECKS
+ internal_error(true,
+#else
+ error_limit(&erl,
+#endif
+ "DBENGINE: metric '%s' %s page of type %u "
+ "from %ld to %ld (now %ld), update every %ld, page length %zu, entries %zu (flags: %s), "
+ "found inconsistent - the right is "
+ "from %ld to %ld, update every %ld, page length %zu, entries %zu: "
+ "%s%s%s%s%s%s%s",
+ uuid_str, msg, vd.type,
+ start_time_s, end_time_s, now_s, update_every_s, page_length, entries, wb?buffer_tostring(wb):"",
+ vd.start_time_s, vd.end_time_s, vd.update_every_s, vd.page_length, vd.entries,
+ err_valid, err_start, err_end, err_update, err_length, err_entries, err_future
+ );
+ }
+
+ buffer_free(wb);
+ }
+
+ return vd;
+}
+
+static inline struct page_details *epdl_get_pd_load_link_list_from_metric_start_time(EPDL *epdl, Word_t metric_id, time_t start_time_s) {
+
+ if(unlikely(epdl->head_to_datafile_extent_queries_pending_for_extent))
+ // stop appending more pages to this epdl
+ epdl_pending_del(epdl);
+
+ struct page_details *pd_list = NULL;
+
+ for(EPDL *ep = epdl; ep ;ep = ep->query.next) {
+ Pvoid_t *pd_by_start_time_s_judyL = PDCJudyLGet(ep->page_details_by_metric_id_JudyL, metric_id, PJE0);
+ internal_fatal(pd_by_start_time_s_judyL == PJERR, "DBENGINE: corrupted extent metrics JudyL");
+
+ if (unlikely(pd_by_start_time_s_judyL && *pd_by_start_time_s_judyL)) {
+ Pvoid_t *pd_pptr = PDCJudyLGet(*pd_by_start_time_s_judyL, start_time_s, PJE0);
+ internal_fatal(pd_pptr == PJERR, "DBENGINE: corrupted metric page details JudyHS");
+
+ if(likely(pd_pptr && *pd_pptr)) {
+ struct page_details *pd = *pd_pptr;
+ internal_fatal(metric_id != pd->metric_id, "DBENGINE: metric ids do not match");
+
+ if(likely(!pd->page)) {
+ if (unlikely(__atomic_load_n(&ep->pdc->workers_should_stop, __ATOMIC_RELAXED)))
+ pdc_page_status_set(pd, PDC_PAGE_FAILED | PDC_PAGE_CANCELLED);
+ else
+ DOUBLE_LINKED_LIST_APPEND_ITEM_UNSAFE(pd_list, pd, load.prev, load.next);
+ }
+ }
+ }
+ }
+
+ return pd_list;
+}
+
+static void epdl_extent_loading_error_log(struct rrdengine_instance *ctx, EPDL *epdl, struct rrdeng_extent_page_descr *descr, const char *msg) {
+ char uuid[UUID_STR_LEN] = "";
+ time_t start_time_s = 0;
+ time_t end_time_s = 0;
+ bool used_epdl = false;
+ bool used_descr = false;
+
+ if (descr) {
+ start_time_s = (time_t)(descr->start_time_ut / USEC_PER_SEC);
+ end_time_s = (time_t)(descr->end_time_ut / USEC_PER_SEC);
+ uuid_unparse_lower(descr->uuid, uuid);
+ used_descr = true;
+ }
+ else if (epdl) {
+ struct page_details *pd = NULL;
+
+ Word_t start = 0;
+ Pvoid_t *pd_by_start_time_s_judyL = PDCJudyLFirst(epdl->page_details_by_metric_id_JudyL, &start, PJE0);
+ if(pd_by_start_time_s_judyL) {
+ start = 0;
+ Pvoid_t *pd_pptr = PDCJudyLFirst(*pd_by_start_time_s_judyL, &start, PJE0);
+ if(pd_pptr) {
+ pd = *pd_pptr;
+ start_time_s = pd->first_time_s;
+ end_time_s = pd->last_time_s;
+ METRIC *metric = (METRIC *)pd->metric_id;
+ uuid_t *u = mrg_metric_uuid(main_mrg, metric);
+ uuid_unparse_lower(*u, uuid);
+ used_epdl = true;
+ }
+ }
+ }
+
+ if(!used_epdl && !used_descr && epdl && epdl->pdc) {
+ start_time_s = epdl->pdc->start_time_s;
+ end_time_s = epdl->pdc->end_time_s;
+ }
+
+ char start_time_str[LOG_DATE_LENGTH + 1] = "";
+ if(start_time_s)
+ log_date(start_time_str, LOG_DATE_LENGTH, start_time_s);
+
+ char end_time_str[LOG_DATE_LENGTH + 1] = "";
+ if(end_time_s)
+ log_date(end_time_str, LOG_DATE_LENGTH, end_time_s);
+
+ error_limit_static_global_var(erl, 1, 0);
+ error_limit(&erl,
+ "DBENGINE: error while reading extent from datafile %u of tier %d, at offset %" PRIu64 " (%u bytes) "
+ "%s from %ld (%s) to %ld (%s) %s%s: "
+ "%s",
+ epdl->datafile->fileno, ctx->config.tier,
+ epdl->extent_offset, epdl->extent_size,
+ used_epdl ? "to extract page (PD)" : used_descr ? "expected page (DESCR)" : "part of a query (PDC)",
+ start_time_s, start_time_str, end_time_s, end_time_str,
+ used_epdl || used_descr ? " of metric " : "",
+ used_epdl || used_descr ? uuid : "",
+ msg);
+}
+
+static bool epdl_populate_pages_from_extent_data(
+ struct rrdengine_instance *ctx,
+ void *data,
+ size_t data_length,
+ EPDL *epdl,
+ bool worker,
+ PDC_PAGE_STATUS tags,
+ bool cached_extent)
+{
+ int ret;
+ unsigned i, count;
+ void *uncompressed_buf = NULL;
+ uint32_t payload_length, payload_offset, trailer_offset, uncompressed_payload_length = 0;
+ bool have_read_error = false;
+ /* persistent structures */
+ struct rrdeng_df_extent_header *header;
+ struct rrdeng_df_extent_trailer *trailer;
+ struct extent_buffer *eb = NULL;
+ uLong crc;
+
+ bool can_use_data = true;
+ if(data_length < sizeof(*header) + sizeof(header->descr[0]) + sizeof(*trailer)) {
+ can_use_data = false;
+
+ // added to satisfy the requirements of older compilers (prevent warnings)
+ payload_length = 0;
+ payload_offset = 0;
+ trailer_offset = 0;
+ count = 0;
+ header = NULL;
+ trailer = NULL;
+ }
+ else {
+ header = data;
+ payload_length = header->payload_length;
+ count = header->number_of_pages;
+ payload_offset = sizeof(*header) + sizeof(header->descr[0]) * count;
+ trailer_offset = data_length - sizeof(*trailer);
+ trailer = data + trailer_offset;
+ }
+
+ if( !can_use_data ||
+ count < 1 ||
+ count > MAX_PAGES_PER_EXTENT ||
+ (header->compression_algorithm != RRD_NO_COMPRESSION && header->compression_algorithm != RRD_LZ4) ||
+ (payload_length != trailer_offset - payload_offset) ||
+ (data_length != payload_offset + payload_length + sizeof(*trailer))
+ ) {
+ epdl_extent_loading_error_log(ctx, epdl, NULL, "header is INVALID");
+ return false;
+ }
+
+ crc = crc32(0L, Z_NULL, 0);
+ crc = crc32(crc, data, epdl->extent_size - sizeof(*trailer));
+ ret = crc32cmp(trailer->checksum, crc);
+ if (unlikely(ret)) {
+ ctx_io_error(ctx);
+ have_read_error = true;
+ epdl_extent_loading_error_log(ctx, epdl, NULL, "CRC32 checksum FAILED");
+ }
+
+ if(worker)
+ worker_is_busy(UV_EVENT_DBENGINE_EXTENT_DECOMPRESSION);
+
+ if (likely(!have_read_error && RRD_NO_COMPRESSION != header->compression_algorithm)) {
+ // find the uncompressed extent size
+ uncompressed_payload_length = 0;
+ for (i = 0; i < count; ++i) {
+ size_t page_length = header->descr[i].page_length;
+ if(page_length > RRDENG_BLOCK_SIZE) {
+ have_read_error = true;
+ break;
+ }
+
+ uncompressed_payload_length += header->descr[i].page_length;
+ }
+
+ if(unlikely(uncompressed_payload_length > MAX_PAGES_PER_EXTENT * RRDENG_BLOCK_SIZE))
+ have_read_error = true;
+
+ if(likely(!have_read_error)) {
+ eb = extent_buffer_get(uncompressed_payload_length);
+ uncompressed_buf = eb->data;
+
+ ret = LZ4_decompress_safe(data + payload_offset, uncompressed_buf,
+ (int) payload_length, (int) uncompressed_payload_length);
+
+ __atomic_add_fetch(&ctx->stats.before_decompress_bytes, payload_length, __ATOMIC_RELAXED);
+ __atomic_add_fetch(&ctx->stats.after_decompress_bytes, ret, __ATOMIC_RELAXED);
+ }
+ }
+
+ if(worker)
+ worker_is_busy(UV_EVENT_DBENGINE_EXTENT_PAGE_LOOKUP);
+
+ size_t stats_data_from_main_cache = 0;
+ size_t stats_data_from_extent = 0;
+ size_t stats_load_compressed = 0;
+ size_t stats_load_uncompressed = 0;
+ size_t stats_load_invalid_page = 0;
+ size_t stats_cache_hit_while_inserting = 0;
+
+ uint32_t page_offset = 0, page_length;
+ time_t now_s = max_acceptable_collected_time();
+ for (i = 0; i < count; i++, page_offset += page_length) {
+ page_length = header->descr[i].page_length;
+ time_t start_time_s = (time_t) (header->descr[i].start_time_ut / USEC_PER_SEC);
+
+ if(!page_length || !start_time_s) {
+ char log[200 + 1];
+ snprintfz(log, 200, "page %u (out of %u) is EMPTY", i, count);
+ epdl_extent_loading_error_log(ctx, epdl, &header->descr[i], log);
+ continue;
+ }
+
+ METRIC *metric = mrg_metric_get_and_acquire(main_mrg, &header->descr[i].uuid, (Word_t)ctx);
+ Word_t metric_id = (Word_t)metric;
+ if(!metric) {
+ char log[200 + 1];
+ snprintfz(log, 200, "page %u (out of %u) has unknown UUID", i, count);
+ epdl_extent_loading_error_log(ctx, epdl, &header->descr[i], log);
+ continue;
+ }
+ mrg_metric_release(main_mrg, metric);
+
+ struct page_details *pd_list = epdl_get_pd_load_link_list_from_metric_start_time(epdl, metric_id, start_time_s);
+ if(likely(!pd_list))
+ continue;
+
+ VALIDATED_PAGE_DESCRIPTOR vd = validate_extent_page_descr(
+ &header->descr[i], now_s,
+ (pd_list) ? pd_list->update_every_s : 0,
+ have_read_error);
+
+ if(worker)
+ worker_is_busy(UV_EVENT_DBENGINE_EXTENT_PAGE_ALLOCATION);
+
+ void *page_data;
+
+ if (unlikely(!vd.is_valid)) {
+ page_data = DBENGINE_EMPTY_PAGE;
+ stats_load_invalid_page++;
+ }
+ else {
+ if (RRD_NO_COMPRESSION == header->compression_algorithm) {
+ page_data = dbengine_page_alloc(vd.page_length);
+ memcpy(page_data, data + payload_offset + page_offset, (size_t) vd.page_length);
+ stats_load_uncompressed++;
+ }
+ else {
+ if (unlikely(page_offset + vd.page_length > uncompressed_payload_length)) {
+ char log[200 + 1];
+ snprintfz(log, 200, "page %u (out of %u) offset %u + page length %zu, "
+ "exceeds the uncompressed buffer size %u",
+ i, count, page_offset, vd.page_length, uncompressed_payload_length);
+ epdl_extent_loading_error_log(ctx, epdl, &header->descr[i], log);
+
+ page_data = DBENGINE_EMPTY_PAGE;
+ stats_load_invalid_page++;
+ }
+ else {
+ page_data = dbengine_page_alloc(vd.page_length);
+ memcpy(page_data, uncompressed_buf + page_offset, vd.page_length);
+ stats_load_compressed++;
+ }
+ }
+ }
+
+ if(worker)
+ worker_is_busy(UV_EVENT_DBENGINE_EXTENT_PAGE_POPULATION);
+
+ PGC_ENTRY page_entry = {
+ .hot = false,
+ .section = (Word_t)ctx,
+ .metric_id = metric_id,
+ .start_time_s = vd.start_time_s,
+ .end_time_s = vd.end_time_s,
+ .update_every_s = vd.update_every_s,
+ .size = (size_t) ((page_data == DBENGINE_EMPTY_PAGE) ? 0 : vd.page_length),
+ .data = page_data
+ };
+
+ bool added = true;
+ PGC_PAGE *page = pgc_page_add_and_acquire(main_cache, page_entry, &added);
+ if (false == added) {
+ dbengine_page_free(page_data, vd.page_length);
+ stats_cache_hit_while_inserting++;
+ stats_data_from_main_cache++;
+ }
+ else
+ stats_data_from_extent++;
+
+ struct page_details *pd = pd_list;
+ do {
+ if(pd != pd_list)
+ pgc_page_dup(main_cache, page);
+
+ pd->page = page;
+ pd->page_length = pgc_page_data_size(main_cache, page);
+ pdc_page_status_set(pd, PDC_PAGE_READY | tags | ((page_data == DBENGINE_EMPTY_PAGE) ? PDC_PAGE_EMPTY : 0));
+
+ pd = pd->load.next;
+ } while(pd);
+
+ if(worker)
+ worker_is_busy(UV_EVENT_DBENGINE_EXTENT_PAGE_LOOKUP);
+ }
+
+ if(stats_data_from_main_cache)
+ __atomic_add_fetch(&rrdeng_cache_efficiency_stats.pages_data_source_main_cache, stats_data_from_main_cache, __ATOMIC_RELAXED);
+
+ if(cached_extent)
+ __atomic_add_fetch(&rrdeng_cache_efficiency_stats.pages_data_source_extent_cache, stats_data_from_extent, __ATOMIC_RELAXED);
+ else {
+ __atomic_add_fetch(&rrdeng_cache_efficiency_stats.pages_data_source_disk, stats_data_from_extent, __ATOMIC_RELAXED);
+ __atomic_add_fetch(&rrdeng_cache_efficiency_stats.extents_loaded_from_disk, 1, __ATOMIC_RELAXED);
+ }
+
+ if(stats_cache_hit_while_inserting)
+ __atomic_add_fetch(&rrdeng_cache_efficiency_stats.pages_load_ok_loaded_but_cache_hit_while_inserting, stats_cache_hit_while_inserting, __ATOMIC_RELAXED);
+
+ if(stats_load_compressed)
+ __atomic_add_fetch(&rrdeng_cache_efficiency_stats.pages_load_ok_compressed, stats_load_compressed, __ATOMIC_RELAXED);
+
+ if(stats_load_uncompressed)
+ __atomic_add_fetch(&rrdeng_cache_efficiency_stats.pages_load_ok_uncompressed, stats_load_uncompressed, __ATOMIC_RELAXED);
+
+ if(stats_load_invalid_page)
+ __atomic_add_fetch(&rrdeng_cache_efficiency_stats.pages_load_fail_invalid_page_in_extent, stats_load_invalid_page, __ATOMIC_RELAXED);
+
+ if(worker)
+ worker_is_idle();
+
+ extent_buffer_release(eb);
+
+ return true;
+}
+
+static inline void *datafile_extent_read(struct rrdengine_instance *ctx, uv_file file, unsigned pos, unsigned size_bytes)
+{
+ void *buffer;
+ uv_fs_t request;
+
+ unsigned real_io_size = ALIGN_BYTES_CEILING(size_bytes);
+ int ret = posix_memalign(&buffer, RRDFILE_ALIGNMENT, real_io_size);
+ if (unlikely(ret))
+ fatal("DBENGINE: posix_memalign(): %s", strerror(ret));
+
+ uv_buf_t iov = uv_buf_init(buffer, real_io_size);
+ ret = uv_fs_read(NULL, &request, file, &iov, 1, pos, NULL);
+ if (unlikely(-1 == ret)) {
+ ctx_io_error(ctx);
+ posix_memfree(buffer);
+ buffer = NULL;
+ }
+ else
+ ctx_io_read_op_bytes(ctx, real_io_size);
+
+ uv_fs_req_cleanup(&request);
+
+ return buffer;
+}
+
+static inline void datafile_extent_read_free(void *buffer) {
+ posix_memfree(buffer);
+}
+
+void epdl_find_extent_and_populate_pages(struct rrdengine_instance *ctx, EPDL *epdl, bool worker) {
+ size_t *statistics_counter = NULL;
+ PDC_PAGE_STATUS not_loaded_pages_tag = 0, loaded_pages_tag = 0;
+
+ bool should_stop = __atomic_load_n(&epdl->pdc->workers_should_stop, __ATOMIC_RELAXED);
+ for(EPDL *ep = epdl->query.next; ep ;ep = ep->query.next) {
+ internal_fatal(ep->datafile != epdl->datafile, "DBENGINE: datafiles do not match");
+ internal_fatal(ep->extent_offset != epdl->extent_offset, "DBENGINE: extent offsets do not match");
+ internal_fatal(ep->extent_size != epdl->extent_size, "DBENGINE: extent sizes do not match");
+ internal_fatal(ep->file != epdl->file, "DBENGINE: files do not match");
+
+ if(!__atomic_load_n(&ep->pdc->workers_should_stop, __ATOMIC_RELAXED)) {
+ should_stop = false;
+ break;
+ }
+ }
+
+ if(unlikely(should_stop)) {
+ statistics_counter = &rrdeng_cache_efficiency_stats.pages_load_fail_cancelled;
+ not_loaded_pages_tag = PDC_PAGE_CANCELLED;
+ goto cleanup;
+ }
+
+ if(worker)
+ worker_is_busy(UV_EVENT_DBENGINE_EXTENT_CACHE_LOOKUP);
+
+ bool extent_found_in_cache = false;
+
+ void *extent_compressed_data = NULL;
+ PGC_PAGE *extent_cache_page = pgc_page_get_and_acquire(
+ extent_cache, (Word_t)ctx,
+ (Word_t)epdl->datafile->fileno, (time_t)epdl->extent_offset,
+ PGC_SEARCH_EXACT);
+
+ if(extent_cache_page) {
+ extent_compressed_data = pgc_page_data(extent_cache_page);
+ internal_fatal(epdl->extent_size != pgc_page_data_size(extent_cache, extent_cache_page),
+ "DBENGINE: cache size does not match the expected size");
+
+ loaded_pages_tag |= PDC_PAGE_EXTENT_FROM_CACHE;
+ not_loaded_pages_tag |= PDC_PAGE_EXTENT_FROM_CACHE;
+ extent_found_in_cache = true;
+ }
+ else {
+ if(worker)
+ worker_is_busy(UV_EVENT_DBENGINE_EXTENT_MMAP);
+
+ void *extent_data = datafile_extent_read(ctx, epdl->file, epdl->extent_offset, epdl->extent_size);
+ if(extent_data != NULL) {
+
+ void *copied_extent_compressed_data = dbengine_extent_alloc(epdl->extent_size);
+ memcpy(copied_extent_compressed_data, extent_data, epdl->extent_size);
+ datafile_extent_read_free(extent_data);
+
+ if(worker)
+ worker_is_busy(UV_EVENT_DBENGINE_EXTENT_CACHE_LOOKUP);
+
+ bool added = false;
+ extent_cache_page = pgc_page_add_and_acquire(extent_cache, (PGC_ENTRY) {
+ .hot = false,
+ .section = (Word_t) ctx,
+ .metric_id = (Word_t) epdl->datafile->fileno,
+ .start_time_s = (time_t) epdl->extent_offset,
+ .size = epdl->extent_size,
+ .end_time_s = 0,
+ .update_every_s = 0,
+ .data = copied_extent_compressed_data,
+ }, &added);
+
+ if (!added) {
+ dbengine_extent_free(copied_extent_compressed_data, epdl->extent_size);
+ internal_fatal(epdl->extent_size != pgc_page_data_size(extent_cache, extent_cache_page),
+ "DBENGINE: cache size does not match the expected size");
+ }
+
+ extent_compressed_data = pgc_page_data(extent_cache_page);
+
+ loaded_pages_tag |= PDC_PAGE_EXTENT_FROM_DISK;
+ not_loaded_pages_tag |= PDC_PAGE_EXTENT_FROM_DISK;
+ }
+ }
+
+ if(extent_compressed_data) {
+ // Need to decompress and then process the pagelist
+ bool extent_used = epdl_populate_pages_from_extent_data(
+ ctx, extent_compressed_data, epdl->extent_size,
+ epdl, worker, loaded_pages_tag, extent_found_in_cache);
+
+ if(extent_used) {
+ // since the extent was used, all the pages that are not
+ // loaded from this extent, were not found in the extent
+ not_loaded_pages_tag |= PDC_PAGE_FAILED_NOT_IN_EXTENT;
+ statistics_counter = &rrdeng_cache_efficiency_stats.pages_load_fail_not_found;
+ }
+ else {
+ not_loaded_pages_tag |= PDC_PAGE_FAILED_INVALID_EXTENT;
+ statistics_counter = &rrdeng_cache_efficiency_stats.pages_load_fail_invalid_extent;
+ }
+ }
+ else {
+ not_loaded_pages_tag |= PDC_PAGE_FAILED_TO_MAP_EXTENT;
+ statistics_counter = &rrdeng_cache_efficiency_stats.pages_load_fail_cant_mmap_extent;
+ }
+
+ if(extent_cache_page)
+ pgc_page_release(extent_cache, extent_cache_page);
+
+cleanup:
+ // remove it from the datafile extent_queries
+ // this can be called multiple times safely
+ epdl_pending_del(epdl);
+
+ // mark all pending pages as failed
+ for(EPDL *ep = epdl; ep ;ep = ep->query.next) {
+ epdl_mark_all_not_loaded_pages_as_failed(
+ ep, not_loaded_pages_tag, statistics_counter);
+ }
+
+ for(EPDL *ep = epdl, *next = NULL; ep ; ep = next) {
+ next = ep->query.next;
+
+ completion_mark_complete_a_job(&ep->pdc->page_completion);
+ pdc_release_and_destroy_if_unreferenced(ep->pdc, true, false);
+
+ // Free the Judy that holds the requested pagelist and the extents
+ epdl_destroy(ep);
+ }
+
+ if(worker)
+ worker_is_idle();
+}
diff --git a/database/engine/pdc.h b/database/engine/pdc.h
new file mode 100644
index 00000000..9bae39ad
--- /dev/null
+++ b/database/engine/pdc.h
@@ -0,0 +1,67 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#ifndef DBENGINE_PDC_H
+#define DBENGINE_PDC_H
+
+#include "../engine/rrdengine.h"
+
+struct rrdeng_cmd;
+
+#ifdef PDC_USE_JULYL
+#define PDCJudyLIns JulyLIns
+#define PDCJudyLGet JulyLGet
+#define PDCJudyLFirst JulyLFirst
+#define PDCJudyLNext JulyLNext
+#define PDCJudyLLast JulyLLast
+#define PDCJudyLPrev JulyLPrev
+#define PDCJudyLFirstThenNext JulyLFirstThenNext
+#define PDCJudyLLastThenPrev JulyLLastThenPrev
+#define PDCJudyLFreeArray JulyLFreeArray
+#else
+#define PDCJudyLIns JudyLIns
+#define PDCJudyLGet JudyLGet
+#define PDCJudyLFirst JudyLFirst
+#define PDCJudyLNext JudyLNext
+#define PDCJudyLLast JudyLLast
+#define PDCJudyLPrev JudyLPrev
+#define PDCJudyLFirstThenNext JudyLFirstThenNext
+#define PDCJudyLLastThenPrev JudyLLastThenPrev
+#define PDCJudyLFreeArray JudyLFreeArray
+#endif
+
+typedef struct extent_page_details_list EPDL;
+typedef void (*execute_extent_page_details_list_t)(struct rrdengine_instance *ctx, EPDL *epdl, enum storage_priority priority);
+void pdc_to_epdl_router(struct rrdengine_instance *ctx, struct page_details_control *pdc, execute_extent_page_details_list_t exec_first_extent_list, execute_extent_page_details_list_t exec_rest_extent_list);
+void epdl_find_extent_and_populate_pages(struct rrdengine_instance *ctx, EPDL *epdl, bool worker);
+
+size_t pdc_cache_size(void);
+size_t pd_cache_size(void);
+size_t epdl_cache_size(void);
+size_t deol_cache_size(void);
+size_t extent_buffer_cache_size(void);
+
+void pdc_init(void);
+void page_details_init(void);
+void epdl_init(void);
+void deol_init(void);
+void extent_buffer_cleanup1(void);
+
+void epdl_cmd_dequeued(void *epdl_ptr);
+void epdl_cmd_queued(void *epdl_ptr, struct rrdeng_cmd *cmd);
+
+struct extent_buffer {
+ size_t bytes;
+
+ struct {
+ struct extent_buffer *prev;
+ struct extent_buffer *next;
+ } cache;
+
+ uint8_t data[];
+};
+
+void extent_buffer_init(void);
+struct extent_buffer *extent_buffer_get(size_t size);
+void extent_buffer_release(struct extent_buffer *eb);
+
+#endif // DBENGINE_PDC_H
diff --git a/database/engine/rrdengine.c b/database/engine/rrdengine.c
index a6840f38..d64868f0 100644
--- a/database/engine/rrdengine.c
+++ b/database/engine/rrdengine.c
@@ -2,6 +2,7 @@
#define NETDATA_RRD_INTERNALS
#include "rrdengine.h"
+#include "pdc.h"
rrdeng_stats_t global_io_errors = 0;
rrdeng_stats_t global_fs_errors = 0;
@@ -11,31 +12,74 @@ rrdeng_stats_t global_flushing_pressure_page_deletions = 0;
unsigned rrdeng_pages_per_extent = MAX_PAGES_PER_EXTENT;
-#if WORKER_UTILIZATION_MAX_JOB_TYPES < (RRDENG_MAX_OPCODE + 2)
+#if WORKER_UTILIZATION_MAX_JOB_TYPES < (RRDENG_OPCODE_MAX + 2)
#error Please increase WORKER_UTILIZATION_MAX_JOB_TYPES to at least (RRDENG_MAX_OPCODE + 2)
#endif
-void *dbengine_page_alloc() {
- void *page = NULL;
- if (unlikely(db_engine_use_malloc))
- page = mallocz(RRDENG_BLOCK_SIZE);
- else {
- page = netdata_mmap(NULL, RRDENG_BLOCK_SIZE, MAP_PRIVATE, enable_ksm);
- if(!page) fatal("Cannot allocate dbengine page cache page, with mmap()");
- }
- return page;
-}
-
-void dbengine_page_free(void *page) {
- if (unlikely(db_engine_use_malloc))
- freez(page);
- else
- netdata_munmap(page, RRDENG_BLOCK_SIZE);
-}
+struct rrdeng_main {
+ uv_thread_t thread;
+ uv_loop_t loop;
+ uv_async_t async;
+ uv_timer_t timer;
+ pid_t tid;
+
+ size_t flushes_running;
+ size_t evictions_running;
+ size_t cleanup_running;
+
+ struct {
+ ARAL *ar;
+
+ struct {
+ SPINLOCK spinlock;
+
+ size_t waiting;
+ struct rrdeng_cmd *waiting_items_by_priority[STORAGE_PRIORITY_INTERNAL_MAX_DONT_USE];
+ size_t executed_by_priority[STORAGE_PRIORITY_INTERNAL_MAX_DONT_USE];
+ } unsafe;
+ } cmd_queue;
+
+ struct {
+ ARAL *ar;
+
+ struct {
+ size_t dispatched;
+ size_t executing;
+ size_t pending_cb;
+ } atomics;
+ } work_cmd;
+
+ struct {
+ ARAL *ar;
+ } handles;
+
+ struct {
+ ARAL *ar;
+ } descriptors;
+
+ struct {
+ ARAL *ar;
+ } xt_io_descr;
+
+} rrdeng_main = {
+ .thread = 0,
+ .loop = {},
+ .async = {},
+ .timer = {},
+ .flushes_running = 0,
+ .evictions_running = 0,
+ .cleanup_running = 0,
+
+ .cmd_queue = {
+ .unsafe = {
+ .spinlock = NETDATA_SPINLOCK_INITIALIZER,
+ },
+ }
+};
static void sanity_check(void)
{
- BUILD_BUG_ON(WORKER_UTILIZATION_MAX_JOB_TYPES < (RRDENG_MAX_OPCODE + 2));
+ BUILD_BUG_ON(WORKER_UTILIZATION_MAX_JOB_TYPES < (RRDENG_OPCODE_MAX + 2));
/* Magic numbers must fit in the super-blocks */
BUILD_BUG_ON(strlen(RRDENG_DF_MAGIC) > RRDENG_MAGIC_SZ);
@@ -54,519 +98,489 @@ static void sanity_check(void)
BUILD_BUG_ON(MAX_PAGES_PER_EXTENT > 255);
/* extent cache count must fit in 32 bits */
- BUILD_BUG_ON(MAX_CACHED_EXTENTS > 32);
+// BUILD_BUG_ON(MAX_CACHED_EXTENTS > 32);
/* page info scratch space must be able to hold 2 32-bit integers */
BUILD_BUG_ON(sizeof(((struct rrdeng_page_info *)0)->scratch) < 2 * sizeof(uint32_t));
}
-/* always inserts into tail */
-static inline void xt_cache_replaceQ_insert(struct rrdengine_worker_config* wc,
- struct extent_cache_element *xt_cache_elem)
-{
- struct extent_cache *xt_cache = &wc->xt_cache;
+// ----------------------------------------------------------------------------
+// work request cache
- xt_cache_elem->prev = NULL;
- xt_cache_elem->next = NULL;
+typedef void *(*work_cb)(struct rrdengine_instance *ctx, void *data, struct completion *completion, uv_work_t* req);
+typedef void (*after_work_cb)(struct rrdengine_instance *ctx, void *data, struct completion *completion, uv_work_t* req, int status);
- if (likely(NULL != xt_cache->replaceQ_tail)) {
- xt_cache_elem->prev = xt_cache->replaceQ_tail;
- xt_cache->replaceQ_tail->next = xt_cache_elem;
- }
- if (unlikely(NULL == xt_cache->replaceQ_head)) {
- xt_cache->replaceQ_head = xt_cache_elem;
- }
- xt_cache->replaceQ_tail = xt_cache_elem;
+struct rrdeng_work {
+ uv_work_t req;
+
+ struct rrdengine_instance *ctx;
+ void *data;
+ struct completion *completion;
+
+ work_cb work_cb;
+ after_work_cb after_work_cb;
+ enum rrdeng_opcode opcode;
+};
+
+static void work_request_init(void) {
+ rrdeng_main.work_cmd.ar = aral_create(
+ "dbengine-work-cmd",
+ sizeof(struct rrdeng_work),
+ 0,
+ 65536, NULL,
+ NULL, NULL, false, false
+ );
}
-static inline void xt_cache_replaceQ_delete(struct rrdengine_worker_config* wc,
- struct extent_cache_element *xt_cache_elem)
-{
- struct extent_cache *xt_cache = &wc->xt_cache;
- struct extent_cache_element *prev, *next;
+static inline bool work_request_full(void) {
+ return __atomic_load_n(&rrdeng_main.work_cmd.atomics.dispatched, __ATOMIC_RELAXED) >= (size_t)(libuv_worker_threads - RESERVED_LIBUV_WORKER_THREADS);
+}
- prev = xt_cache_elem->prev;
- next = xt_cache_elem->next;
+static inline void work_done(struct rrdeng_work *work_request) {
+ aral_freez(rrdeng_main.work_cmd.ar, work_request);
+}
- if (likely(NULL != prev)) {
- prev->next = next;
- }
- if (likely(NULL != next)) {
- next->prev = prev;
- }
- if (unlikely(xt_cache_elem == xt_cache->replaceQ_head)) {
- xt_cache->replaceQ_head = next;
- }
- if (unlikely(xt_cache_elem == xt_cache->replaceQ_tail)) {
- xt_cache->replaceQ_tail = prev;
- }
- xt_cache_elem->prev = xt_cache_elem->next = NULL;
+static void work_standard_worker(uv_work_t *req) {
+ __atomic_add_fetch(&rrdeng_main.work_cmd.atomics.executing, 1, __ATOMIC_RELAXED);
+
+ register_libuv_worker_jobs();
+ worker_is_busy(UV_EVENT_WORKER_INIT);
+
+ struct rrdeng_work *work_request = req->data;
+ work_request->data = work_request->work_cb(work_request->ctx, work_request->data, work_request->completion, req);
+ worker_is_idle();
+
+ __atomic_sub_fetch(&rrdeng_main.work_cmd.atomics.dispatched, 1, __ATOMIC_RELAXED);
+ __atomic_sub_fetch(&rrdeng_main.work_cmd.atomics.executing, 1, __ATOMIC_RELAXED);
+ __atomic_add_fetch(&rrdeng_main.work_cmd.atomics.pending_cb, 1, __ATOMIC_RELAXED);
+
+ // signal the event loop a worker is available
+ fatal_assert(0 == uv_async_send(&rrdeng_main.async));
}
-static inline void xt_cache_replaceQ_set_hot(struct rrdengine_worker_config* wc,
- struct extent_cache_element *xt_cache_elem)
-{
- xt_cache_replaceQ_delete(wc, xt_cache_elem);
- xt_cache_replaceQ_insert(wc, xt_cache_elem);
+static void after_work_standard_callback(uv_work_t* req, int status) {
+ struct rrdeng_work *work_request = req->data;
+
+ worker_is_busy(RRDENG_OPCODE_MAX + work_request->opcode);
+
+ if(work_request->after_work_cb)
+ work_request->after_work_cb(work_request->ctx, work_request->data, work_request->completion, req, status);
+
+ work_done(work_request);
+ __atomic_sub_fetch(&rrdeng_main.work_cmd.atomics.pending_cb, 1, __ATOMIC_RELAXED);
+
+ worker_is_idle();
}
-/* Returns the index of the cached extent if it was successfully inserted in the extent cache, otherwise -1 */
-static int try_insert_into_xt_cache(struct rrdengine_worker_config* wc, struct extent_info *extent)
-{
- struct extent_cache *xt_cache = &wc->xt_cache;
- struct extent_cache_element *xt_cache_elem;
- unsigned idx;
- int ret;
+static bool work_dispatch(struct rrdengine_instance *ctx, void *data, struct completion *completion, enum rrdeng_opcode opcode, work_cb work_cb, after_work_cb after_work_cb) {
+ struct rrdeng_work *work_request = NULL;
- ret = find_first_zero(xt_cache->allocation_bitmap);
- if (-1 == ret || ret >= MAX_CACHED_EXTENTS) {
- for (xt_cache_elem = xt_cache->replaceQ_head ; NULL != xt_cache_elem ; xt_cache_elem = xt_cache_elem->next) {
- idx = xt_cache_elem - xt_cache->extent_array;
- if (!check_bit(xt_cache->inflight_bitmap, idx)) {
- xt_cache_replaceQ_delete(wc, xt_cache_elem);
- break;
- }
- }
- if (NULL == xt_cache_elem)
- return -1;
- } else {
- idx = (unsigned)ret;
- xt_cache_elem = &xt_cache->extent_array[idx];
+ internal_fatal(rrdeng_main.tid != gettid(), "work_dispatch() can only be run from the event loop thread");
+
+ work_request = aral_mallocz(rrdeng_main.work_cmd.ar);
+ memset(work_request, 0, sizeof(struct rrdeng_work));
+ work_request->req.data = work_request;
+ work_request->ctx = ctx;
+ work_request->data = data;
+ work_request->completion = completion;
+ work_request->work_cb = work_cb;
+ work_request->after_work_cb = after_work_cb;
+ work_request->opcode = opcode;
+
+ if(uv_queue_work(&rrdeng_main.loop, &work_request->req, work_standard_worker, after_work_standard_callback)) {
+ internal_fatal(true, "DBENGINE: cannot queue work");
+ work_done(work_request);
+ return false;
}
- xt_cache_elem->extent = extent;
- xt_cache_elem->fileno = extent->datafile->fileno;
- xt_cache_elem->inflight_io_descr = NULL;
- xt_cache_replaceQ_insert(wc, xt_cache_elem);
- modify_bit(&xt_cache->allocation_bitmap, idx, 1);
- return (int)idx;
+ __atomic_add_fetch(&rrdeng_main.work_cmd.atomics.dispatched, 1, __ATOMIC_RELAXED);
+
+ return true;
}
-/**
- * Returns 0 if the cached extent was found in the extent cache, 1 otherwise.
- * Sets *idx to point to the position of the extent inside the cache.
- **/
-static uint8_t lookup_in_xt_cache(struct rrdengine_worker_config* wc, struct extent_info *extent, unsigned *idx)
-{
- struct extent_cache *xt_cache = &wc->xt_cache;
- struct extent_cache_element *xt_cache_elem;
- unsigned i;
+// ----------------------------------------------------------------------------
+// page descriptor cache
+
+void page_descriptors_init(void) {
+ rrdeng_main.descriptors.ar = aral_create(
+ "dbengine-descriptors",
+ sizeof(struct page_descr_with_data),
+ 0,
+ 65536 * 4,
+ NULL,
+ NULL, NULL, false, false);
+}
- for (i = 0 ; i < MAX_CACHED_EXTENTS ; ++i) {
- xt_cache_elem = &xt_cache->extent_array[i];
- if (check_bit(xt_cache->allocation_bitmap, i) && xt_cache_elem->extent == extent &&
- xt_cache_elem->fileno == extent->datafile->fileno) {
- *idx = i;
- return 0;
- }
- }
- return 1;
+struct page_descr_with_data *page_descriptor_get(void) {
+ struct page_descr_with_data *descr = aral_mallocz(rrdeng_main.descriptors.ar);
+ memset(descr, 0, sizeof(struct page_descr_with_data));
+ return descr;
}
-#if 0 /* disabled code */
-static void delete_from_xt_cache(struct rrdengine_worker_config* wc, unsigned idx)
-{
- struct extent_cache *xt_cache = &wc->xt_cache;
- struct extent_cache_element *xt_cache_elem;
+static inline void page_descriptor_release(struct page_descr_with_data *descr) {
+ aral_freez(rrdeng_main.descriptors.ar, descr);
+}
- xt_cache_elem = &xt_cache->extent_array[idx];
- xt_cache_replaceQ_delete(wc, xt_cache_elem);
- xt_cache_elem->extent = NULL;
- modify_bit(&wc->xt_cache.allocation_bitmap, idx, 0); /* invalidate it */
- modify_bit(&wc->xt_cache.inflight_bitmap, idx, 0); /* not in-flight anymore */
+// ----------------------------------------------------------------------------
+// extent io descriptor cache
+
+static void extent_io_descriptor_init(void) {
+ rrdeng_main.xt_io_descr.ar = aral_create(
+ "dbengine-extent-io",
+ sizeof(struct extent_io_descriptor),
+ 0,
+ 65536,
+ NULL,
+ NULL, NULL, false, false
+ );
}
-#endif
-void enqueue_inflight_read_to_xt_cache(struct rrdengine_worker_config* wc, unsigned idx,
- struct extent_io_descriptor *xt_io_descr)
-{
- struct extent_cache *xt_cache = &wc->xt_cache;
- struct extent_cache_element *xt_cache_elem;
- struct extent_io_descriptor *old_next;
+static struct extent_io_descriptor *extent_io_descriptor_get(void) {
+ struct extent_io_descriptor *xt_io_descr = aral_mallocz(rrdeng_main.xt_io_descr.ar);
+ memset(xt_io_descr, 0, sizeof(struct extent_io_descriptor));
+ return xt_io_descr;
+}
- xt_cache_elem = &xt_cache->extent_array[idx];
- old_next = xt_cache_elem->inflight_io_descr->next;
- xt_cache_elem->inflight_io_descr->next = xt_io_descr;
- xt_io_descr->next = old_next;
+static inline void extent_io_descriptor_release(struct extent_io_descriptor *xt_io_descr) {
+ aral_freez(rrdeng_main.xt_io_descr.ar, xt_io_descr);
}
-void read_cached_extent_cb(struct rrdengine_worker_config* wc, unsigned idx, struct extent_io_descriptor *xt_io_descr)
-{
- unsigned i, j, page_offset;
- struct rrdengine_instance *ctx = wc->ctx;
- struct rrdeng_page_descr *descr;
- struct page_cache_descr *pg_cache_descr;
- void *page;
- struct extent_info *extent = xt_io_descr->descr_array[0]->extent;
-
- for (i = 0 ; i < xt_io_descr->descr_count; ++i) {
- page = dbengine_page_alloc();
- descr = xt_io_descr->descr_array[i];
- for (j = 0, page_offset = 0 ; j < extent->number_of_pages ; ++j) {
- /* care, we don't hold the descriptor mutex */
- if (!uuid_compare(*extent->pages[j]->id, *descr->id) &&
- extent->pages[j]->page_length == descr->page_length &&
- extent->pages[j]->start_time_ut == descr->start_time_ut &&
- extent->pages[j]->end_time_ut == descr->end_time_ut) {
- break;
- }
- page_offset += extent->pages[j]->page_length;
+// ----------------------------------------------------------------------------
+// query handle cache
+
+void rrdeng_query_handle_init(void) {
+ rrdeng_main.handles.ar = aral_create(
+ "dbengine-query-handles",
+ sizeof(struct rrdeng_query_handle),
+ 0,
+ 65536,
+ NULL,
+ NULL, NULL, false, false);
+}
- }
- /* care, we don't hold the descriptor mutex */
- (void) memcpy(page, wc->xt_cache.extent_array[idx].pages + page_offset, descr->page_length);
-
- rrdeng_page_descr_mutex_lock(ctx, descr);
- pg_cache_descr = descr->pg_cache_descr;
- pg_cache_descr->page = page;
- pg_cache_descr->flags |= RRD_PAGE_POPULATED;
- pg_cache_descr->flags &= ~RRD_PAGE_READ_PENDING;
- rrdeng_page_descr_mutex_unlock(ctx, descr);
- pg_cache_replaceQ_insert(ctx, descr);
- if (xt_io_descr->release_descr) {
- pg_cache_put(ctx, descr);
- } else {
- debug(D_RRDENGINE, "%s: Waking up waiters.", __func__);
- pg_cache_wake_up_waiters(ctx, descr);
- }
+struct rrdeng_query_handle *rrdeng_query_handle_get(void) {
+ struct rrdeng_query_handle *handle = aral_mallocz(rrdeng_main.handles.ar);
+ memset(handle, 0, sizeof(struct rrdeng_query_handle));
+ return handle;
+}
+
+void rrdeng_query_handle_release(struct rrdeng_query_handle *handle) {
+ aral_freez(rrdeng_main.handles.ar, handle);
+}
+
+// ----------------------------------------------------------------------------
+// WAL cache
+
+static struct {
+ struct {
+ SPINLOCK spinlock;
+ WAL *available_items;
+ size_t available;
+ } protected;
+
+ struct {
+ size_t allocated;
+ } atomics;
+} wal_globals = {
+ .protected = {
+ .spinlock = NETDATA_SPINLOCK_INITIALIZER,
+ .available_items = NULL,
+ .available = 0,
+ },
+ .atomics = {
+ .allocated = 0,
+ },
+};
+
+static void wal_cleanup1(void) {
+ WAL *wal = NULL;
+
+ if(!netdata_spinlock_trylock(&wal_globals.protected.spinlock))
+ return;
+
+ if(wal_globals.protected.available_items && wal_globals.protected.available > storage_tiers) {
+ wal = wal_globals.protected.available_items;
+ DOUBLE_LINKED_LIST_REMOVE_ITEM_UNSAFE(wal_globals.protected.available_items, wal, cache.prev, cache.next);
+ wal_globals.protected.available--;
}
- if (xt_io_descr->completion)
- completion_mark_complete(xt_io_descr->completion);
- freez(xt_io_descr);
-}
-
-static void fill_page_with_nulls(void *page, uint32_t page_length, uint8_t type) {
- switch(type) {
- case PAGE_METRICS: {
- storage_number n = pack_storage_number(NAN, SN_FLAG_NONE);
- storage_number *array = (storage_number *)page;
- size_t slots = page_length / sizeof(n);
- for(size_t i = 0; i < slots ; i++)
- array[i] = n;
- }
- break;
-
- case PAGE_TIER: {
- storage_number_tier1_t n = {
- .min_value = NAN,
- .max_value = NAN,
- .sum_value = NAN,
- .count = 1,
- .anomaly_count = 0,
- };
- storage_number_tier1_t *array = (storage_number_tier1_t *)page;
- size_t slots = page_length / sizeof(n);
- for(size_t i = 0; i < slots ; i++)
- array[i] = n;
- }
- break;
- default: {
- static bool logged = false;
- if(!logged) {
- error("DBENGINE: cannot fill page with nulls on unknown page type id %d", type);
- logged = true;
- }
- memset(page, 0, page_length);
- }
+ netdata_spinlock_unlock(&wal_globals.protected.spinlock);
+
+ if(wal) {
+ posix_memfree(wal->buf);
+ freez(wal);
+ __atomic_sub_fetch(&wal_globals.atomics.allocated, 1, __ATOMIC_RELAXED);
}
}
-struct rrdeng_page_descr *get_descriptor(struct pg_cache_page_index *page_index, time_t start_time_s)
-{
- uv_rwlock_rdlock(&page_index->lock);
- Pvoid_t *PValue = JudyLGet(page_index->JudyL_array, start_time_s, PJE0);
- struct rrdeng_page_descr *descr = unlikely(NULL == PValue) ? NULL : *PValue;
- uv_rwlock_rdunlock(&page_index->lock);
- return descr;
-};
+WAL *wal_get(struct rrdengine_instance *ctx, unsigned size) {
+ if(!size || size > RRDENG_BLOCK_SIZE)
+ fatal("DBENGINE: invalid WAL size requested");
-static void do_extent_processing (struct rrdengine_worker_config *wc, struct extent_io_descriptor *xt_io_descr, bool read_failed)
-{
- struct rrdengine_instance *ctx = wc->ctx;
- struct page_cache *pg_cache = &ctx->pg_cache;
- struct rrdeng_page_descr *descr;
- struct page_cache_descr *pg_cache_descr;
- int ret;
- unsigned i, j, count;
- void *page, *uncompressed_buf = NULL;
- uint32_t payload_length, payload_offset, page_offset, uncompressed_payload_length = 0;
- uint8_t have_read_error = 0;
- /* persistent structures */
- struct rrdeng_df_extent_header *header;
- struct rrdeng_df_extent_trailer *trailer;
- uLong crc;
+ WAL *wal = NULL;
- header = xt_io_descr->buf;
- payload_length = header->payload_length;
- count = header->number_of_pages;
- payload_offset = sizeof(*header) + sizeof(header->descr[0]) * count;
- trailer = xt_io_descr->buf + xt_io_descr->bytes - sizeof(*trailer);
-
- if (unlikely(read_failed)) {
- struct rrdengine_datafile *datafile = xt_io_descr->descr_array[0]->extent->datafile;
-
- ++ctx->stats.io_errors;
- rrd_stat_atomic_add(&global_io_errors, 1);
- have_read_error = 1;
- error("%s: uv_fs_read - extent at offset %"PRIu64"(%u) in datafile %u-%u.", __func__, xt_io_descr->pos,
- xt_io_descr->bytes, datafile->tier, datafile->fileno);
- goto after_crc_check;
+ netdata_spinlock_lock(&wal_globals.protected.spinlock);
+
+ if(likely(wal_globals.protected.available_items)) {
+ wal = wal_globals.protected.available_items;
+ DOUBLE_LINKED_LIST_REMOVE_ITEM_UNSAFE(wal_globals.protected.available_items, wal, cache.prev, cache.next);
+ wal_globals.protected.available--;
}
- crc = crc32(0L, Z_NULL, 0);
- crc = crc32(crc, xt_io_descr->buf, xt_io_descr->bytes - sizeof(*trailer));
- ret = crc32cmp(trailer->checksum, crc);
-#ifdef NETDATA_INTERNAL_CHECKS
- {
- struct rrdengine_datafile *datafile = xt_io_descr->descr_array[0]->extent->datafile;
- debug(D_RRDENGINE, "%s: Extent at offset %"PRIu64"(%u) was read from datafile %u-%u. CRC32 check: %s", __func__,
- xt_io_descr->pos, xt_io_descr->bytes, datafile->tier, datafile->fileno, ret ? "FAILED" : "SUCCEEDED");
+
+ uint64_t transaction_id = __atomic_fetch_add(&ctx->atomic.transaction_id, 1, __ATOMIC_RELAXED);
+ netdata_spinlock_unlock(&wal_globals.protected.spinlock);
+
+ if(unlikely(!wal)) {
+ wal = mallocz(sizeof(WAL));
+ wal->buf_size = RRDENG_BLOCK_SIZE;
+ int ret = posix_memalign((void *)&wal->buf, RRDFILE_ALIGNMENT, wal->buf_size);
+ if (unlikely(ret))
+ fatal("DBENGINE: posix_memalign:%s", strerror(ret));
+ __atomic_add_fetch(&wal_globals.atomics.allocated, 1, __ATOMIC_RELAXED);
}
-#endif
- if (unlikely(ret)) {
- struct rrdengine_datafile *datafile = xt_io_descr->descr_array[0]->extent->datafile;
- ++ctx->stats.io_errors;
- rrd_stat_atomic_add(&global_io_errors, 1);
- have_read_error = 1;
- error("%s: Extent at offset %"PRIu64"(%u) was read from datafile %u-%u. CRC32 check: FAILED", __func__,
- xt_io_descr->pos, xt_io_descr->bytes, datafile->tier, datafile->fileno);
+ // these need to survive
+ unsigned buf_size = wal->buf_size;
+ void *buf = wal->buf;
+
+ memset(wal, 0, sizeof(WAL));
+
+ // put them back
+ wal->buf_size = buf_size;
+ wal->buf = buf;
+
+ memset(wal->buf, 0, wal->buf_size);
+
+ wal->transaction_id = transaction_id;
+ wal->size = size;
+
+ return wal;
+}
+
+void wal_release(WAL *wal) {
+ if(unlikely(!wal)) return;
+
+ netdata_spinlock_lock(&wal_globals.protected.spinlock);
+ DOUBLE_LINKED_LIST_APPEND_ITEM_UNSAFE(wal_globals.protected.available_items, wal, cache.prev, cache.next);
+ wal_globals.protected.available++;
+ netdata_spinlock_unlock(&wal_globals.protected.spinlock);
+}
+
+// ----------------------------------------------------------------------------
+// command queue cache
+
+struct rrdeng_cmd {
+ struct rrdengine_instance *ctx;
+ enum rrdeng_opcode opcode;
+ void *data;
+ struct completion *completion;
+ enum storage_priority priority;
+ dequeue_callback_t dequeue_cb;
+
+ struct {
+ struct rrdeng_cmd *prev;
+ struct rrdeng_cmd *next;
+ } queue;
+};
+
+static void rrdeng_cmd_queue_init(void) {
+ rrdeng_main.cmd_queue.ar = aral_create("dbengine-opcodes",
+ sizeof(struct rrdeng_cmd),
+ 0,
+ 65536,
+ NULL,
+ NULL, NULL, false, false);
+}
+
+static inline STORAGE_PRIORITY rrdeng_enq_cmd_map_opcode_to_priority(enum rrdeng_opcode opcode, STORAGE_PRIORITY priority) {
+ if(unlikely(priority >= STORAGE_PRIORITY_INTERNAL_MAX_DONT_USE))
+ priority = STORAGE_PRIORITY_BEST_EFFORT;
+
+ switch(opcode) {
+ case RRDENG_OPCODE_QUERY:
+ priority = STORAGE_PRIORITY_INTERNAL_QUERY_PREP;
+ break;
+
+ default:
+ break;
}
-after_crc_check:
- if (!have_read_error && RRD_NO_COMPRESSION != header->compression_algorithm) {
- uncompressed_payload_length = 0;
- for (i = 0 ; i < count ; ++i) {
- uncompressed_payload_length += header->descr[i].page_length;
+ return priority;
+}
+
+void rrdeng_enqueue_epdl_cmd(struct rrdeng_cmd *cmd) {
+ epdl_cmd_queued(cmd->data, cmd);
+}
+
+void rrdeng_dequeue_epdl_cmd(struct rrdeng_cmd *cmd) {
+ epdl_cmd_dequeued(cmd->data);
+}
+
+void rrdeng_req_cmd(requeue_callback_t get_cmd_cb, void *data, STORAGE_PRIORITY priority) {
+ netdata_spinlock_lock(&rrdeng_main.cmd_queue.unsafe.spinlock);
+
+ struct rrdeng_cmd *cmd = get_cmd_cb(data);
+ if(cmd) {
+ priority = rrdeng_enq_cmd_map_opcode_to_priority(cmd->opcode, priority);
+
+ if (cmd->priority > priority) {
+ DOUBLE_LINKED_LIST_REMOVE_ITEM_UNSAFE(rrdeng_main.cmd_queue.unsafe.waiting_items_by_priority[cmd->priority], cmd, queue.prev, queue.next);
+ DOUBLE_LINKED_LIST_APPEND_ITEM_UNSAFE(rrdeng_main.cmd_queue.unsafe.waiting_items_by_priority[priority], cmd, queue.prev, queue.next);
+ cmd->priority = priority;
}
- uncompressed_buf = mallocz(uncompressed_payload_length);
- ret = LZ4_decompress_safe(xt_io_descr->buf + payload_offset, uncompressed_buf,
- payload_length, uncompressed_payload_length);
- ctx->stats.before_decompress_bytes += payload_length;
- ctx->stats.after_decompress_bytes += ret;
- debug(D_RRDENGINE, "LZ4 decompressed %u bytes to %d bytes.", payload_length, ret);
- /* care, we don't hold the descriptor mutex */
}
- {
- uint8_t xt_is_cached = 0;
- unsigned xt_idx;
- struct extent_info *extent = xt_io_descr->descr_array[0]->extent;
-
- xt_is_cached = !lookup_in_xt_cache(wc, extent, &xt_idx);
- if (xt_is_cached && check_bit(wc->xt_cache.inflight_bitmap, xt_idx)) {
- struct extent_cache *xt_cache = &wc->xt_cache;
- struct extent_cache_element *xt_cache_elem = &xt_cache->extent_array[xt_idx];
- struct extent_io_descriptor *curr, *next;
-
- if (have_read_error) {
- memset(xt_cache_elem->pages, 0, sizeof(xt_cache_elem->pages));
- } else if (RRD_NO_COMPRESSION == header->compression_algorithm) {
- (void)memcpy(xt_cache_elem->pages, xt_io_descr->buf + payload_offset, payload_length);
- } else {
- (void)memcpy(xt_cache_elem->pages, uncompressed_buf, uncompressed_payload_length);
- }
- /* complete all connected in-flight read requests */
- for (curr = xt_cache_elem->inflight_io_descr->next ; curr ; curr = next) {
- next = curr->next;
- read_cached_extent_cb(wc, xt_idx, curr);
+
+ netdata_spinlock_unlock(&rrdeng_main.cmd_queue.unsafe.spinlock);
+}
+
+void rrdeng_enq_cmd(struct rrdengine_instance *ctx, enum rrdeng_opcode opcode, void *data, struct completion *completion,
+ enum storage_priority priority, enqueue_callback_t enqueue_cb, dequeue_callback_t dequeue_cb) {
+
+ priority = rrdeng_enq_cmd_map_opcode_to_priority(opcode, priority);
+
+ struct rrdeng_cmd *cmd = aral_mallocz(rrdeng_main.cmd_queue.ar);
+ memset(cmd, 0, sizeof(struct rrdeng_cmd));
+ cmd->ctx = ctx;
+ cmd->opcode = opcode;
+ cmd->data = data;
+ cmd->completion = completion;
+ cmd->priority = priority;
+ cmd->dequeue_cb = dequeue_cb;
+
+ netdata_spinlock_lock(&rrdeng_main.cmd_queue.unsafe.spinlock);
+ DOUBLE_LINKED_LIST_APPEND_ITEM_UNSAFE(rrdeng_main.cmd_queue.unsafe.waiting_items_by_priority[priority], cmd, queue.prev, queue.next);
+ rrdeng_main.cmd_queue.unsafe.waiting++;
+ if(enqueue_cb)
+ enqueue_cb(cmd);
+ netdata_spinlock_unlock(&rrdeng_main.cmd_queue.unsafe.spinlock);
+
+ fatal_assert(0 == uv_async_send(&rrdeng_main.async));
+}
+
+static inline bool rrdeng_cmd_has_waiting_opcodes_in_lower_priorities(STORAGE_PRIORITY priority, STORAGE_PRIORITY max_priority) {
+ for(; priority <= max_priority ; priority++)
+ if(rrdeng_main.cmd_queue.unsafe.waiting_items_by_priority[priority])
+ return true;
+
+ return false;
+}
+
+static inline struct rrdeng_cmd rrdeng_deq_cmd(void) {
+ struct rrdeng_cmd *cmd = NULL;
+
+ STORAGE_PRIORITY max_priority = work_request_full() ? STORAGE_PRIORITY_INTERNAL_DBENGINE : STORAGE_PRIORITY_BEST_EFFORT;
+
+ // find an opcode to execute from the queue
+ netdata_spinlock_lock(&rrdeng_main.cmd_queue.unsafe.spinlock);
+ for(STORAGE_PRIORITY priority = STORAGE_PRIORITY_INTERNAL_DBENGINE; priority <= max_priority ; priority++) {
+ cmd = rrdeng_main.cmd_queue.unsafe.waiting_items_by_priority[priority];
+ if(cmd) {
+
+ // avoid starvation of lower priorities
+ if(unlikely(priority >= STORAGE_PRIORITY_HIGH &&
+ priority < STORAGE_PRIORITY_BEST_EFFORT &&
+ ++rrdeng_main.cmd_queue.unsafe.executed_by_priority[priority] % 50 == 0 &&
+ rrdeng_cmd_has_waiting_opcodes_in_lower_priorities(priority + 1, max_priority))) {
+ // let the others run 2% of the requests
+ cmd = NULL;
+ continue;
}
- xt_cache_elem->inflight_io_descr = NULL;
- modify_bit(&xt_cache->inflight_bitmap, xt_idx, 0); /* not in-flight anymore */
+
+ // remove it from the queue
+ DOUBLE_LINKED_LIST_REMOVE_ITEM_UNSAFE(rrdeng_main.cmd_queue.unsafe.waiting_items_by_priority[priority], cmd, queue.prev, queue.next);
+ rrdeng_main.cmd_queue.unsafe.waiting--;
+ break;
}
}
- uv_rwlock_rdlock(&pg_cache->metrics_index.lock);
- Pvoid_t *PValue = JudyHSGet(pg_cache->metrics_index.JudyHS_array, xt_io_descr->descr_array[0]->id, sizeof(uuid_t));
- struct pg_cache_page_index *page_index = likely( NULL != PValue) ? *PValue : NULL;
- uv_rwlock_rdunlock(&pg_cache->metrics_index.lock);
-
-
- for (i = 0, page_offset = 0; i < count; page_offset += header->descr[i++].page_length) {
- uint8_t is_prefetched_page;
- descr = NULL;
- for (j = 0 ; j < xt_io_descr->descr_count; ++j) {
- struct rrdeng_page_descr descrj;
-
- descrj = xt_io_descr->descr_read_array[j];
- /* care, we don't hold the descriptor mutex */
- if (!uuid_compare(*(uuid_t *) header->descr[i].uuid, *descrj.id) &&
- header->descr[i].page_length == descrj.page_length &&
- header->descr[i].start_time_ut == descrj.start_time_ut &&
- header->descr[i].end_time_ut == descrj.end_time_ut) {
- //descr = descrj;
- descr = get_descriptor(page_index, (time_t) (descrj.start_time_ut / USEC_PER_SEC));
- if (unlikely(!descr)) {
- error_limit_static_thread_var(erl, 1, 0);
- error_limit(&erl, "%s: Required descriptor is not in the page index anymore", __FUNCTION__);
- }
- break;
- }
- }
- is_prefetched_page = 0;
- if (!descr) { /* This extent page has not been requested. Try populating it for locality (best effort). */
- descr = pg_cache_lookup_unpopulated_and_lock(ctx, (uuid_t *)header->descr[i].uuid,
- header->descr[i].start_time_ut);
- if (!descr)
- continue; /* Failed to reserve a suitable page */
- is_prefetched_page = 1;
- }
- page = dbengine_page_alloc();
-
- /* care, we don't hold the descriptor mutex */
- if (have_read_error) {
- fill_page_with_nulls(page, descr->page_length, descr->type);
- } else if (RRD_NO_COMPRESSION == header->compression_algorithm) {
- (void) memcpy(page, xt_io_descr->buf + payload_offset + page_offset, descr->page_length);
- } else {
- (void) memcpy(page, uncompressed_buf + page_offset, descr->page_length);
- }
- rrdeng_page_descr_mutex_lock(ctx, descr);
- pg_cache_descr = descr->pg_cache_descr;
- pg_cache_descr->page = page;
- pg_cache_descr->flags |= RRD_PAGE_POPULATED;
- pg_cache_descr->flags &= ~RRD_PAGE_READ_PENDING;
- rrdeng_page_descr_mutex_unlock(ctx, descr);
- pg_cache_replaceQ_insert(ctx, descr);
- if (xt_io_descr->release_descr || is_prefetched_page) {
- pg_cache_put(ctx, descr);
- } else {
- debug(D_RRDENGINE, "%s: Waking up waiters.", __func__);
- pg_cache_wake_up_waiters(ctx, descr);
- }
+ if(cmd && cmd->dequeue_cb) {
+ cmd->dequeue_cb(cmd);
+ cmd->dequeue_cb = NULL;
}
- if (!have_read_error && RRD_NO_COMPRESSION != header->compression_algorithm) {
- freez(uncompressed_buf);
+
+ netdata_spinlock_unlock(&rrdeng_main.cmd_queue.unsafe.spinlock);
+
+ struct rrdeng_cmd ret;
+ if(cmd) {
+ // copy it, to return it
+ ret = *cmd;
+
+ aral_freez(rrdeng_main.cmd_queue.ar, cmd);
}
- if (xt_io_descr->completion)
- completion_mark_complete(xt_io_descr->completion);
+ else
+ ret = (struct rrdeng_cmd) {
+ .ctx = NULL,
+ .opcode = RRDENG_OPCODE_NOOP,
+ .priority = STORAGE_PRIORITY_BEST_EFFORT,
+ .completion = NULL,
+ .data = NULL,
+ };
+
+ return ret;
}
-static void read_extent_cb(uv_fs_t *req)
-{
- struct rrdengine_worker_config *wc = req->loop->data;
- struct extent_io_descriptor *xt_io_descr;
- xt_io_descr = req->data;
- do_extent_processing(wc, xt_io_descr, req->result < 0);
- uv_fs_req_cleanup(req);
- posix_memfree(xt_io_descr->buf);
- freez(xt_io_descr);
+// ----------------------------------------------------------------------------
+
+struct {
+ ARAL *aral[RRD_STORAGE_TIERS];
+} dbengine_page_alloc_globals = {};
+
+static inline ARAL *page_size_lookup(size_t size) {
+ for(size_t tier = 0; tier < storage_tiers ;tier++)
+ if(size == tier_page_size[tier])
+ return dbengine_page_alloc_globals.aral[tier];
+
+ return NULL;
}
-static void read_mmap_extent_cb(uv_work_t *req, int status __maybe_unused)
-{
- struct rrdengine_worker_config *wc = req->loop->data;
- struct rrdengine_instance *ctx = wc->ctx;
- struct extent_io_descriptor *xt_io_descr;
- xt_io_descr = req->data;
+static void dbengine_page_alloc_init(void) {
+ for(size_t i = storage_tiers; i > 0 ;i--) {
+ size_t tier = storage_tiers - i;
- if (likely(xt_io_descr->map_base)) {
- do_extent_processing(wc, xt_io_descr, false);
- munmap(xt_io_descr->map_base, xt_io_descr->map_length);
- freez(xt_io_descr);
- return;
- }
+ char buf[20 + 1];
+ snprintfz(buf, 20, "tier%zu-pages", tier);
- // MMAP failed, so do uv_fs_read
- int ret = posix_memalign((void *)&xt_io_descr->buf, RRDFILE_ALIGNMENT, ALIGN_BYTES_CEILING(xt_io_descr->bytes));
- if (unlikely(ret)) {
- fatal("posix_memalign:%s", strerror(ret));
+ dbengine_page_alloc_globals.aral[tier] = aral_create(
+ buf,
+ tier_page_size[tier],
+ 64,
+ 512 * tier_page_size[tier],
+ pgc_aral_statistics(),
+ NULL, NULL, false, false);
}
- unsigned real_io_size = ALIGN_BYTES_CEILING( xt_io_descr->bytes);
- xt_io_descr->iov = uv_buf_init((void *)xt_io_descr->buf, real_io_size);
- xt_io_descr->req.data = xt_io_descr;
- ret = uv_fs_read(req->loop, &xt_io_descr->req, xt_io_descr->file, &xt_io_descr->iov, 1, (unsigned) xt_io_descr->pos, read_extent_cb);
- fatal_assert(-1 != ret);
- ctx->stats.io_read_bytes += real_io_size;
- ctx->stats.io_read_extent_bytes += real_io_size;
}
-static void do_mmap_read_extent(uv_work_t *req)
-{
- struct extent_io_descriptor *xt_io_descr = (struct extent_io_descriptor * )req->data;
- struct rrdengine_worker_config *wc = req->loop->data;
- struct rrdengine_instance *ctx = wc->ctx;
-
- off_t map_start = ALIGN_BYTES_FLOOR(xt_io_descr->pos);
- size_t length = ALIGN_BYTES_CEILING(xt_io_descr->pos + xt_io_descr->bytes) - map_start;
- unsigned real_io_size = xt_io_descr->bytes;
-
- void *data = mmap(NULL, length, PROT_READ, MAP_SHARED, xt_io_descr->file, map_start);
- if (likely(data != MAP_FAILED)) {
- xt_io_descr->map_base = data;
- xt_io_descr->map_length = length;
- xt_io_descr->buf = data + (xt_io_descr->pos - map_start);
- ctx->stats.io_read_bytes += real_io_size;
- ctx->stats.io_read_extent_bytes += real_io_size;
- }
+void *dbengine_page_alloc(size_t size) {
+ ARAL *ar = page_size_lookup(size);
+ if(ar) return aral_mallocz(ar);
+
+ return mallocz(size);
}
-static void do_read_extent(struct rrdengine_worker_config* wc,
- struct rrdeng_page_descr **descr,
- unsigned count,
- uint8_t release_descr)
-{
- struct rrdengine_instance *ctx = wc->ctx;
- struct page_cache_descr *pg_cache_descr;
- int ret;
- unsigned i, size_bytes, pos;
- struct extent_io_descriptor *xt_io_descr;
- struct rrdengine_datafile *datafile;
- struct extent_info *extent = descr[0]->extent;
- uint8_t xt_is_cached = 0, xt_is_inflight = 0;
- unsigned xt_idx;
-
- datafile = extent->datafile;
- pos = extent->offset;
- size_bytes = extent->size;
-
- xt_io_descr = callocz(1, sizeof(*xt_io_descr));
- for (i = 0 ; i < count; ++i) {
- rrdeng_page_descr_mutex_lock(ctx, descr[i]);
- pg_cache_descr = descr[i]->pg_cache_descr;
- pg_cache_descr->flags |= RRD_PAGE_READ_PENDING;
- rrdeng_page_descr_mutex_unlock(ctx, descr[i]);
- xt_io_descr->descr_array[i] = descr[i];
- xt_io_descr->descr_read_array[i] = *(descr[i]);
- }
- xt_io_descr->descr_count = count;
- xt_io_descr->file = datafile->file;
- xt_io_descr->bytes = size_bytes;
- xt_io_descr->pos = pos;
- xt_io_descr->req_worker.data = xt_io_descr;
- xt_io_descr->completion = NULL;
- xt_io_descr->release_descr = release_descr;
- xt_io_descr->buf = NULL;
-
- xt_is_cached = !lookup_in_xt_cache(wc, extent, &xt_idx);
- if (xt_is_cached) {
- xt_cache_replaceQ_set_hot(wc, &wc->xt_cache.extent_array[xt_idx]);
- xt_is_inflight = check_bit(wc->xt_cache.inflight_bitmap, xt_idx);
- if (xt_is_inflight) {
- enqueue_inflight_read_to_xt_cache(wc, xt_idx, xt_io_descr);
- return;
- }
- return read_cached_extent_cb(wc, xt_idx, xt_io_descr);
- } else {
- ret = try_insert_into_xt_cache(wc, extent);
- if (-1 != ret) {
- xt_idx = (unsigned)ret;
- modify_bit(&wc->xt_cache.inflight_bitmap, xt_idx, 1);
- wc->xt_cache.extent_array[xt_idx].inflight_io_descr = xt_io_descr;
- }
- }
+void dbengine_page_free(void *page, size_t size __maybe_unused) {
+ if(unlikely(!page || page == DBENGINE_EMPTY_PAGE))
+ return;
- ret = uv_queue_work(wc->loop, &xt_io_descr->req_worker, do_mmap_read_extent, read_mmap_extent_cb);
- fatal_assert(-1 != ret);
+ ARAL *ar = page_size_lookup(size);
+ if(ar)
+ aral_freez(ar, page);
+ else
+ freez(page);
+}
- ++ctx->stats.io_read_requests;
- ++ctx->stats.io_read_extents;
- ctx->stats.pg_cache_backfills += count;
+// ----------------------------------------------------------------------------
+
+void *dbengine_extent_alloc(size_t size) {
+ void *extent = mallocz(size);
+ return extent;
}
-static void commit_data_extent(struct rrdengine_worker_config* wc, struct extent_io_descriptor *xt_io_descr)
-{
- struct rrdengine_instance *ctx = wc->ctx;
+void dbengine_extent_free(void *extent, size_t size __maybe_unused) {
+ freez(extent);
+}
+
+static void journalfile_extent_build(struct rrdengine_instance *ctx, struct extent_io_descriptor *xt_io_descr) {
unsigned count, payload_length, descr_size, size_bytes;
void *buf;
/* persistent structures */
@@ -582,12 +596,13 @@ static void commit_data_extent(struct rrdengine_worker_config* wc, struct extent
payload_length = sizeof(*jf_metric_data) + descr_size;
size_bytes = sizeof(*jf_header) + payload_length + sizeof(*jf_trailer);
- buf = wal_get_transaction_buffer(wc, size_bytes);
+ xt_io_descr->wal = wal_get(ctx, size_bytes);
+ buf = xt_io_descr->wal->buf;
jf_header = buf;
jf_header->type = STORE_DATA;
jf_header->reserved = 0;
- jf_header->id = ctx->commit_log.transaction_id++;
+ jf_header->id = xt_io_descr->wal->transaction_id;
jf_header->payload_length = payload_length;
jf_metric_data = buf + sizeof(*jf_header);
@@ -602,265 +617,210 @@ static void commit_data_extent(struct rrdengine_worker_config* wc, struct extent
crc32set(jf_trailer->checksum, crc);
}
-static void do_commit_transaction(struct rrdengine_worker_config* wc, uint8_t type, void *data)
-{
- switch (type) {
- case STORE_DATA:
- commit_data_extent(wc, (struct extent_io_descriptor *)data);
- break;
- default:
- fatal_assert(type == STORE_DATA);
- break;
- }
-}
+static void after_extent_flushed_to_open(struct rrdengine_instance *ctx __maybe_unused, void *data __maybe_unused, struct completion *completion __maybe_unused, uv_work_t* req __maybe_unused, int status __maybe_unused) {
+ if(completion)
+ completion_mark_complete(completion);
-static void after_invalidate_oldest_committed(struct rrdengine_worker_config* wc)
-{
- int error;
-
- error = uv_thread_join(wc->now_invalidating_dirty_pages);
- if (error) {
- error("uv_thread_join(): %s", uv_strerror(error));
- }
- freez(wc->now_invalidating_dirty_pages);
- wc->now_invalidating_dirty_pages = NULL;
- wc->cleanup_thread_invalidating_dirty_pages = 0;
+ if(ctx_is_available_for_queries(ctx))
+ rrdeng_enq_cmd(ctx, RRDENG_OPCODE_DATABASE_ROTATE, NULL, NULL, STORAGE_PRIORITY_INTERNAL_DBENGINE, NULL, NULL);
}
-static void invalidate_oldest_committed(void *arg)
-{
- struct rrdengine_instance *ctx = arg;
- struct rrdengine_worker_config *wc = &ctx->worker_config;
- struct page_cache *pg_cache = &ctx->pg_cache;
- int ret;
- struct rrdeng_page_descr *descr;
- struct page_cache_descr *pg_cache_descr;
- Pvoid_t *PValue;
- Word_t Index;
- unsigned nr_committed_pages;
+static void *extent_flushed_to_open_tp_worker(struct rrdengine_instance *ctx __maybe_unused, void *data __maybe_unused, struct completion *completion __maybe_unused, uv_work_t *uv_work_req __maybe_unused) {
+ worker_is_busy(UV_EVENT_DBENGINE_FLUSHED_TO_OPEN);
- do {
- uv_rwlock_wrlock(&pg_cache->committed_page_index.lock);
- for (Index = 0,
- PValue = JudyLFirst(pg_cache->committed_page_index.JudyL_array, &Index, PJE0),
- descr = unlikely(NULL == PValue) ? NULL : *PValue;
+ uv_fs_t *uv_fs_request = data;
+ struct extent_io_descriptor *xt_io_descr = uv_fs_request->data;
+ struct page_descr_with_data *descr;
+ struct rrdengine_datafile *datafile;
+ unsigned i;
- descr != NULL;
+ datafile = xt_io_descr->datafile;
- PValue = JudyLNext(pg_cache->committed_page_index.JudyL_array, &Index, PJE0),
- descr = unlikely(NULL == PValue) ? NULL : *PValue) {
- fatal_assert(0 != descr->page_length);
+ bool still_running = ctx_is_available_for_queries(ctx);
- rrdeng_page_descr_mutex_lock(ctx, descr);
- pg_cache_descr = descr->pg_cache_descr;
- if (!(pg_cache_descr->flags & RRD_PAGE_WRITE_PENDING) && pg_cache_try_get_unsafe(descr, 1)) {
- rrdeng_page_descr_mutex_unlock(ctx, descr);
+ for (i = 0 ; i < xt_io_descr->descr_count ; ++i) {
+ descr = xt_io_descr->descr_array[i];
- ret = JudyLDel(&pg_cache->committed_page_index.JudyL_array, Index, PJE0);
- fatal_assert(1 == ret);
- break;
- }
- rrdeng_page_descr_mutex_unlock(ctx, descr);
- }
- uv_rwlock_wrunlock(&pg_cache->committed_page_index.lock);
+ if (likely(still_running))
+ pgc_open_add_hot_page(
+ (Word_t)ctx, descr->metric_id,
+ (time_t) (descr->start_time_ut / USEC_PER_SEC),
+ (time_t) (descr->end_time_ut / USEC_PER_SEC),
+ descr->update_every_s,
+ datafile,
+ xt_io_descr->pos, xt_io_descr->bytes, descr->page_length);
- if (!descr) {
- info("Failed to invalidate any dirty pages to relieve page cache pressure.");
+ page_descriptor_release(descr);
+ }
- goto out;
- }
- pg_cache_punch_hole(ctx, descr, 1, 1, NULL);
+ uv_fs_req_cleanup(uv_fs_request);
+ posix_memfree(xt_io_descr->buf);
+ extent_io_descriptor_release(xt_io_descr);
- uv_rwlock_wrlock(&pg_cache->committed_page_index.lock);
- nr_committed_pages = --pg_cache->committed_page_index.nr_committed_pages;
- uv_rwlock_wrunlock(&pg_cache->committed_page_index.lock);
- rrd_stat_atomic_add(&ctx->stats.flushing_pressure_page_deletions, 1);
- rrd_stat_atomic_add(&global_flushing_pressure_page_deletions, 1);
+ netdata_spinlock_lock(&datafile->writers.spinlock);
+ datafile->writers.flushed_to_open_running--;
+ netdata_spinlock_unlock(&datafile->writers.spinlock);
- } while (nr_committed_pages >= pg_cache_committed_hard_limit(ctx));
-out:
- wc->cleanup_thread_invalidating_dirty_pages = 1;
- /* wake up event loop */
- fatal_assert(0 == uv_async_send(&wc->async));
-}
+ if(datafile->fileno != ctx_last_fileno_get(ctx) && still_running)
+ // we just finished a flushing on a datafile that is not the active one
+ rrdeng_enq_cmd(ctx, RRDENG_OPCODE_JOURNAL_INDEX, datafile, NULL, STORAGE_PRIORITY_INTERNAL_DBENGINE, NULL, NULL);
-void rrdeng_invalidate_oldest_committed(struct rrdengine_worker_config* wc)
-{
- struct rrdengine_instance *ctx = wc->ctx;
- struct page_cache *pg_cache = &ctx->pg_cache;
- unsigned nr_committed_pages;
- int error;
+ return data;
+}
- if (unlikely(ctx->quiesce != NO_QUIESCE)) /* Shutting down */
- return;
+// Main event loop callback
+static void after_extent_write_datafile_io(uv_fs_t *uv_fs_request) {
+ worker_is_busy(RRDENG_OPCODE_MAX + RRDENG_OPCODE_EXTENT_WRITE);
- uv_rwlock_rdlock(&pg_cache->committed_page_index.lock);
- nr_committed_pages = pg_cache->committed_page_index.nr_committed_pages;
- uv_rwlock_rdunlock(&pg_cache->committed_page_index.lock);
+ struct extent_io_descriptor *xt_io_descr = uv_fs_request->data;
+ struct rrdengine_datafile *datafile = xt_io_descr->datafile;
+ struct rrdengine_instance *ctx = datafile->ctx;
- if (nr_committed_pages >= pg_cache_committed_hard_limit(ctx)) {
- /* delete the oldest page in memory */
- if (wc->now_invalidating_dirty_pages) {
- /* already deleting a page */
- return;
- }
- errno = 0;
- error("Failed to flush dirty buffers quickly enough in dbengine instance \"%s\". "
- "Metric data are being deleted, please reduce disk load or use a faster disk.", ctx->dbfiles_path);
-
- wc->now_invalidating_dirty_pages = mallocz(sizeof(*wc->now_invalidating_dirty_pages));
- wc->cleanup_thread_invalidating_dirty_pages = 0;
-
- error = uv_thread_create(wc->now_invalidating_dirty_pages, invalidate_oldest_committed, ctx);
- if (error) {
- error("uv_thread_create(): %s", uv_strerror(error));
- freez(wc->now_invalidating_dirty_pages);
- wc->now_invalidating_dirty_pages = NULL;
- }
+ if (uv_fs_request->result < 0) {
+ ctx_io_error(ctx);
+ error("DBENGINE: %s: uv_fs_write(): %s", __func__, uv_strerror((int)uv_fs_request->result));
}
+
+ journalfile_v1_extent_write(ctx, xt_io_descr->datafile, xt_io_descr->wal, &rrdeng_main.loop);
+
+ netdata_spinlock_lock(&datafile->writers.spinlock);
+ datafile->writers.running--;
+ datafile->writers.flushed_to_open_running++;
+ netdata_spinlock_unlock(&datafile->writers.spinlock);
+
+ rrdeng_enq_cmd(xt_io_descr->ctx,
+ RRDENG_OPCODE_FLUSHED_TO_OPEN,
+ uv_fs_request,
+ xt_io_descr->completion,
+ STORAGE_PRIORITY_INTERNAL_DBENGINE,
+ NULL,
+ NULL);
+
+ worker_is_idle();
}
-void flush_pages_cb(uv_fs_t* req)
-{
- struct rrdengine_worker_config* wc = req->loop->data;
- struct rrdengine_instance *ctx = wc->ctx;
- struct page_cache *pg_cache = &ctx->pg_cache;
- struct extent_io_descriptor *xt_io_descr;
- struct rrdeng_page_descr *descr;
- struct page_cache_descr *pg_cache_descr;
- unsigned i, count;
-
- xt_io_descr = req->data;
- if (req->result < 0) {
- ++ctx->stats.io_errors;
- rrd_stat_atomic_add(&global_io_errors, 1);
- error("%s: uv_fs_write: %s", __func__, uv_strerror((int)req->result));
- }
-#ifdef NETDATA_INTERNAL_CHECKS
- {
- struct rrdengine_datafile *datafile = xt_io_descr->descr_array[0]->extent->datafile;
- debug(D_RRDENGINE, "%s: Extent at offset %"PRIu64"(%u) was written to datafile %u-%u. Waking up waiters.",
- __func__, xt_io_descr->pos, xt_io_descr->bytes, datafile->tier, datafile->fileno);
- }
-#endif
- count = xt_io_descr->descr_count;
- for (i = 0 ; i < count ; ++i) {
- /* care, we don't hold the descriptor mutex */
- descr = xt_io_descr->descr_array[i];
+static bool datafile_is_full(struct rrdengine_instance *ctx, struct rrdengine_datafile *datafile) {
+ bool ret = false;
+ netdata_spinlock_lock(&datafile->writers.spinlock);
- pg_cache_replaceQ_insert(ctx, descr);
+ if(ctx_is_available_for_queries(ctx) && datafile->pos > rrdeng_target_data_file_size(ctx))
+ ret = true;
- rrdeng_page_descr_mutex_lock(ctx, descr);
- pg_cache_descr = descr->pg_cache_descr;
- pg_cache_descr->flags &= ~(RRD_PAGE_DIRTY | RRD_PAGE_WRITE_PENDING);
- /* wake up waiters, care no reference being held */
- pg_cache_wake_up_waiters_unsafe(descr);
- rrdeng_page_descr_mutex_unlock(ctx, descr);
- }
- if (xt_io_descr->completion)
- completion_mark_complete(xt_io_descr->completion);
- uv_fs_req_cleanup(req);
- posix_memfree(xt_io_descr->buf);
- freez(xt_io_descr);
+ netdata_spinlock_unlock(&datafile->writers.spinlock);
+
+ return ret;
+}
+
+static struct rrdengine_datafile *get_datafile_to_write_extent(struct rrdengine_instance *ctx) {
+ struct rrdengine_datafile *datafile;
- uv_rwlock_wrlock(&pg_cache->committed_page_index.lock);
- pg_cache->committed_page_index.nr_committed_pages -= count;
- uv_rwlock_wrunlock(&pg_cache->committed_page_index.lock);
- wc->inflight_dirty_pages -= count;
+ // get the latest datafile
+ uv_rwlock_rdlock(&ctx->datafiles.rwlock);
+ datafile = ctx->datafiles.first->prev;
+ // become a writer on this datafile, to prevent it from vanishing
+ netdata_spinlock_lock(&datafile->writers.spinlock);
+ datafile->writers.running++;
+ netdata_spinlock_unlock(&datafile->writers.spinlock);
+ uv_rwlock_rdunlock(&ctx->datafiles.rwlock);
+
+ if(datafile_is_full(ctx, datafile)) {
+ // remember the datafile we have become writers to
+ struct rrdengine_datafile *old_datafile = datafile;
+
+ // only 1 datafile creation at a time
+ static netdata_mutex_t mutex = NETDATA_MUTEX_INITIALIZER;
+ netdata_mutex_lock(&mutex);
+
+ // take the latest datafile again - without this, multiple threads may create multiple files
+ uv_rwlock_rdlock(&ctx->datafiles.rwlock);
+ datafile = ctx->datafiles.first->prev;
+ uv_rwlock_rdunlock(&ctx->datafiles.rwlock);
+
+ if(datafile_is_full(ctx, datafile) && create_new_datafile_pair(ctx) == 0)
+ rrdeng_enq_cmd(ctx, RRDENG_OPCODE_JOURNAL_INDEX, datafile, NULL, STORAGE_PRIORITY_INTERNAL_DBENGINE, NULL,
+ NULL);
+
+ netdata_mutex_unlock(&mutex);
+
+ // get the new latest datafile again, like above
+ uv_rwlock_rdlock(&ctx->datafiles.rwlock);
+ datafile = ctx->datafiles.first->prev;
+ // become a writer on this datafile, to prevent it from vanishing
+ netdata_spinlock_lock(&datafile->writers.spinlock);
+ datafile->writers.running++;
+ netdata_spinlock_unlock(&datafile->writers.spinlock);
+ uv_rwlock_rdunlock(&ctx->datafiles.rwlock);
+
+ // release the writers on the old datafile
+ netdata_spinlock_lock(&old_datafile->writers.spinlock);
+ old_datafile->writers.running--;
+ netdata_spinlock_unlock(&old_datafile->writers.spinlock);
+ }
+
+ return datafile;
}
/*
- * completion must be NULL or valid.
- * Returns 0 when no flushing can take place.
- * Returns datafile bytes to be written on successful flushing initiation.
+ * Take a page list in a judy array and write them
*/
-static int do_flush_pages(struct rrdengine_worker_config* wc, int force, struct completion *completion)
-{
- struct rrdengine_instance *ctx = wc->ctx;
- struct page_cache *pg_cache = &ctx->pg_cache;
+static struct extent_io_descriptor *datafile_extent_build(struct rrdengine_instance *ctx, struct page_descr_with_data *base, struct completion *completion) {
int ret;
int compressed_size, max_compressed_size = 0;
unsigned i, count, size_bytes, pos, real_io_size;
uint32_t uncompressed_payload_length, payload_offset;
- struct rrdeng_page_descr *descr, *eligible_pages[MAX_PAGES_PER_EXTENT];
- struct page_cache_descr *pg_cache_descr;
+ struct page_descr_with_data *descr, *eligible_pages[MAX_PAGES_PER_EXTENT];
struct extent_io_descriptor *xt_io_descr;
+ struct extent_buffer *eb = NULL;
void *compressed_buf = NULL;
- Word_t descr_commit_idx_array[MAX_PAGES_PER_EXTENT];
- Pvoid_t *PValue;
Word_t Index;
- uint8_t compression_algorithm = ctx->global_compress_alg;
- struct extent_info *extent;
+ uint8_t compression_algorithm = ctx->config.global_compress_alg;
struct rrdengine_datafile *datafile;
/* persistent structures */
struct rrdeng_df_extent_header *header;
struct rrdeng_df_extent_trailer *trailer;
uLong crc;
- if (force) {
- debug(D_RRDENGINE, "Asynchronous flushing of extent has been forced by page pressure.");
- }
- uv_rwlock_wrlock(&pg_cache->committed_page_index.lock);
- for (Index = 0, count = 0, uncompressed_payload_length = 0,
- PValue = JudyLFirst(pg_cache->committed_page_index.JudyL_array, &Index, PJE0),
- descr = unlikely(NULL == PValue) ? NULL : *PValue ;
-
- descr != NULL && count != rrdeng_pages_per_extent;
-
- PValue = JudyLNext(pg_cache->committed_page_index.JudyL_array, &Index, PJE0),
- descr = unlikely(NULL == PValue) ? NULL : *PValue) {
- uint8_t page_write_pending;
-
- fatal_assert(0 != descr->page_length);
- page_write_pending = 0;
-
- rrdeng_page_descr_mutex_lock(ctx, descr);
- pg_cache_descr = descr->pg_cache_descr;
- if (!(pg_cache_descr->flags & RRD_PAGE_WRITE_PENDING)) {
- page_write_pending = 1;
- /* care, no reference being held */
- pg_cache_descr->flags |= RRD_PAGE_WRITE_PENDING;
- uncompressed_payload_length += descr->page_length;
- descr_commit_idx_array[count] = Index;
- eligible_pages[count++] = descr;
- }
- rrdeng_page_descr_mutex_unlock(ctx, descr);
+ for(descr = base, Index = 0, count = 0, uncompressed_payload_length = 0;
+ descr && count != rrdeng_pages_per_extent;
+ descr = descr->link.next, Index++) {
+
+ uncompressed_payload_length += descr->page_length;
+ eligible_pages[count++] = descr;
- if (page_write_pending) {
- ret = JudyLDel(&pg_cache->committed_page_index.JudyL_array, Index, PJE0);
- fatal_assert(1 == ret);
- }
}
- uv_rwlock_wrunlock(&pg_cache->committed_page_index.lock);
if (!count) {
- debug(D_RRDENGINE, "%s: no pages eligible for flushing.", __func__);
if (completion)
completion_mark_complete(completion);
- return 0;
+
+ __atomic_sub_fetch(&ctx->atomic.extents_currently_being_flushed, 1, __ATOMIC_RELAXED);
+ return NULL;
}
- wc->inflight_dirty_pages += count;
- xt_io_descr = mallocz(sizeof(*xt_io_descr));
+ xt_io_descr = extent_io_descriptor_get();
+ xt_io_descr->ctx = ctx;
payload_offset = sizeof(*header) + count * sizeof(header->descr[0]);
switch (compression_algorithm) {
- case RRD_NO_COMPRESSION:
- size_bytes = payload_offset + uncompressed_payload_length + sizeof(*trailer);
- break;
- default: /* Compress */
- fatal_assert(uncompressed_payload_length < LZ4_MAX_INPUT_SIZE);
- max_compressed_size = LZ4_compressBound(uncompressed_payload_length);
- compressed_buf = mallocz(max_compressed_size);
- size_bytes = payload_offset + MAX(uncompressed_payload_length, (unsigned)max_compressed_size) + sizeof(*trailer);
- break;
+ case RRD_NO_COMPRESSION:
+ size_bytes = payload_offset + uncompressed_payload_length + sizeof(*trailer);
+ break;
+
+ default: /* Compress */
+ fatal_assert(uncompressed_payload_length < LZ4_MAX_INPUT_SIZE);
+ max_compressed_size = LZ4_compressBound(uncompressed_payload_length);
+ eb = extent_buffer_get(max_compressed_size);
+ compressed_buf = eb->data;
+ size_bytes = payload_offset + MAX(uncompressed_payload_length, (unsigned)max_compressed_size) + sizeof(*trailer);
+ break;
}
+
ret = posix_memalign((void *)&xt_io_descr->buf, RRDFILE_ALIGNMENT, ALIGN_BYTES_CEILING(size_bytes));
if (unlikely(ret)) {
- fatal("posix_memalign:%s", strerror(ret));
+ fatal("DBENGINE: posix_memalign:%s", strerror(ret));
/* freez(xt_io_descr);*/
}
memset(xt_io_descr->buf, 0, ALIGN_BYTES_CEILING(size_bytes));
- (void) memcpy(xt_io_descr->descr_array, eligible_pages, sizeof(struct rrdeng_page_descr *) * count);
+ (void) memcpy(xt_io_descr->descr_array, eligible_pages, sizeof(struct page_descr_with_data *) * count);
xt_io_descr->descr_count = count;
pos = 0;
@@ -869,17 +829,7 @@ static int do_flush_pages(struct rrdengine_worker_config* wc, int force, struct
header->number_of_pages = count;
pos += sizeof(*header);
- extent = mallocz(sizeof(*extent) + count * sizeof(extent->pages[0]));
- datafile = ctx->datafiles.last; /* TODO: check for exceeded size quota */
- extent->offset = datafile->pos;
- extent->number_of_pages = count;
- extent->datafile = datafile;
- extent->next = NULL;
-
for (i = 0 ; i < count ; ++i) {
- /* This is here for performance reasons */
- xt_io_descr->descr_commit_idx_array[i] = descr_commit_idx_array[i];
-
descr = xt_io_descr->descr_array[i];
header->descr[i].type = descr->type;
uuid_copy(*(uuid_t *)header->descr[i].uuid, *descr->id);
@@ -890,35 +840,40 @@ static int do_flush_pages(struct rrdengine_worker_config* wc, int force, struct
}
for (i = 0 ; i < count ; ++i) {
descr = xt_io_descr->descr_array[i];
- /* care, we don't hold the descriptor mutex */
- (void) memcpy(xt_io_descr->buf + pos, descr->pg_cache_descr->page, descr->page_length);
- descr->extent = extent;
- extent->pages[i] = descr;
-
+ (void) memcpy(xt_io_descr->buf + pos, descr->page, descr->page_length);
pos += descr->page_length;
}
- df_extent_insert(extent);
- switch (compression_algorithm) {
- case RRD_NO_COMPRESSION:
- header->payload_length = uncompressed_payload_length;
- break;
- default: /* Compress */
- compressed_size = LZ4_compress_default(xt_io_descr->buf + payload_offset, compressed_buf,
- uncompressed_payload_length, max_compressed_size);
- ctx->stats.before_compress_bytes += uncompressed_payload_length;
- ctx->stats.after_compress_bytes += compressed_size;
- debug(D_RRDENGINE, "LZ4 compressed %"PRIu32" bytes to %d bytes.", uncompressed_payload_length, compressed_size);
+ if(likely(compression_algorithm == RRD_LZ4)) {
+ compressed_size = LZ4_compress_default(
+ xt_io_descr->buf + payload_offset,
+ compressed_buf,
+ (int)uncompressed_payload_length,
+ max_compressed_size);
+
+ __atomic_add_fetch(&ctx->stats.before_compress_bytes, uncompressed_payload_length, __ATOMIC_RELAXED);
+ __atomic_add_fetch(&ctx->stats.after_compress_bytes, compressed_size, __ATOMIC_RELAXED);
+
(void) memcpy(xt_io_descr->buf + payload_offset, compressed_buf, compressed_size);
- freez(compressed_buf);
+ extent_buffer_release(eb);
size_bytes = payload_offset + compressed_size + sizeof(*trailer);
header->payload_length = compressed_size;
- break;
}
- extent->size = size_bytes;
- xt_io_descr->bytes = size_bytes;
+ else { // RRD_NO_COMPRESSION
+ header->payload_length = uncompressed_payload_length;
+ }
+
+ real_io_size = ALIGN_BYTES_CEILING(size_bytes);
+
+ datafile = get_datafile_to_write_extent(ctx);
+ netdata_spinlock_lock(&datafile->writers.spinlock);
+ xt_io_descr->datafile = datafile;
xt_io_descr->pos = datafile->pos;
- xt_io_descr->req.data = xt_io_descr;
+ datafile->pos += real_io_size;
+ netdata_spinlock_unlock(&datafile->writers.spinlock);
+
+ xt_io_descr->bytes = size_bytes;
+ xt_io_descr->uv_fs_request.data = xt_io_descr;
xt_io_descr->completion = completion;
trailer = xt_io_descr->buf + size_bytes - sizeof(*trailer);
@@ -926,324 +881,508 @@ static int do_flush_pages(struct rrdengine_worker_config* wc, int force, struct
crc = crc32(crc, xt_io_descr->buf, size_bytes - sizeof(*trailer));
crc32set(trailer->checksum, crc);
- real_io_size = ALIGN_BYTES_CEILING(size_bytes);
xt_io_descr->iov = uv_buf_init((void *)xt_io_descr->buf, real_io_size);
- ret = uv_fs_write(wc->loop, &xt_io_descr->req, datafile->file, &xt_io_descr->iov, 1, datafile->pos, flush_pages_cb);
- fatal_assert(-1 != ret);
- ctx->stats.io_write_bytes += real_io_size;
- ++ctx->stats.io_write_requests;
- ctx->stats.io_write_extent_bytes += real_io_size;
- ++ctx->stats.io_write_extents;
- do_commit_transaction(wc, STORE_DATA, xt_io_descr);
- datafile->pos += ALIGN_BYTES_CEILING(size_bytes);
- ctx->disk_space += ALIGN_BYTES_CEILING(size_bytes);
- rrdeng_test_quota(wc);
+ journalfile_extent_build(ctx, xt_io_descr);
+
+ ctx_last_flush_fileno_set(ctx, datafile->fileno);
+ ctx_current_disk_space_increase(ctx, real_io_size);
+ ctx_io_write_op_bytes(ctx, real_io_size);
- return ALIGN_BYTES_CEILING(size_bytes);
+ return xt_io_descr;
}
-static void after_delete_old_data(struct rrdengine_worker_config* wc)
-{
- struct rrdengine_instance *ctx = wc->ctx;
- struct rrdengine_datafile *datafile;
- struct rrdengine_journalfile *journalfile;
- unsigned deleted_bytes, journalfile_bytes, datafile_bytes;
- int ret, error;
- char path[RRDENG_PATH_MAX];
+static void after_extent_write(struct rrdengine_instance *ctx __maybe_unused, void *data __maybe_unused, struct completion *completion __maybe_unused, uv_work_t* uv_work_req __maybe_unused, int status __maybe_unused) {
+ struct extent_io_descriptor *xt_io_descr = data;
- datafile = ctx->datafiles.first;
- journalfile = datafile->journalfile;
- datafile_bytes = datafile->pos;
- journalfile_bytes = journalfile->pos;
- deleted_bytes = 0;
+ if(xt_io_descr) {
+ int ret = uv_fs_write(&rrdeng_main.loop,
+ &xt_io_descr->uv_fs_request,
+ xt_io_descr->datafile->file,
+ &xt_io_descr->iov,
+ 1,
+ (int64_t) xt_io_descr->pos,
+ after_extent_write_datafile_io);
- info("Deleting data and journal file pair.");
- datafile_list_delete(ctx, datafile);
- ret = destroy_journal_file(journalfile, datafile);
- if (!ret) {
- generate_journalfilepath(datafile, path, sizeof(path));
- info("Deleted journal file \"%s\".", path);
- deleted_bytes += journalfile_bytes;
- }
- ret = destroy_data_file(datafile);
- if (!ret) {
- generate_datafilepath(datafile, path, sizeof(path));
- info("Deleted data file \"%s\".", path);
- deleted_bytes += datafile_bytes;
+ fatal_assert(-1 != ret);
}
- freez(journalfile);
- freez(datafile);
+}
- ctx->disk_space -= deleted_bytes;
- info("Reclaimed %u bytes of disk space.", deleted_bytes);
+static void *extent_write_tp_worker(struct rrdengine_instance *ctx __maybe_unused, void *data __maybe_unused, struct completion *completion __maybe_unused, uv_work_t *uv_work_req __maybe_unused) {
+ worker_is_busy(UV_EVENT_DBENGINE_EXTENT_WRITE);
+ struct page_descr_with_data *base = data;
+ struct extent_io_descriptor *xt_io_descr = datafile_extent_build(ctx, base, completion);
+ return xt_io_descr;
+}
- error = uv_thread_join(wc->now_deleting_files);
- if (error) {
- error("uv_thread_join(): %s", uv_strerror(error));
- }
- freez(wc->now_deleting_files);
- /* unfreeze command processing */
- wc->now_deleting_files = NULL;
+static void after_database_rotate(struct rrdengine_instance *ctx __maybe_unused, void *data __maybe_unused, struct completion *completion __maybe_unused, uv_work_t* req __maybe_unused, int status __maybe_unused) {
+ __atomic_store_n(&ctx->atomic.now_deleting_files, false, __ATOMIC_RELAXED);
+}
- wc->cleanup_thread_deleting_files = 0;
- rrdcontext_db_rotation();
+struct uuid_first_time_s {
+ uuid_t *uuid;
+ time_t first_time_s;
+ METRIC *metric;
+ size_t pages_found;
+ size_t df_matched;
+ size_t df_index_oldest;
+};
- /* interrupt event loop */
- uv_stop(wc->loop);
+static int journal_metric_compare(const void *key, const void *metric)
+{
+ return uuid_compare(*(uuid_t *) key, ((struct journal_metric_list *) metric)->uuid);
}
-static void delete_old_data(void *arg)
-{
- struct rrdengine_instance *ctx = arg;
- struct rrdengine_worker_config* wc = &ctx->worker_config;
- struct rrdengine_datafile *datafile;
- struct extent_info *extent, *next;
- struct rrdeng_page_descr *descr;
- unsigned count, i;
- uint8_t can_delete_metric;
- uuid_t metric_id;
-
- /* Safe to use since it will be deleted after we are done */
- datafile = ctx->datafiles.first;
-
- for (extent = datafile->extents.first ; extent != NULL ; extent = next) {
- count = extent->number_of_pages;
- for (i = 0 ; i < count ; ++i) {
- descr = extent->pages[i];
- can_delete_metric = pg_cache_punch_hole(ctx, descr, 0, 0, &metric_id);
- if (unlikely(can_delete_metric)) {
- /*
- * If the metric is empty, has no active writers and if the metadata log has been initialized then
- * attempt to delete the corresponding netdata dimension.
- */
- metaqueue_delete_dimension_uuid(&metric_id);
- }
- }
- next = extent->next;
- freez(extent);
- }
- wc->cleanup_thread_deleting_files = 1;
- /* wake up event loop */
- fatal_assert(0 == uv_async_send(&wc->async));
+struct rrdengine_datafile *datafile_release_and_acquire_next_for_retention(struct rrdengine_instance *ctx, struct rrdengine_datafile *datafile) {
+
+ uv_rwlock_rdlock(&ctx->datafiles.rwlock);
+
+ struct rrdengine_datafile *next_datafile = datafile->next;
+
+ while(next_datafile && !datafile_acquire(next_datafile, DATAFILE_ACQUIRE_RETENTION))
+ next_datafile = next_datafile->next;
+
+ uv_rwlock_rdunlock(&ctx->datafiles.rwlock);
+
+ datafile_release(datafile, DATAFILE_ACQUIRE_RETENTION);
+
+ return next_datafile;
}
-void rrdeng_test_quota(struct rrdengine_worker_config* wc)
+void find_uuid_first_time(
+ struct rrdengine_instance *ctx,
+ struct rrdengine_datafile *datafile,
+ struct uuid_first_time_s *uuid_first_entry_list,
+ size_t count)
{
- struct rrdengine_instance *ctx = wc->ctx;
- struct rrdengine_datafile *datafile;
- unsigned current_size, target_size;
- uint8_t out_of_space, only_one_datafile;
- int ret, error;
-
- out_of_space = 0;
- /* Do not allow the pinned pages to exceed the disk space quota to avoid deadlocks */
- if (unlikely(ctx->disk_space > MAX(ctx->max_disk_space, 2 * ctx->metric_API_max_producers * RRDENG_BLOCK_SIZE))) {
- out_of_space = 1;
- }
- datafile = ctx->datafiles.last;
- current_size = datafile->pos;
- target_size = ctx->max_disk_space / TARGET_DATAFILES;
- target_size = MIN(target_size, MAX_DATAFILE_SIZE);
- target_size = MAX(target_size, MIN_DATAFILE_SIZE);
- only_one_datafile = (datafile == ctx->datafiles.first) ? 1 : 0;
- if (unlikely(current_size >= target_size || (out_of_space && only_one_datafile))) {
- /* Finalize data and journal file and create a new pair */
- wal_flush_transaction_buffer(wc);
- ret = create_new_datafile_pair(ctx, 1, ctx->last_fileno + 1);
- if (likely(!ret)) {
- ++ctx->last_fileno;
+ // acquire the datafile to work with it
+ uv_rwlock_rdlock(&ctx->datafiles.rwlock);
+ while(datafile && !datafile_acquire(datafile, DATAFILE_ACQUIRE_RETENTION))
+ datafile = datafile->next;
+ uv_rwlock_rdunlock(&ctx->datafiles.rwlock);
+
+ if (unlikely(!datafile))
+ return;
+
+ unsigned journalfile_count = 0;
+ size_t binary_match = 0;
+ size_t not_matching_bsearches = 0;
+
+ while (datafile) {
+ struct journal_v2_header *j2_header = journalfile_v2_data_acquire(datafile->journalfile, NULL, 0, 0);
+ if (!j2_header) {
+ datafile = datafile_release_and_acquire_next_for_retention(ctx, datafile);
+ continue;
+ }
+
+ time_t journal_start_time_s = (time_t) (j2_header->start_time_ut / USEC_PER_SEC);
+ struct journal_metric_list *uuid_list = (struct journal_metric_list *)((uint8_t *) j2_header + j2_header->metric_offset);
+ struct uuid_first_time_s *uuid_original_entry;
+
+ size_t journal_metric_count = j2_header->metric_count;
+
+ for (size_t index = 0; index < count; ++index) {
+ uuid_original_entry = &uuid_first_entry_list[index];
+
+ // Check here if we should skip this
+ if (uuid_original_entry->df_matched > 3 || uuid_original_entry->pages_found > 5)
+ continue;
+
+ struct journal_metric_list *live_entry = bsearch(uuid_original_entry->uuid,uuid_list,journal_metric_count,sizeof(*uuid_list), journal_metric_compare);
+ if (!live_entry) {
+ // Not found in this journal
+ not_matching_bsearches++;
+ continue;
+ }
+
+ uuid_original_entry->pages_found += live_entry->entries;
+ uuid_original_entry->df_matched++;
+
+ time_t old_first_time_s = uuid_original_entry->first_time_s;
+
+ // Calculate first / last for this match
+ time_t first_time_s = live_entry->delta_start_s + journal_start_time_s;
+ uuid_original_entry->first_time_s = MIN(uuid_original_entry->first_time_s, first_time_s);
+
+ if (uuid_original_entry->first_time_s != old_first_time_s)
+ uuid_original_entry->df_index_oldest = uuid_original_entry->df_matched;
+
+ binary_match++;
}
+
+ journalfile_count++;
+ journalfile_v2_data_release(datafile->journalfile);
+ datafile = datafile_release_and_acquire_next_for_retention(ctx, datafile);
}
- if (unlikely(out_of_space && NO_QUIESCE == ctx->quiesce)) {
- /* delete old data */
- if (wc->now_deleting_files) {
- /* already deleting data */
- return;
+
+ // Let's scan the open cache for almost exact match
+ size_t open_cache_count = 0;
+
+ size_t df_index[10] = { 0 };
+ size_t without_metric = 0;
+ size_t open_cache_gave_first_time_s = 0;
+ size_t metric_count = 0;
+ size_t without_retention = 0;
+ size_t not_needed_bsearches = 0;
+
+ for (size_t index = 0; index < count; ++index) {
+ struct uuid_first_time_s *uuid_first_t_entry = &uuid_first_entry_list[index];
+
+ metric_count++;
+
+ size_t idx = uuid_first_t_entry->df_index_oldest;
+ if(idx >= 10)
+ idx = 9;
+
+ df_index[idx]++;
+
+ not_needed_bsearches += uuid_first_t_entry->df_matched - uuid_first_t_entry->df_index_oldest;
+
+ if (unlikely(!uuid_first_t_entry->metric)) {
+ without_metric++;
+ continue;
}
- if (NULL == ctx->datafiles.first->next) {
- error("Cannot delete data file \"%s/"DATAFILE_PREFIX RRDENG_FILE_NUMBER_PRINT_TMPL DATAFILE_EXTENSION"\""
- " to reclaim space, there are no other file pairs left.",
- ctx->dbfiles_path, ctx->datafiles.first->tier, ctx->datafiles.first->fileno);
- return;
+
+ PGC_PAGE *page = pgc_page_get_and_acquire(
+ open_cache, (Word_t)ctx,
+ (Word_t)uuid_first_t_entry->metric, 0,
+ PGC_SEARCH_FIRST);
+
+ if (page) {
+ time_t old_first_time_s = uuid_first_t_entry->first_time_s;
+
+ time_t first_time_s = pgc_page_start_time_s(page);
+ uuid_first_t_entry->first_time_s = MIN(uuid_first_t_entry->first_time_s, first_time_s);
+ pgc_page_release(open_cache, page);
+ open_cache_count++;
+
+ if(uuid_first_t_entry->first_time_s != old_first_time_s) {
+ open_cache_gave_first_time_s++;
+ }
}
- info("Deleting data file \"%s/"DATAFILE_PREFIX RRDENG_FILE_NUMBER_PRINT_TMPL DATAFILE_EXTENSION"\".",
- ctx->dbfiles_path, ctx->datafiles.first->tier, ctx->datafiles.first->fileno);
- wc->now_deleting_files = mallocz(sizeof(*wc->now_deleting_files));
- wc->cleanup_thread_deleting_files = 0;
-
- error = uv_thread_create(wc->now_deleting_files, delete_old_data, ctx);
- if (error) {
- error("uv_thread_create(): %s", uv_strerror(error));
- freez(wc->now_deleting_files);
- wc->now_deleting_files = NULL;
+ else {
+ if(!uuid_first_t_entry->df_index_oldest)
+ without_retention++;
}
}
+ internal_error(true,
+ "DBENGINE: analyzed the retention of %zu rotated metrics of tier %d, "
+ "did %zu jv2 matching binary searches (%zu not matching, %zu overflown) in %u journal files, "
+ "%zu metrics with entries in open cache, "
+ "metrics first time found per datafile index ([not in jv2]:%zu, [1]:%zu, [2]:%zu, [3]:%zu, [4]:%zu, [5]:%zu, [6]:%zu, [7]:%zu, [8]:%zu, [bigger]: %zu), "
+ "open cache found first time %zu, "
+ "metrics without any remaining retention %zu, "
+ "metrics not in MRG %zu",
+ metric_count,
+ ctx->config.tier,
+ binary_match,
+ not_matching_bsearches,
+ not_needed_bsearches,
+ journalfile_count,
+ open_cache_count,
+ df_index[0], df_index[1], df_index[2], df_index[3], df_index[4], df_index[5], df_index[6], df_index[7], df_index[8], df_index[9],
+ open_cache_gave_first_time_s,
+ without_retention,
+ without_metric
+ );
}
-static inline int rrdeng_threads_alive(struct rrdengine_worker_config* wc)
-{
- if (wc->now_invalidating_dirty_pages || wc->now_deleting_files) {
- return 1;
+static void update_metrics_first_time_s(struct rrdengine_instance *ctx, struct rrdengine_datafile *datafile_to_delete, struct rrdengine_datafile *first_datafile_remaining, bool worker) {
+ __atomic_add_fetch(&rrdeng_cache_efficiency_stats.metrics_retention_started, 1, __ATOMIC_RELAXED);
+
+ if(worker)
+ worker_is_busy(UV_EVENT_DBENGINE_FIND_ROTATED_METRICS);
+
+ struct rrdengine_journalfile *journalfile = datafile_to_delete->journalfile;
+ struct journal_v2_header *j2_header = journalfile_v2_data_acquire(journalfile, NULL, 0, 0);
+ struct journal_metric_list *uuid_list = (struct journal_metric_list *)((uint8_t *) j2_header + j2_header->metric_offset);
+
+ size_t count = j2_header->metric_count;
+ struct uuid_first_time_s *uuid_first_t_entry;
+ struct uuid_first_time_s *uuid_first_entry_list = callocz(count, sizeof(struct uuid_first_time_s));
+
+ size_t added = 0;
+ for (size_t index = 0; index < count; ++index) {
+ METRIC *metric = mrg_metric_get_and_acquire(main_mrg, &uuid_list[index].uuid, (Word_t) ctx);
+ if (!metric)
+ continue;
+
+ uuid_first_entry_list[added].metric = metric;
+ uuid_first_entry_list[added].first_time_s = LONG_MAX;
+ uuid_first_entry_list[added].df_matched = 0;
+ uuid_first_entry_list[added].df_index_oldest = 0;
+ uuid_first_entry_list[added].uuid = mrg_metric_uuid(main_mrg, metric);
+ added++;
+ }
+
+ info("DBENGINE: recalculating tier %d retention for %zu metrics starting with datafile %u",
+ ctx->config.tier, count, first_datafile_remaining->fileno);
+
+ journalfile_v2_data_release(journalfile);
+
+ // Update the first time / last time for all metrics we plan to delete
+
+ if(worker)
+ worker_is_busy(UV_EVENT_DBENGINE_FIND_REMAINING_RETENTION);
+
+ find_uuid_first_time(ctx, first_datafile_remaining, uuid_first_entry_list, added);
+
+ if(worker)
+ worker_is_busy(UV_EVENT_DBENGINE_POPULATE_MRG);
+
+ info("DBENGINE: updating tier %d metrics registry retention for %zu metrics",
+ ctx->config.tier, added);
+
+ size_t deleted_metrics = 0, zero_retention_referenced = 0, zero_disk_retention = 0, zero_disk_but_live = 0;
+ for (size_t index = 0; index < added; ++index) {
+ uuid_first_t_entry = &uuid_first_entry_list[index];
+ if (likely(uuid_first_t_entry->first_time_s != LONG_MAX)) {
+ mrg_metric_set_first_time_s_if_bigger(main_mrg, uuid_first_t_entry->metric, uuid_first_t_entry->first_time_s);
+ mrg_metric_release(main_mrg, uuid_first_t_entry->metric);
+ }
+ else {
+ zero_disk_retention++;
+
+ // there is no retention for this metric
+ bool has_retention = mrg_metric_zero_disk_retention(main_mrg, uuid_first_t_entry->metric);
+ if (!has_retention) {
+ bool deleted = mrg_metric_release_and_delete(main_mrg, uuid_first_t_entry->metric);
+ if(deleted)
+ deleted_metrics++;
+ else
+ zero_retention_referenced++;
+ }
+ else {
+ zero_disk_but_live++;
+ mrg_metric_release(main_mrg, uuid_first_t_entry->metric);
+ }
+ }
}
- return 0;
+ freez(uuid_first_entry_list);
+
+ internal_error(zero_disk_retention,
+ "DBENGINE: deleted %zu metrics, zero retention but referenced %zu (out of %zu total, of which %zu have main cache retention) zero on-disk retention tier %d metrics from metrics registry",
+ deleted_metrics, zero_retention_referenced, zero_disk_retention, zero_disk_but_live, ctx->config.tier);
+
+ if(worker)
+ worker_is_idle();
}
-static void rrdeng_cleanup_finished_threads(struct rrdengine_worker_config* wc)
-{
- struct rrdengine_instance *ctx = wc->ctx;
+void datafile_delete(struct rrdengine_instance *ctx, struct rrdengine_datafile *datafile, bool update_retention, bool worker) {
+ if(worker)
+ worker_is_busy(UV_EVENT_DBENGINE_DATAFILE_DELETE_WAIT);
+
+ bool datafile_got_for_deletion = datafile_acquire_for_deletion(datafile);
+
+ if (update_retention)
+ update_metrics_first_time_s(ctx, datafile, datafile->next, worker);
- if (unlikely(wc->cleanup_thread_invalidating_dirty_pages)) {
- after_invalidate_oldest_committed(wc);
+ while (!datafile_got_for_deletion) {
+ if(worker)
+ worker_is_busy(UV_EVENT_DBENGINE_DATAFILE_DELETE_WAIT);
+
+ datafile_got_for_deletion = datafile_acquire_for_deletion(datafile);
+
+ if (!datafile_got_for_deletion) {
+ info("DBENGINE: waiting for data file '%s/"
+ DATAFILE_PREFIX RRDENG_FILE_NUMBER_PRINT_TMPL DATAFILE_EXTENSION
+ "' to be available for deletion, "
+ "it is in use currently by %u users.",
+ ctx->config.dbfiles_path, ctx->datafiles.first->tier, ctx->datafiles.first->fileno, datafile->users.lockers);
+
+ __atomic_add_fetch(&rrdeng_cache_efficiency_stats.datafile_deletion_spin, 1, __ATOMIC_RELAXED);
+ sleep_usec(1 * USEC_PER_SEC);
+ }
}
- if (unlikely(wc->cleanup_thread_deleting_files)) {
- after_delete_old_data(wc);
+
+ __atomic_add_fetch(&rrdeng_cache_efficiency_stats.datafile_deletion_started, 1, __ATOMIC_RELAXED);
+ info("DBENGINE: deleting data file '%s/"
+ DATAFILE_PREFIX RRDENG_FILE_NUMBER_PRINT_TMPL DATAFILE_EXTENSION
+ "'.",
+ ctx->config.dbfiles_path, ctx->datafiles.first->tier, ctx->datafiles.first->fileno);
+
+ if(worker)
+ worker_is_busy(UV_EVENT_DBENGINE_DATAFILE_DELETE);
+
+ struct rrdengine_journalfile *journal_file;
+ unsigned deleted_bytes, journal_file_bytes, datafile_bytes;
+ int ret;
+ char path[RRDENG_PATH_MAX];
+
+ uv_rwlock_wrlock(&ctx->datafiles.rwlock);
+ datafile_list_delete_unsafe(ctx, datafile);
+ uv_rwlock_wrunlock(&ctx->datafiles.rwlock);
+
+ journal_file = datafile->journalfile;
+ datafile_bytes = datafile->pos;
+ journal_file_bytes = journalfile_current_size(journal_file);
+ deleted_bytes = journalfile_v2_data_size_get(journal_file);
+
+ info("DBENGINE: deleting data and journal files to maintain disk quota");
+ ret = journalfile_destroy_unsafe(journal_file, datafile);
+ if (!ret) {
+ journalfile_v1_generate_path(datafile, path, sizeof(path));
+ info("DBENGINE: deleted journal file \"%s\".", path);
+ journalfile_v2_generate_path(datafile, path, sizeof(path));
+ info("DBENGINE: deleted journal file \"%s\".", path);
+ deleted_bytes += journal_file_bytes;
}
- if (unlikely(SET_QUIESCE == ctx->quiesce && !rrdeng_threads_alive(wc))) {
- ctx->quiesce = QUIESCED;
- completion_mark_complete(&ctx->rrdengine_completion);
+ ret = destroy_data_file_unsafe(datafile);
+ if (!ret) {
+ generate_datafilepath(datafile, path, sizeof(path));
+ info("DBENGINE: deleted data file \"%s\".", path);
+ deleted_bytes += datafile_bytes;
}
+ freez(journal_file);
+ freez(datafile);
+
+ ctx_current_disk_space_decrease(ctx, deleted_bytes);
+ info("DBENGINE: reclaimed %u bytes of disk space.", deleted_bytes);
}
-/* return 0 on success */
-int init_rrd_files(struct rrdengine_instance *ctx)
-{
- int ret = init_data_files(ctx);
-
- BUFFER *wb = buffer_create(1000);
- size_t all_errors = 0;
- usec_t now = now_realtime_usec();
-
- if(ctx->load_errors[LOAD_ERRORS_PAGE_FLIPPED_TIME].counter) {
- buffer_sprintf(wb, "%s%zu pages had start time > end time (latest: %llu secs ago)"
- , (all_errors)?", ":""
- , ctx->load_errors[LOAD_ERRORS_PAGE_FLIPPED_TIME].counter
- , (now - ctx->load_errors[LOAD_ERRORS_PAGE_FLIPPED_TIME].latest_end_time_ut) / USEC_PER_SEC
- );
- all_errors += ctx->load_errors[LOAD_ERRORS_PAGE_FLIPPED_TIME].counter;
- }
+static void *database_rotate_tp_worker(struct rrdengine_instance *ctx __maybe_unused, void *data __maybe_unused, struct completion *completion __maybe_unused, uv_work_t *uv_work_req __maybe_unused) {
+ datafile_delete(ctx, ctx->datafiles.first, ctx_is_available_for_queries(ctx), true);
- if(ctx->load_errors[LOAD_ERRORS_PAGE_EQUAL_TIME].counter) {
- buffer_sprintf(wb, "%s%zu pages had start time = end time with more than 1 entries (latest: %llu secs ago)"
- , (all_errors)?", ":""
- , ctx->load_errors[LOAD_ERRORS_PAGE_EQUAL_TIME].counter
- , (now - ctx->load_errors[LOAD_ERRORS_PAGE_EQUAL_TIME].latest_end_time_ut) / USEC_PER_SEC
- );
- all_errors += ctx->load_errors[LOAD_ERRORS_PAGE_EQUAL_TIME].counter;
- }
+ if (rrdeng_ctx_exceeded_disk_quota(ctx))
+ rrdeng_enq_cmd(ctx, RRDENG_OPCODE_DATABASE_ROTATE, NULL, NULL, STORAGE_PRIORITY_INTERNAL_DBENGINE, NULL, NULL);
- if(ctx->load_errors[LOAD_ERRORS_PAGE_ZERO_ENTRIES].counter) {
- buffer_sprintf(wb, "%s%zu pages had zero points (latest: %llu secs ago)"
- , (all_errors)?", ":""
- , ctx->load_errors[LOAD_ERRORS_PAGE_ZERO_ENTRIES].counter
- , (now - ctx->load_errors[LOAD_ERRORS_PAGE_ZERO_ENTRIES].latest_end_time_ut) / USEC_PER_SEC
- );
- all_errors += ctx->load_errors[LOAD_ERRORS_PAGE_ZERO_ENTRIES].counter;
- }
+ rrdcontext_db_rotation();
- if(ctx->load_errors[LOAD_ERRORS_PAGE_UPDATE_ZERO].counter) {
- buffer_sprintf(wb, "%s%zu pages had update every == 0 with entries > 1 (latest: %llu secs ago)"
- , (all_errors)?", ":""
- , ctx->load_errors[LOAD_ERRORS_PAGE_UPDATE_ZERO].counter
- , (now - ctx->load_errors[LOAD_ERRORS_PAGE_UPDATE_ZERO].latest_end_time_ut) / USEC_PER_SEC
- );
- all_errors += ctx->load_errors[LOAD_ERRORS_PAGE_UPDATE_ZERO].counter;
- }
+ return data;
+}
- if(ctx->load_errors[LOAD_ERRORS_PAGE_FLEXY_TIME].counter) {
- buffer_sprintf(wb, "%s%zu pages had a different number of points compared to their timestamps (latest: %llu secs ago; these page have been loaded)"
- , (all_errors)?", ":""
- , ctx->load_errors[LOAD_ERRORS_PAGE_FLEXY_TIME].counter
- , (now - ctx->load_errors[LOAD_ERRORS_PAGE_FLEXY_TIME].latest_end_time_ut) / USEC_PER_SEC
- );
- all_errors += ctx->load_errors[LOAD_ERRORS_PAGE_FLEXY_TIME].counter;
- }
+static void after_flush_all_hot_and_dirty_pages_of_section(struct rrdengine_instance *ctx __maybe_unused, void *data __maybe_unused, struct completion *completion __maybe_unused, uv_work_t* req __maybe_unused, int status __maybe_unused) {
+ ;
+}
+
+static void *flush_all_hot_and_dirty_pages_of_section_tp_worker(struct rrdengine_instance *ctx __maybe_unused, void *data __maybe_unused, struct completion *completion __maybe_unused, uv_work_t *uv_work_req __maybe_unused) {
+ worker_is_busy(UV_EVENT_DBENGINE_QUIESCE);
+ pgc_flush_all_hot_and_dirty_pages(main_cache, (Word_t)ctx);
+ completion_mark_complete(&ctx->quiesce.completion);
+ return data;
+}
+
+static void after_populate_mrg(struct rrdengine_instance *ctx __maybe_unused, void *data __maybe_unused, struct completion *completion __maybe_unused, uv_work_t* req __maybe_unused, int status __maybe_unused) {
+ ;
+}
+
+static void *populate_mrg_tp_worker(struct rrdengine_instance *ctx __maybe_unused, void *data __maybe_unused, struct completion *completion __maybe_unused, uv_work_t *uv_work_req __maybe_unused) {
+ worker_is_busy(UV_EVENT_DBENGINE_POPULATE_MRG);
+
+ do {
+ struct rrdengine_datafile *datafile = NULL;
+
+ // find a datafile to work
+ uv_rwlock_rdlock(&ctx->datafiles.rwlock);
+ for(datafile = ctx->datafiles.first; datafile ; datafile = datafile->next) {
+ if(!netdata_spinlock_trylock(&datafile->populate_mrg.spinlock))
+ continue;
+
+ if(datafile->populate_mrg.populated) {
+ netdata_spinlock_unlock(&datafile->populate_mrg.spinlock);
+ continue;
+ }
+
+ // we have the spinlock and it is not populated
+ break;
+ }
+ uv_rwlock_rdunlock(&ctx->datafiles.rwlock);
+
+ if(!datafile)
+ break;
+
+ journalfile_v2_populate_retention_to_mrg(ctx, datafile->journalfile);
+ datafile->populate_mrg.populated = true;
+ netdata_spinlock_unlock(&datafile->populate_mrg.spinlock);
- if(ctx->load_errors[LOAD_ERRORS_DROPPED_EXTENT].counter) {
- buffer_sprintf(wb, "%s%zu extents have been dropped because they didn't have any valid pages"
- , (all_errors)?", ":""
- , ctx->load_errors[LOAD_ERRORS_DROPPED_EXTENT].counter
- );
- all_errors += ctx->load_errors[LOAD_ERRORS_DROPPED_EXTENT].counter;
+ } while(1);
+
+ completion_mark_complete(completion);
+
+ return data;
+}
+
+static void after_ctx_shutdown(struct rrdengine_instance *ctx __maybe_unused, void *data __maybe_unused, struct completion *completion __maybe_unused, uv_work_t* req __maybe_unused, int status __maybe_unused) {
+ ;
+}
+
+static void *ctx_shutdown_tp_worker(struct rrdengine_instance *ctx __maybe_unused, void *data __maybe_unused, struct completion *completion __maybe_unused, uv_work_t *uv_work_req __maybe_unused) {
+ worker_is_busy(UV_EVENT_DBENGINE_SHUTDOWN);
+
+ completion_wait_for(&ctx->quiesce.completion);
+ completion_destroy(&ctx->quiesce.completion);
+
+ bool logged = false;
+ while(__atomic_load_n(&ctx->atomic.extents_currently_being_flushed, __ATOMIC_RELAXED) ||
+ __atomic_load_n(&ctx->atomic.inflight_queries, __ATOMIC_RELAXED)) {
+ if(!logged) {
+ logged = true;
+ info("DBENGINE: waiting for %zu inflight queries to finish to shutdown tier %d...",
+ __atomic_load_n(&ctx->atomic.inflight_queries, __ATOMIC_RELAXED),
+ (ctx->config.legacy) ? -1 : ctx->config.tier);
+ }
+ sleep_usec(1 * USEC_PER_MS);
}
- if(all_errors)
- info("DBENGINE: tier %d: %s", ctx->tier, buffer_tostring(wb));
+ completion_mark_complete(completion);
- buffer_free(wb);
- return ret;
+ return data;
}
-void finalize_rrd_files(struct rrdengine_instance *ctx)
-{
- return finalize_data_files(ctx);
+static void *cache_flush_tp_worker(struct rrdengine_instance *ctx __maybe_unused, void *data __maybe_unused, struct completion *completion __maybe_unused, uv_work_t *uv_work_req __maybe_unused) {
+ if (!main_cache)
+ return data;
+
+ worker_is_busy(UV_EVENT_DBENGINE_FLUSH_MAIN_CACHE);
+ pgc_flush_pages(main_cache, 0);
+
+ return data;
}
-void rrdeng_init_cmd_queue(struct rrdengine_worker_config* wc)
-{
- wc->cmd_queue.head = wc->cmd_queue.tail = 0;
- wc->queue_size = 0;
- fatal_assert(0 == uv_cond_init(&wc->cmd_cond));
- fatal_assert(0 == uv_mutex_init(&wc->cmd_mutex));
+static void *cache_evict_tp_worker(struct rrdengine_instance *ctx __maybe_unused, void *data __maybe_unused, struct completion *completion __maybe_unused, uv_work_t *req __maybe_unused) {
+ if (!main_cache)
+ return data;
+
+ worker_is_busy(UV_EVENT_DBENGINE_EVICT_MAIN_CACHE);
+ pgc_evict_pages(main_cache, 0, 0);
+
+ return data;
}
-void rrdeng_enq_cmd(struct rrdengine_worker_config* wc, struct rrdeng_cmd *cmd)
-{
- unsigned queue_size;
+static void after_prep_query(struct rrdengine_instance *ctx __maybe_unused, void *data __maybe_unused, struct completion *completion __maybe_unused, uv_work_t* req __maybe_unused, int status __maybe_unused) {
+ ;
+}
- /* wait for free space in queue */
- uv_mutex_lock(&wc->cmd_mutex);
- while ((queue_size = wc->queue_size) == RRDENG_CMD_Q_MAX_SIZE) {
- uv_cond_wait(&wc->cmd_cond, &wc->cmd_mutex);
- }
- fatal_assert(queue_size < RRDENG_CMD_Q_MAX_SIZE);
- /* enqueue command */
- wc->cmd_queue.cmd_array[wc->cmd_queue.tail] = *cmd;
- wc->cmd_queue.tail = wc->cmd_queue.tail != RRDENG_CMD_Q_MAX_SIZE - 1 ?
- wc->cmd_queue.tail + 1 : 0;
- wc->queue_size = queue_size + 1;
- uv_mutex_unlock(&wc->cmd_mutex);
+static void *query_prep_tp_worker(struct rrdengine_instance *ctx __maybe_unused, void *data __maybe_unused, struct completion *completion __maybe_unused, uv_work_t *req __maybe_unused) {
+ worker_is_busy(UV_EVENT_DBENGINE_QUERY);
+ PDC *pdc = data;
+ rrdeng_prep_query(pdc);
+ return data;
+}
- /* wake up event loop */
- fatal_assert(0 == uv_async_send(&wc->async));
+unsigned rrdeng_target_data_file_size(struct rrdengine_instance *ctx) {
+ unsigned target_size = ctx->config.max_disk_space / TARGET_DATAFILES;
+ target_size = MIN(target_size, MAX_DATAFILE_SIZE);
+ target_size = MAX(target_size, MIN_DATAFILE_SIZE);
+ return target_size;
}
-struct rrdeng_cmd rrdeng_deq_cmd(struct rrdengine_worker_config* wc)
+bool rrdeng_ctx_exceeded_disk_quota(struct rrdengine_instance *ctx)
{
- struct rrdeng_cmd ret;
- unsigned queue_size;
-
- uv_mutex_lock(&wc->cmd_mutex);
- queue_size = wc->queue_size;
- if (queue_size == 0) {
- ret.opcode = RRDENG_NOOP;
- } else {
- /* dequeue command */
- ret = wc->cmd_queue.cmd_array[wc->cmd_queue.head];
- if (queue_size == 1) {
- wc->cmd_queue.head = wc->cmd_queue.tail = 0;
- } else {
- wc->cmd_queue.head = wc->cmd_queue.head != RRDENG_CMD_Q_MAX_SIZE - 1 ?
- wc->cmd_queue.head + 1 : 0;
- }
- wc->queue_size = queue_size - 1;
+ uint64_t estimated_disk_space = ctx_current_disk_space_get(ctx) + rrdeng_target_data_file_size(ctx) -
+ (ctx->datafiles.first->prev ? ctx->datafiles.first->prev->pos : 0);
- /* wake up producers */
- uv_cond_signal(&wc->cmd_cond);
- }
- uv_mutex_unlock(&wc->cmd_mutex);
+ return estimated_disk_space > ctx->config.max_disk_space;
+}
- return ret;
+/* return 0 on success */
+int init_rrd_files(struct rrdengine_instance *ctx)
+{
+ return init_data_files(ctx);
}
-static void load_configuration_dynamic(void)
+void finalize_rrd_files(struct rrdengine_instance *ctx)
{
- unsigned read_num = (unsigned)config_get_number(CONFIG_SECTION_DB, "dbengine pages per extent", MAX_PAGES_PER_EXTENT);
- if (read_num > 0 && read_num <= MAX_PAGES_PER_EXTENT)
- rrdeng_pages_per_extent = read_num;
- else {
- error("Invalid dbengine pages per extent %u given. Using %u.", read_num, rrdeng_pages_per_extent);
- config_set_number(CONFIG_SECTION_DB, "dbengine pages per extent", rrdeng_pages_per_extent);
- }
+ return finalize_data_files(ctx);
}
void async_cb(uv_async_t *handle)
@@ -1253,256 +1392,413 @@ void async_cb(uv_async_t *handle)
debug(D_RRDENGINE, "%s called, active=%d.", __func__, uv_is_active((uv_handle_t *)handle));
}
-/* Flushes dirty pages when timer expires */
#define TIMER_PERIOD_MS (1000)
-void timer_cb(uv_timer_t* handle)
-{
- worker_is_busy(RRDENG_MAX_OPCODE + 1);
- struct rrdengine_worker_config* wc = handle->data;
- struct rrdengine_instance *ctx = wc->ctx;
+static void *extent_read_tp_worker(struct rrdengine_instance *ctx __maybe_unused, void *data __maybe_unused, struct completion *completion __maybe_unused, uv_work_t *uv_work_req __maybe_unused) {
+ EPDL *epdl = data;
+ epdl_find_extent_and_populate_pages(ctx, epdl, true);
+ return data;
+}
- uv_stop(handle->loop);
- uv_update_time(handle->loop);
- rrdeng_test_quota(wc);
- debug(D_RRDENGINE, "%s: timeout reached.", __func__);
- if (likely(!wc->now_deleting_files && !wc->now_invalidating_dirty_pages)) {
- /* There is free space so we can write to disk and we are not actively deleting dirty buffers */
- struct page_cache *pg_cache = &ctx->pg_cache;
- unsigned long total_bytes, bytes_written, nr_committed_pages, bytes_to_write = 0, producers, low_watermark,
- high_watermark;
-
- uv_rwlock_rdlock(&pg_cache->committed_page_index.lock);
- nr_committed_pages = pg_cache->committed_page_index.nr_committed_pages;
- uv_rwlock_rdunlock(&pg_cache->committed_page_index.lock);
- producers = ctx->metric_API_max_producers;
- /* are flushable pages more than 25% of the maximum page cache size */
- high_watermark = (ctx->max_cache_pages * 25LLU) / 100;
- low_watermark = (ctx->max_cache_pages * 5LLU) / 100; /* 5%, must be smaller than high_watermark */
-
- /* Flush more pages only if disk can keep up */
- if (wc->inflight_dirty_pages < high_watermark + producers) {
- if (nr_committed_pages > producers &&
- /* committed to be written pages are more than the produced number */
- nr_committed_pages - producers > high_watermark) {
- /* Flushing speed must increase to stop page cache from filling with dirty pages */
- bytes_to_write = (nr_committed_pages - producers - low_watermark) * RRDENG_BLOCK_SIZE;
- }
- bytes_to_write = MAX(DATAFILE_IDEAL_IO_SIZE, bytes_to_write);
+static void epdl_populate_pages_asynchronously(struct rrdengine_instance *ctx, EPDL *epdl, STORAGE_PRIORITY priority) {
+ rrdeng_enq_cmd(ctx, RRDENG_OPCODE_EXTENT_READ, epdl, NULL, priority,
+ rrdeng_enqueue_epdl_cmd, rrdeng_dequeue_epdl_cmd);
+}
- debug(D_RRDENGINE, "Flushing pages to disk.");
- for (total_bytes = bytes_written = do_flush_pages(wc, 0, NULL);
- bytes_written && (total_bytes < bytes_to_write);
- total_bytes += bytes_written) {
- bytes_written = do_flush_pages(wc, 0, NULL);
- }
+void pdc_route_asynchronously(struct rrdengine_instance *ctx, struct page_details_control *pdc) {
+ pdc_to_epdl_router(ctx, pdc, epdl_populate_pages_asynchronously, epdl_populate_pages_asynchronously);
+}
+
+void epdl_populate_pages_synchronously(struct rrdengine_instance *ctx, EPDL *epdl, enum storage_priority priority __maybe_unused) {
+ epdl_find_extent_and_populate_pages(ctx, epdl, false);
+}
+
+void pdc_route_synchronously(struct rrdengine_instance *ctx, struct page_details_control *pdc) {
+ pdc_to_epdl_router(ctx, pdc, epdl_populate_pages_synchronously, epdl_populate_pages_synchronously);
+}
+
+#define MAX_RETRIES_TO_START_INDEX (100)
+static void *journal_v2_indexing_tp_worker(struct rrdengine_instance *ctx __maybe_unused, void *data __maybe_unused, struct completion *completion __maybe_unused, uv_work_t *uv_work_req __maybe_unused) {
+ unsigned count = 0;
+ worker_is_busy(UV_EVENT_DBENGINE_JOURNAL_INDEX_WAIT);
+
+ while (__atomic_load_n(&ctx->atomic.now_deleting_files, __ATOMIC_RELAXED) && count++ < MAX_RETRIES_TO_START_INDEX)
+ sleep_usec(100 * USEC_PER_MS);
+
+ if (count == MAX_RETRIES_TO_START_INDEX) {
+ worker_is_idle();
+ return data;
+ }
+
+ struct rrdengine_datafile *datafile = ctx->datafiles.first;
+ worker_is_busy(UV_EVENT_DBENGINE_JOURNAL_INDEX);
+ count = 0;
+ while (datafile && datafile->fileno != ctx_last_fileno_get(ctx) && datafile->fileno != ctx_last_flush_fileno_get(ctx)) {
+
+ netdata_spinlock_lock(&datafile->writers.spinlock);
+ bool available = (datafile->writers.running || datafile->writers.flushed_to_open_running) ? false : true;
+ netdata_spinlock_unlock(&datafile->writers.spinlock);
+
+ if(!available)
+ continue;
+
+ if (unlikely(!journalfile_v2_data_available(datafile->journalfile))) {
+ info("DBENGINE: journal file %u is ready to be indexed", datafile->fileno);
+ pgc_open_cache_to_journal_v2(open_cache, (Word_t) ctx, (int) datafile->fileno, ctx->config.page_type,
+ journalfile_migrate_to_v2_callback, (void *) datafile->journalfile);
+ count++;
}
+
+ datafile = datafile->next;
+
+ if (unlikely(!ctx_is_available_for_queries(ctx)))
+ break;
}
- load_configuration_dynamic();
-#ifdef NETDATA_INTERNAL_CHECKS
+
+ errno = 0;
+ internal_error(count, "DBENGINE: journal indexing done; %u files processed", count);
+
+ worker_is_idle();
+
+ return data;
+}
+
+static void after_do_cache_flush(struct rrdengine_instance *ctx __maybe_unused, void *data __maybe_unused, struct completion *completion __maybe_unused, uv_work_t* req __maybe_unused, int status __maybe_unused) {
+ rrdeng_main.flushes_running--;
+}
+
+static void after_do_cache_evict(struct rrdengine_instance *ctx __maybe_unused, void *data __maybe_unused, struct completion *completion __maybe_unused, uv_work_t* req __maybe_unused, int status __maybe_unused) {
+ rrdeng_main.evictions_running--;
+}
+
+static void after_extent_read(struct rrdengine_instance *ctx __maybe_unused, void *data __maybe_unused, struct completion *completion __maybe_unused, uv_work_t* req __maybe_unused, int status __maybe_unused) {
+ ;
+}
+
+static void after_journal_v2_indexing(struct rrdengine_instance *ctx __maybe_unused, void *data __maybe_unused, struct completion *completion __maybe_unused, uv_work_t* req __maybe_unused, int status __maybe_unused) {
+ __atomic_store_n(&ctx->atomic.migration_to_v2_running, false, __ATOMIC_RELAXED);
+ rrdeng_enq_cmd(ctx, RRDENG_OPCODE_DATABASE_ROTATE, NULL, NULL, STORAGE_PRIORITY_INTERNAL_DBENGINE, NULL, NULL);
+}
+
+struct rrdeng_buffer_sizes rrdeng_get_buffer_sizes(void) {
+ return (struct rrdeng_buffer_sizes) {
+ .pgc = pgc_aral_overhead() + pgc_aral_structures(),
+ .mrg = mrg_aral_overhead() + mrg_aral_structures(),
+ .opcodes = aral_overhead(rrdeng_main.cmd_queue.ar) + aral_structures(rrdeng_main.cmd_queue.ar),
+ .handles = aral_overhead(rrdeng_main.handles.ar) + aral_structures(rrdeng_main.handles.ar),
+ .descriptors = aral_overhead(rrdeng_main.descriptors.ar) + aral_structures(rrdeng_main.descriptors.ar),
+ .wal = __atomic_load_n(&wal_globals.atomics.allocated, __ATOMIC_RELAXED) * (sizeof(WAL) + RRDENG_BLOCK_SIZE),
+ .workers = aral_overhead(rrdeng_main.work_cmd.ar),
+ .pdc = pdc_cache_size(),
+ .xt_io = aral_overhead(rrdeng_main.xt_io_descr.ar) + aral_structures(rrdeng_main.xt_io_descr.ar),
+ .xt_buf = extent_buffer_cache_size(),
+ .epdl = epdl_cache_size(),
+ .deol = deol_cache_size(),
+ .pd = pd_cache_size(),
+
+#ifdef PDC_USE_JULYL
+ .julyl = julyl_cache_size(),
+#endif
+ };
+}
+
+static void after_cleanup(struct rrdengine_instance *ctx __maybe_unused, void *data __maybe_unused, struct completion *completion __maybe_unused, uv_work_t* req __maybe_unused, int status __maybe_unused) {
+ rrdeng_main.cleanup_running--;
+}
+
+static void *cleanup_tp_worker(struct rrdengine_instance *ctx __maybe_unused, void *data __maybe_unused, struct completion *completion __maybe_unused, uv_work_t *uv_work_req __maybe_unused) {
+ worker_is_busy(UV_EVENT_DBENGINE_BUFFERS_CLEANUP);
+
+ wal_cleanup1();
+ extent_buffer_cleanup1();
+
{
- char buf[4096];
- debug(D_RRDENGINE, "%s", get_rrdeng_statistics(wc->ctx, buf, sizeof(buf)));
+ static time_t last_run_s = 0;
+ time_t now_s = now_monotonic_sec();
+ if(now_s - last_run_s >= 10) {
+ last_run_s = now_s;
+ journalfile_v2_data_unmount_cleanup(now_s);
+ }
}
+
+#ifdef PDC_USE_JULYL
+ julyl_cleanup1();
#endif
+ return data;
+}
+
+void timer_cb(uv_timer_t* handle) {
+ worker_is_busy(RRDENG_TIMER_CB);
+ uv_stop(handle->loop);
+ uv_update_time(handle->loop);
+
+ worker_set_metric(RRDENG_OPCODES_WAITING, (NETDATA_DOUBLE)rrdeng_main.cmd_queue.unsafe.waiting);
+ worker_set_metric(RRDENG_WORKS_DISPATCHED, (NETDATA_DOUBLE)__atomic_load_n(&rrdeng_main.work_cmd.atomics.dispatched, __ATOMIC_RELAXED));
+ worker_set_metric(RRDENG_WORKS_EXECUTING, (NETDATA_DOUBLE)__atomic_load_n(&rrdeng_main.work_cmd.atomics.executing, __ATOMIC_RELAXED));
+
+ rrdeng_enq_cmd(NULL, RRDENG_OPCODE_FLUSH_INIT, NULL, NULL, STORAGE_PRIORITY_INTERNAL_DBENGINE, NULL, NULL);
+ rrdeng_enq_cmd(NULL, RRDENG_OPCODE_EVICT_INIT, NULL, NULL, STORAGE_PRIORITY_INTERNAL_DBENGINE, NULL, NULL);
+ rrdeng_enq_cmd(NULL, RRDENG_OPCODE_CLEANUP, NULL, NULL, STORAGE_PRIORITY_INTERNAL_DBENGINE, NULL, NULL);
+
worker_is_idle();
}
-#define MAX_CMD_BATCH_SIZE (256)
+static void dbengine_initialize_structures(void) {
+ pgc_and_mrg_initialize();
+
+ pdc_init();
+ page_details_init();
+ epdl_init();
+ deol_init();
+ rrdeng_cmd_queue_init();
+ work_request_init();
+ rrdeng_query_handle_init();
+ page_descriptors_init();
+ extent_buffer_init();
+ dbengine_page_alloc_init();
+ extent_io_descriptor_init();
+}
-void rrdeng_worker(void* arg)
-{
- worker_register("DBENGINE");
- worker_register_job_name(RRDENG_NOOP, "noop");
- worker_register_job_name(RRDENG_READ_PAGE, "page read");
- worker_register_job_name(RRDENG_READ_EXTENT, "extent read");
- worker_register_job_name(RRDENG_COMMIT_PAGE, "commit");
- worker_register_job_name(RRDENG_FLUSH_PAGES, "flush");
- worker_register_job_name(RRDENG_SHUTDOWN, "shutdown");
- worker_register_job_name(RRDENG_INVALIDATE_OLDEST_MEMORY_PAGE, "page lru");
- worker_register_job_name(RRDENG_QUIESCE, "quiesce");
- worker_register_job_name(RRDENG_MAX_OPCODE, "cleanup");
- worker_register_job_name(RRDENG_MAX_OPCODE + 1, "timer");
-
- struct rrdengine_worker_config* wc = arg;
- struct rrdengine_instance *ctx = wc->ctx;
- uv_loop_t* loop;
- int shutdown, ret;
- enum rrdeng_opcode opcode;
- uv_timer_t timer_req;
- struct rrdeng_cmd cmd;
- unsigned cmd_batch_size;
+bool rrdeng_dbengine_spawn(struct rrdengine_instance *ctx __maybe_unused) {
+ static bool spawned = false;
+ static SPINLOCK spinlock = NETDATA_SPINLOCK_INITIALIZER;
- rrdeng_init_cmd_queue(wc);
+ netdata_spinlock_lock(&spinlock);
- loop = wc->loop = mallocz(sizeof(uv_loop_t));
- ret = uv_loop_init(loop);
- if (ret) {
- error("uv_loop_init(): %s", uv_strerror(ret));
- goto error_after_loop_init;
- }
- loop->data = wc;
+ if(!spawned) {
+ int ret;
- ret = uv_async_init(wc->loop, &wc->async, async_cb);
- if (ret) {
- error("uv_async_init(): %s", uv_strerror(ret));
- goto error_after_async_init;
- }
- wc->async.data = wc;
+ ret = uv_loop_init(&rrdeng_main.loop);
+ if (ret) {
+ error("DBENGINE: uv_loop_init(): %s", uv_strerror(ret));
+ return false;
+ }
+ rrdeng_main.loop.data = &rrdeng_main;
- wc->now_deleting_files = NULL;
- wc->cleanup_thread_deleting_files = 0;
+ ret = uv_async_init(&rrdeng_main.loop, &rrdeng_main.async, async_cb);
+ if (ret) {
+ error("DBENGINE: uv_async_init(): %s", uv_strerror(ret));
+ fatal_assert(0 == uv_loop_close(&rrdeng_main.loop));
+ return false;
+ }
+ rrdeng_main.async.data = &rrdeng_main;
+
+ ret = uv_timer_init(&rrdeng_main.loop, &rrdeng_main.timer);
+ if (ret) {
+ error("DBENGINE: uv_timer_init(): %s", uv_strerror(ret));
+ uv_close((uv_handle_t *)&rrdeng_main.async, NULL);
+ fatal_assert(0 == uv_loop_close(&rrdeng_main.loop));
+ return false;
+ }
+ rrdeng_main.timer.data = &rrdeng_main;
- wc->now_invalidating_dirty_pages = NULL;
- wc->cleanup_thread_invalidating_dirty_pages = 0;
- wc->inflight_dirty_pages = 0;
+ dbengine_initialize_structures();
- /* dirty page flushing timer */
- ret = uv_timer_init(loop, &timer_req);
- if (ret) {
- error("uv_timer_init(): %s", uv_strerror(ret));
- goto error_after_timer_init;
+ fatal_assert(0 == uv_thread_create(&rrdeng_main.thread, dbengine_event_loop, &rrdeng_main));
+ spawned = true;
}
- timer_req.data = wc;
- wc->error = 0;
- /* wake up initialization thread */
- completion_mark_complete(&ctx->rrdengine_completion);
+ netdata_spinlock_unlock(&spinlock);
+ return true;
+}
- fatal_assert(0 == uv_timer_start(&timer_req, timer_cb, TIMER_PERIOD_MS, TIMER_PERIOD_MS));
- shutdown = 0;
- int set_name = 0;
- while (likely(shutdown == 0 || rrdeng_threads_alive(wc))) {
+void dbengine_event_loop(void* arg) {
+ sanity_check();
+ uv_thread_set_name_np(pthread_self(), "DBENGINE");
+ service_register(SERVICE_THREAD_TYPE_EVENT_LOOP, NULL, NULL, NULL, true);
+
+ worker_register("DBENGINE");
+
+ // opcode jobs
+ worker_register_job_name(RRDENG_OPCODE_NOOP, "noop");
+
+ worker_register_job_name(RRDENG_OPCODE_QUERY, "query");
+ worker_register_job_name(RRDENG_OPCODE_EXTENT_WRITE, "extent write");
+ worker_register_job_name(RRDENG_OPCODE_EXTENT_READ, "extent read");
+ worker_register_job_name(RRDENG_OPCODE_FLUSHED_TO_OPEN, "flushed to open");
+ worker_register_job_name(RRDENG_OPCODE_DATABASE_ROTATE, "db rotate");
+ worker_register_job_name(RRDENG_OPCODE_JOURNAL_INDEX, "journal index");
+ worker_register_job_name(RRDENG_OPCODE_FLUSH_INIT, "flush init");
+ worker_register_job_name(RRDENG_OPCODE_EVICT_INIT, "evict init");
+ worker_register_job_name(RRDENG_OPCODE_CTX_SHUTDOWN, "ctx shutdown");
+ worker_register_job_name(RRDENG_OPCODE_CTX_QUIESCE, "ctx quiesce");
+
+ worker_register_job_name(RRDENG_OPCODE_MAX, "get opcode");
+
+ worker_register_job_name(RRDENG_OPCODE_MAX + RRDENG_OPCODE_QUERY, "query cb");
+ worker_register_job_name(RRDENG_OPCODE_MAX + RRDENG_OPCODE_EXTENT_WRITE, "extent write cb");
+ worker_register_job_name(RRDENG_OPCODE_MAX + RRDENG_OPCODE_EXTENT_READ, "extent read cb");
+ worker_register_job_name(RRDENG_OPCODE_MAX + RRDENG_OPCODE_FLUSHED_TO_OPEN, "flushed to open cb");
+ worker_register_job_name(RRDENG_OPCODE_MAX + RRDENG_OPCODE_DATABASE_ROTATE, "db rotate cb");
+ worker_register_job_name(RRDENG_OPCODE_MAX + RRDENG_OPCODE_JOURNAL_INDEX, "journal index cb");
+ worker_register_job_name(RRDENG_OPCODE_MAX + RRDENG_OPCODE_FLUSH_INIT, "flush init cb");
+ worker_register_job_name(RRDENG_OPCODE_MAX + RRDENG_OPCODE_EVICT_INIT, "evict init cb");
+ worker_register_job_name(RRDENG_OPCODE_MAX + RRDENG_OPCODE_CTX_SHUTDOWN, "ctx shutdown cb");
+ worker_register_job_name(RRDENG_OPCODE_MAX + RRDENG_OPCODE_CTX_QUIESCE, "ctx quiesce cb");
+
+ // special jobs
+ worker_register_job_name(RRDENG_TIMER_CB, "timer");
+ worker_register_job_name(RRDENG_FLUSH_TRANSACTION_BUFFER_CB, "transaction buffer flush cb");
+
+ worker_register_job_custom_metric(RRDENG_OPCODES_WAITING, "opcodes waiting", "opcodes", WORKER_METRIC_ABSOLUTE);
+ worker_register_job_custom_metric(RRDENG_WORKS_DISPATCHED, "works dispatched", "works", WORKER_METRIC_ABSOLUTE);
+ worker_register_job_custom_metric(RRDENG_WORKS_EXECUTING, "works executing", "works", WORKER_METRIC_ABSOLUTE);
+
+ struct rrdeng_main *main = arg;
+ enum rrdeng_opcode opcode;
+ struct rrdeng_cmd cmd;
+ main->tid = gettid();
+
+ fatal_assert(0 == uv_timer_start(&main->timer, timer_cb, TIMER_PERIOD_MS, TIMER_PERIOD_MS));
+
+ bool shutdown = false;
+ while (likely(!shutdown)) {
worker_is_idle();
- uv_run(loop, UV_RUN_DEFAULT);
- worker_is_busy(RRDENG_MAX_OPCODE);
- rrdeng_cleanup_finished_threads(wc);
+ uv_run(&main->loop, UV_RUN_DEFAULT);
/* wait for commands */
- cmd_batch_size = 0;
do {
- /*
- * Avoid starving the loop when there are too many commands coming in.
- * timer_cb will interrupt the loop again to allow serving more commands.
- */
- if (unlikely(cmd_batch_size >= MAX_CMD_BATCH_SIZE))
- break;
-
- cmd = rrdeng_deq_cmd(wc);
+ worker_is_busy(RRDENG_OPCODE_MAX);
+ cmd = rrdeng_deq_cmd();
opcode = cmd.opcode;
- ++cmd_batch_size;
- if(likely(opcode != RRDENG_NOOP))
- worker_is_busy(opcode);
+ worker_is_busy(opcode);
switch (opcode) {
- case RRDENG_NOOP:
- /* the command queue was empty, do nothing */
- break;
- case RRDENG_SHUTDOWN:
- shutdown = 1;
- break;
- case RRDENG_QUIESCE:
- ctx->drop_metrics_under_page_cache_pressure = 0;
- ctx->quiesce = SET_QUIESCE;
- fatal_assert(0 == uv_timer_stop(&timer_req));
- uv_close((uv_handle_t *)&timer_req, NULL);
- while (do_flush_pages(wc, 1, NULL)) {
- ; /* Force flushing of all committed pages. */
+ case RRDENG_OPCODE_EXTENT_READ: {
+ struct rrdengine_instance *ctx = cmd.ctx;
+ EPDL *epdl = cmd.data;
+ work_dispatch(ctx, epdl, NULL, opcode, extent_read_tp_worker, after_extent_read);
+ break;
}
- wal_flush_transaction_buffer(wc);
- if (!rrdeng_threads_alive(wc)) {
- ctx->quiesce = QUIESCED;
- completion_mark_complete(&ctx->rrdengine_completion);
+
+ case RRDENG_OPCODE_QUERY: {
+ struct rrdengine_instance *ctx = cmd.ctx;
+ PDC *pdc = cmd.data;
+ work_dispatch(ctx, pdc, NULL, opcode, query_prep_tp_worker, after_prep_query);
+ break;
}
- break;
- case RRDENG_READ_PAGE:
- do_read_extent(wc, &cmd.read_page.page_cache_descr, 1, 0);
- break;
- case RRDENG_READ_EXTENT:
- do_read_extent(wc, cmd.read_extent.page_cache_descr, cmd.read_extent.page_count, 1);
- if (unlikely(!set_name)) {
- set_name = 1;
- uv_thread_set_name_np(ctx->worker_config.thread, "DBENGINE");
+
+ case RRDENG_OPCODE_EXTENT_WRITE: {
+ struct rrdengine_instance *ctx = cmd.ctx;
+ struct page_descr_with_data *base = cmd.data;
+ struct completion *completion = cmd.completion; // optional
+ work_dispatch(ctx, base, completion, opcode, extent_write_tp_worker, after_extent_write);
+ break;
}
- break;
- case RRDENG_COMMIT_PAGE:
- do_commit_transaction(wc, STORE_DATA, NULL);
- break;
- case RRDENG_FLUSH_PAGES: {
- if (wc->now_invalidating_dirty_pages) {
- /* Do not flush if the disk cannot keep up */
- completion_mark_complete(cmd.completion);
- } else {
- (void)do_flush_pages(wc, 1, cmd.completion);
+
+ case RRDENG_OPCODE_FLUSHED_TO_OPEN: {
+ struct rrdengine_instance *ctx = cmd.ctx;
+ uv_fs_t *uv_fs_request = cmd.data;
+ struct extent_io_descriptor *xt_io_descr = uv_fs_request->data;
+ struct completion *completion = xt_io_descr->completion;
+ work_dispatch(ctx, uv_fs_request, completion, opcode, extent_flushed_to_open_tp_worker, after_extent_flushed_to_open);
+ break;
+ }
+
+ case RRDENG_OPCODE_FLUSH_INIT: {
+ if(rrdeng_main.flushes_running < (size_t)(libuv_worker_threads / 4)) {
+ rrdeng_main.flushes_running++;
+ work_dispatch(NULL, NULL, NULL, opcode, cache_flush_tp_worker, after_do_cache_flush);
+ }
+ break;
+ }
+
+ case RRDENG_OPCODE_EVICT_INIT: {
+ if(!rrdeng_main.evictions_running) {
+ rrdeng_main.evictions_running++;
+ work_dispatch(NULL, NULL, NULL, opcode, cache_evict_tp_worker, after_do_cache_evict);
+ }
+ break;
+ }
+
+ case RRDENG_OPCODE_CLEANUP: {
+ if(!rrdeng_main.cleanup_running) {
+ rrdeng_main.cleanup_running++;
+ work_dispatch(NULL, NULL, NULL, opcode, cleanup_tp_worker, after_cleanup);
+ }
+ break;
+ }
+
+ case RRDENG_OPCODE_JOURNAL_INDEX: {
+ struct rrdengine_instance *ctx = cmd.ctx;
+ struct rrdengine_datafile *datafile = cmd.data;
+ if(!__atomic_load_n(&ctx->atomic.migration_to_v2_running, __ATOMIC_RELAXED)) {
+
+ __atomic_store_n(&ctx->atomic.migration_to_v2_running, true, __ATOMIC_RELAXED);
+ work_dispatch(ctx, datafile, NULL, opcode, journal_v2_indexing_tp_worker, after_journal_v2_indexing);
+ }
+ break;
+ }
+
+ case RRDENG_OPCODE_DATABASE_ROTATE: {
+ struct rrdengine_instance *ctx = cmd.ctx;
+ if (!__atomic_load_n(&ctx->atomic.now_deleting_files, __ATOMIC_RELAXED) &&
+ ctx->datafiles.first->next != NULL &&
+ ctx->datafiles.first->next->next != NULL &&
+ rrdeng_ctx_exceeded_disk_quota(ctx)) {
+
+ __atomic_store_n(&ctx->atomic.now_deleting_files, true, __ATOMIC_RELAXED);
+ work_dispatch(ctx, NULL, NULL, opcode, database_rotate_tp_worker, after_database_rotate);
+ }
+ break;
+ }
+
+ case RRDENG_OPCODE_CTX_POPULATE_MRG: {
+ struct rrdengine_instance *ctx = cmd.ctx;
+ struct completion *completion = cmd.completion;
+ work_dispatch(ctx, NULL, completion, opcode, populate_mrg_tp_worker, after_populate_mrg);
+ break;
+ }
+
+ case RRDENG_OPCODE_CTX_QUIESCE: {
+ // a ctx will shutdown shortly
+ struct rrdengine_instance *ctx = cmd.ctx;
+ __atomic_store_n(&ctx->quiesce.enabled, true, __ATOMIC_RELEASE);
+ work_dispatch(ctx, NULL, NULL, opcode,
+ flush_all_hot_and_dirty_pages_of_section_tp_worker,
+ after_flush_all_hot_and_dirty_pages_of_section);
+ break;
+ }
+
+ case RRDENG_OPCODE_CTX_SHUTDOWN: {
+ // a ctx is shutting down
+ struct rrdengine_instance *ctx = cmd.ctx;
+ struct completion *completion = cmd.completion;
+ work_dispatch(ctx, NULL, completion, opcode, ctx_shutdown_tp_worker, after_ctx_shutdown);
+ break;
+ }
+
+ case RRDENG_OPCODE_NOOP: {
+ /* the command queue was empty, do nothing */
+ break;
+ }
+
+ // not opcodes
+ case RRDENG_OPCODE_MAX:
+ default: {
+ internal_fatal(true, "DBENGINE: unknown opcode");
+ break;
}
- break;
- case RRDENG_INVALIDATE_OLDEST_MEMORY_PAGE:
- rrdeng_invalidate_oldest_committed(wc);
- break;
- }
- default:
- debug(D_RRDENGINE, "%s: default.", __func__);
- break;
}
- } while (opcode != RRDENG_NOOP);
+
+ } while (opcode != RRDENG_OPCODE_NOOP);
}
/* cleanup operations of the event loop */
- info("Shutting down RRD engine event loop for tier %d", ctx->tier);
+ info("DBENGINE: shutting down dbengine thread");
/*
* uv_async_send after uv_close does not seem to crash in linux at the moment,
* it is however undocumented behaviour and we need to be aware if this becomes
* an issue in the future.
*/
- uv_close((uv_handle_t *)&wc->async, NULL);
-
- while (do_flush_pages(wc, 1, NULL)) {
- ; /* Force flushing of all committed pages. */
- }
- wal_flush_transaction_buffer(wc);
- uv_run(loop, UV_RUN_DEFAULT);
-
- info("Shutting down RRD engine event loop for tier %d complete", ctx->tier);
- /* TODO: don't let the API block by waiting to enqueue commands */
- uv_cond_destroy(&wc->cmd_cond);
-/* uv_mutex_destroy(&wc->cmd_mutex); */
- fatal_assert(0 == uv_loop_close(loop));
- freez(loop);
-
+ uv_close((uv_handle_t *)&main->async, NULL);
+ uv_timer_stop(&main->timer);
+ uv_close((uv_handle_t *)&main->timer, NULL);
+ uv_run(&main->loop, UV_RUN_DEFAULT);
+ uv_loop_close(&main->loop);
worker_unregister();
- return;
-
-error_after_timer_init:
- uv_close((uv_handle_t *)&wc->async, NULL);
-error_after_async_init:
- fatal_assert(0 == uv_loop_close(loop));
-error_after_loop_init:
- freez(loop);
-
- wc->error = UV_EAGAIN;
- /* wake up initialization thread */
- completion_mark_complete(&ctx->rrdengine_completion);
- worker_unregister();
-}
-
-/* C entry point for development purposes
- * make "LDFLAGS=-errdengine_main"
- */
-void rrdengine_main(void)
-{
- int ret;
- struct rrdengine_instance *ctx;
-
- sanity_check();
- ret = rrdeng_init(NULL, &ctx, "/tmp", RRDENG_MIN_PAGE_CACHE_SIZE_MB, RRDENG_MIN_DISK_SPACE_MB, 0);
- if (ret) {
- exit(ret);
- }
- rrdeng_exit(ctx);
- fprintf(stderr, "Hello world!");
- exit(0);
}
diff --git a/database/engine/rrdengine.h b/database/engine/rrdengine.h
index 521d2521..49266681 100644
--- a/database/engine/rrdengine.h
+++ b/database/engine/rrdengine.h
@@ -19,202 +19,315 @@
#include "journalfile.h"
#include "rrdengineapi.h"
#include "pagecache.h"
-#include "rrdenglocking.h"
-
-#ifdef NETDATA_RRD_INTERNALS
-
-#endif /* NETDATA_RRD_INTERNALS */
+#include "metric.h"
+#include "cache.h"
+#include "pdc.h"
extern unsigned rrdeng_pages_per_extent;
/* Forward declarations */
struct rrdengine_instance;
+struct rrdeng_cmd;
#define MAX_PAGES_PER_EXTENT (64) /* TODO: can go higher only when journal supports bigger than 4KiB transactions */
#define RRDENG_FILE_NUMBER_SCAN_TMPL "%1u-%10u"
#define RRDENG_FILE_NUMBER_PRINT_TMPL "%1.1u-%10.10u"
+typedef struct page_details_control {
+ struct rrdengine_instance *ctx;
+ struct metric *metric;
+
+ struct completion prep_completion;
+ struct completion page_completion; // sync between the query thread and the workers
+
+ Pvoid_t page_list_JudyL; // the list of page details
+ unsigned completed_jobs; // the number of jobs completed last time the query thread checked
+ bool workers_should_stop; // true when the query thread left and the workers should stop
+ bool prep_done;
+
+ SPINLOCK refcount_spinlock; // spinlock to protect refcount
+ int32_t refcount; // the number of workers currently working on this request + 1 for the query thread
+ size_t executed_with_gaps;
+
+ time_t start_time_s;
+ time_t end_time_s;
+ STORAGE_PRIORITY priority;
+
+ time_t optimal_end_time_s;
+} PDC;
+
+PDC *pdc_get(void);
+
+typedef enum __attribute__ ((__packed__)) {
+ // final status for all pages
+ // if a page does not have one of these, it is considered unroutable
+ PDC_PAGE_READY = (1 << 0), // ready to be processed (pd->page is not null)
+ PDC_PAGE_FAILED = (1 << 1), // failed to be loaded (pd->page is null)
+ PDC_PAGE_SKIP = (1 << 2), // don't use this page, it is not good for us
+ PDC_PAGE_INVALID = (1 << 3), // don't use this page, it is invalid
+ PDC_PAGE_EMPTY = (1 << 4), // the page is empty, does not have any data
+
+ // other statuses for tracking issues
+ PDC_PAGE_PREPROCESSED = (1 << 5), // used during preprocessing
+ PDC_PAGE_PROCESSED = (1 << 6), // processed by the query caller
+ PDC_PAGE_RELEASED = (1 << 7), // already released
+
+ // data found in cache (preloaded) or on disk?
+ PDC_PAGE_PRELOADED = (1 << 8), // data found in memory
+ PDC_PAGE_DISK_PENDING = (1 << 9), // data need to be loaded from disk
+
+ // worker related statuses
+ PDC_PAGE_FAILED_INVALID_EXTENT = (1 << 10),
+ PDC_PAGE_FAILED_NOT_IN_EXTENT = (1 << 11),
+ PDC_PAGE_FAILED_TO_MAP_EXTENT = (1 << 12),
+ PDC_PAGE_FAILED_TO_ACQUIRE_DATAFILE= (1 << 13),
+
+ PDC_PAGE_EXTENT_FROM_CACHE = (1 << 14),
+ PDC_PAGE_EXTENT_FROM_DISK = (1 << 15),
+
+ PDC_PAGE_CANCELLED = (1 << 16), // the query thread had left when we try to load the page
+
+ PDC_PAGE_SOURCE_MAIN_CACHE = (1 << 17),
+ PDC_PAGE_SOURCE_OPEN_CACHE = (1 << 18),
+ PDC_PAGE_SOURCE_JOURNAL_V2 = (1 << 19),
+ PDC_PAGE_PRELOADED_PASS4 = (1 << 20),
+
+ // datafile acquired
+ PDC_PAGE_DATAFILE_ACQUIRED = (1 << 30),
+} PDC_PAGE_STATUS;
+
+#define PDC_PAGE_QUERY_GLOBAL_SKIP_LIST (PDC_PAGE_FAILED | PDC_PAGE_SKIP | PDC_PAGE_INVALID | PDC_PAGE_RELEASED)
+
+struct page_details {
+ struct {
+ struct rrdengine_datafile *ptr;
+ uv_file file;
+ unsigned fileno;
+
+ struct {
+ uint64_t pos;
+ uint32_t bytes;
+ } extent;
+ } datafile;
+
+ struct pgc_page *page;
+ Word_t metric_id;
+ time_t first_time_s;
+ time_t last_time_s;
+ uint32_t update_every_s;
+ uint16_t page_length;
+ PDC_PAGE_STATUS status;
+
+ struct {
+ struct page_details *prev;
+ struct page_details *next;
+ } load;
+};
+
+struct page_details *page_details_get(void);
+
+#define pdc_page_status_check(pd, flag) (__atomic_load_n(&((pd)->status), __ATOMIC_ACQUIRE) & (flag))
+#define pdc_page_status_set(pd, flag) __atomic_or_fetch(&((pd)->status), flag, __ATOMIC_RELEASE)
+#define pdc_page_status_clear(pd, flag) __atomic_and_fetch(&((od)->status), ~(flag), __ATOMIC_RELEASE)
+
+struct jv2_extents_info {
+ size_t index;
+ uint64_t pos;
+ unsigned bytes;
+ size_t number_of_pages;
+};
+
+struct jv2_metrics_info {
+ uuid_t *uuid;
+ uint32_t page_list_header;
+ time_t first_time_s;
+ time_t last_time_s;
+ size_t number_of_pages;
+ Pvoid_t JudyL_pages_by_start_time;
+};
+
+struct jv2_page_info {
+ time_t start_time_s;
+ time_t end_time_s;
+ time_t update_every_s;
+ size_t page_length;
+ uint32_t extent_index;
+ void *custom_data;
+
+ // private
+ struct pgc_page *page;
+};
+
+typedef enum __attribute__ ((__packed__)) {
+ RRDENG_CHO_UNALIGNED = (1 << 0), // set when this metric is not page aligned according to page alignment
+ RRDENG_FIRST_PAGE_ALLOCATED = (1 << 1), // set when this metric has allocated its first page
+ RRDENG_1ST_METRIC_WRITER = (1 << 2),
+} RRDENG_COLLECT_HANDLE_OPTIONS;
+
+typedef enum __attribute__ ((__packed__)) {
+ RRDENG_PAGE_PAST_COLLECTION = (1 << 0),
+ RRDENG_PAGE_REPEATED_COLLECTION = (1 << 1),
+ RRDENG_PAGE_BIG_GAP = (1 << 2),
+ RRDENG_PAGE_GAP = (1 << 3),
+ RRDENG_PAGE_FUTURE_POINT = (1 << 4),
+ RRDENG_PAGE_CREATED_IN_FUTURE = (1 << 5),
+ RRDENG_PAGE_COMPLETED_IN_FUTURE = (1 << 6),
+ RRDENG_PAGE_UNALIGNED = (1 << 7),
+ RRDENG_PAGE_CONFLICT = (1 << 8),
+ RRDENG_PAGE_FULL = (1 << 9),
+ RRDENG_PAGE_COLLECT_FINALIZE = (1 << 10),
+ RRDENG_PAGE_UPDATE_EVERY_CHANGE = (1 << 11),
+ RRDENG_PAGE_STEP_TOO_SMALL = (1 << 12),
+ RRDENG_PAGE_STEP_UNALIGNED = (1 << 13),
+} RRDENG_COLLECT_PAGE_FLAGS;
+
struct rrdeng_collect_handle {
- struct pg_cache_page_index *page_index;
- struct rrdeng_page_descr *descr;
- unsigned long page_correlation_id;
- // set to 1 when this dimension is not page aligned with the other dimensions in the chart
- uint8_t unaligned_page;
+ struct metric *metric;
+ struct pgc_page *page;
struct pg_alignment *alignment;
+ RRDENG_COLLECT_HANDLE_OPTIONS options;
+ uint8_t type;
+ RRDENG_COLLECT_PAGE_FLAGS page_flags;
+ uint32_t page_entries_max;
+ uint32_t page_position; // keep track of the current page size, to make sure we don't exceed it
+ usec_t page_start_time_ut;
+ usec_t page_end_time_ut;
+ usec_t update_every_ut;
};
struct rrdeng_query_handle {
- struct rrdeng_page_descr *descr;
+ struct metric *metric;
+ struct pgc_page *page;
struct rrdengine_instance *ctx;
- struct pg_cache_page_index *page_index;
- time_t wanted_start_time_s;
+ storage_number *metric_data;
+ struct page_details_control *pdc;
+
+ // the request
+ time_t start_time_s;
+ time_t end_time_s;
+ STORAGE_PRIORITY priority;
+
+ // internal data
time_t now_s;
+ time_t dt_s;
+
unsigned position;
unsigned entries;
- storage_number *page;
- usec_t page_end_time_ut;
- uint32_t page_length;
- time_t dt_s;
+
+#ifdef NETDATA_INTERNAL_CHECKS
+ usec_t started_time_s;
+ pid_t query_pid;
+ struct rrdeng_query_handle *prev, *next;
+#endif
};
-typedef enum {
- RRDENGINE_STATUS_UNINITIALIZED = 0,
- RRDENGINE_STATUS_INITIALIZING,
- RRDENGINE_STATUS_INITIALIZED
-} rrdengine_state_t;
+struct rrdeng_query_handle *rrdeng_query_handle_get(void);
+void rrdeng_query_handle_release(struct rrdeng_query_handle *handle);
enum rrdeng_opcode {
/* can be used to return empty status or flush the command queue */
- RRDENG_NOOP = 0,
-
- RRDENG_READ_PAGE,
- RRDENG_READ_EXTENT,
- RRDENG_COMMIT_PAGE,
- RRDENG_FLUSH_PAGES,
- RRDENG_SHUTDOWN,
- RRDENG_INVALIDATE_OLDEST_MEMORY_PAGE,
- RRDENG_QUIESCE,
-
- RRDENG_MAX_OPCODE
-};
-
-struct rrdeng_read_page {
- struct rrdeng_page_descr *page_cache_descr;
+ RRDENG_OPCODE_NOOP = 0,
+
+ RRDENG_OPCODE_QUERY,
+ RRDENG_OPCODE_EXTENT_WRITE,
+ RRDENG_OPCODE_EXTENT_READ,
+ RRDENG_OPCODE_FLUSHED_TO_OPEN,
+ RRDENG_OPCODE_DATABASE_ROTATE,
+ RRDENG_OPCODE_JOURNAL_INDEX,
+ RRDENG_OPCODE_FLUSH_INIT,
+ RRDENG_OPCODE_EVICT_INIT,
+ RRDENG_OPCODE_CTX_SHUTDOWN,
+ RRDENG_OPCODE_CTX_QUIESCE,
+ RRDENG_OPCODE_CTX_POPULATE_MRG,
+ RRDENG_OPCODE_CLEANUP,
+
+ RRDENG_OPCODE_MAX
};
-struct rrdeng_read_extent {
- struct rrdeng_page_descr *page_cache_descr[MAX_PAGES_PER_EXTENT];
- int page_count;
-};
-
-struct rrdeng_cmd {
- enum rrdeng_opcode opcode;
- union {
- struct rrdeng_read_page read_page;
- struct rrdeng_read_extent read_extent;
- struct completion *completion;
- };
-};
-
-#define RRDENG_CMD_Q_MAX_SIZE (2048)
-
-struct rrdeng_cmdqueue {
- unsigned head, tail;
- struct rrdeng_cmd cmd_array[RRDENG_CMD_Q_MAX_SIZE];
+// WORKERS IDS:
+// RRDENG_MAX_OPCODE : reserved for the cleanup
+// RRDENG_MAX_OPCODE + opcode : reserved for the callbacks of each opcode
+// RRDENG_MAX_OPCODE + RRDENG_MAX_OPCODE : reserved for the timer
+#define RRDENG_TIMER_CB (RRDENG_OPCODE_MAX + RRDENG_OPCODE_MAX)
+#define RRDENG_FLUSH_TRANSACTION_BUFFER_CB (RRDENG_TIMER_CB + 1)
+#define RRDENG_OPCODES_WAITING (RRDENG_TIMER_CB + 2)
+#define RRDENG_WORKS_DISPATCHED (RRDENG_TIMER_CB + 3)
+#define RRDENG_WORKS_EXECUTING (RRDENG_TIMER_CB + 4)
+
+struct extent_io_data {
+ unsigned fileno;
+ uv_file file;
+ uint64_t pos;
+ unsigned bytes;
+ uint16_t page_length;
};
struct extent_io_descriptor {
- uv_fs_t req;
- uv_work_t req_worker;
+ struct rrdengine_instance *ctx;
+ uv_fs_t uv_fs_request;
uv_buf_t iov;
uv_file file;
void *buf;
- void *map_base;
- size_t map_length;
+ struct wal *wal;
uint64_t pos;
unsigned bytes;
struct completion *completion;
unsigned descr_count;
- int release_descr;
- struct rrdeng_page_descr *descr_array[MAX_PAGES_PER_EXTENT];
- struct rrdeng_page_descr descr_read_array[MAX_PAGES_PER_EXTENT];
- Word_t descr_commit_idx_array[MAX_PAGES_PER_EXTENT];
+ struct page_descr_with_data *descr_array[MAX_PAGES_PER_EXTENT];
+ struct rrdengine_datafile *datafile;
struct extent_io_descriptor *next; /* multiple requests to be served by the same cached extent */
};
struct generic_io_descriptor {
+ struct rrdengine_instance *ctx;
uv_fs_t req;
uv_buf_t iov;
void *buf;
+ void *data;
uint64_t pos;
unsigned bytes;
struct completion *completion;
};
-struct extent_cache_element {
- struct extent_info *extent; /* The ABA problem is avoided with the help of fileno below */
- unsigned fileno;
- struct extent_cache_element *prev; /* LRU */
- struct extent_cache_element *next; /* LRU */
- struct extent_io_descriptor *inflight_io_descr; /* I/O descriptor for in-flight extent */
- uint8_t pages[MAX_PAGES_PER_EXTENT * RRDENG_BLOCK_SIZE];
-};
-
-#define MAX_CACHED_EXTENTS 16 /* cannot be over 32 to fit in 32-bit architectures */
-
-/* Initialize by setting the structure to zero */
-struct extent_cache {
- struct extent_cache_element extent_array[MAX_CACHED_EXTENTS];
- unsigned allocation_bitmap; /* 1 if the corresponding position in the extent_array is allocated */
- unsigned inflight_bitmap; /* 1 if the corresponding position in the extent_array is waiting for I/O */
-
- struct extent_cache_element *replaceQ_head; /* LRU */
- struct extent_cache_element *replaceQ_tail; /* MRU */
-};
-
-struct rrdengine_worker_config {
- struct rrdengine_instance *ctx;
-
- uv_thread_t thread;
- uv_loop_t* loop;
- uv_async_t async;
-
- /* file deletion thread */
- uv_thread_t *now_deleting_files;
- unsigned long cleanup_thread_deleting_files; /* set to 0 when now_deleting_files is still running */
-
- /* dirty page deletion thread */
- uv_thread_t *now_invalidating_dirty_pages;
- /* set to 0 when now_invalidating_dirty_pages is still running */
- unsigned long cleanup_thread_invalidating_dirty_pages;
- unsigned inflight_dirty_pages;
-
- /* FIFO command queue */
- uv_mutex_t cmd_mutex;
- uv_cond_t cmd_cond;
- volatile unsigned queue_size;
- struct rrdeng_cmdqueue cmd_queue;
+typedef struct wal {
+ uint64_t transaction_id;
+ void *buf;
+ size_t size;
+ size_t buf_size;
+ struct generic_io_descriptor io_descr;
- struct extent_cache xt_cache;
+ struct {
+ struct wal *prev;
+ struct wal *next;
+ } cache;
+} WAL;
- int error;
-};
+WAL *wal_get(struct rrdengine_instance *ctx, unsigned size);
+void wal_release(WAL *wal);
/*
* Debug statistics not used by code logic.
* They only describe operations since DB engine instance load time.
*/
struct rrdengine_statistics {
- rrdeng_stats_t metric_API_producers;
- rrdeng_stats_t metric_API_consumers;
- rrdeng_stats_t pg_cache_insertions;
- rrdeng_stats_t pg_cache_deletions;
- rrdeng_stats_t pg_cache_hits;
- rrdeng_stats_t pg_cache_misses;
- rrdeng_stats_t pg_cache_backfills;
- rrdeng_stats_t pg_cache_evictions;
rrdeng_stats_t before_decompress_bytes;
rrdeng_stats_t after_decompress_bytes;
rrdeng_stats_t before_compress_bytes;
rrdeng_stats_t after_compress_bytes;
+
rrdeng_stats_t io_write_bytes;
rrdeng_stats_t io_write_requests;
rrdeng_stats_t io_read_bytes;
rrdeng_stats_t io_read_requests;
- rrdeng_stats_t io_write_extent_bytes;
- rrdeng_stats_t io_write_extents;
- rrdeng_stats_t io_read_extent_bytes;
- rrdeng_stats_t io_read_extents;
+
rrdeng_stats_t datafile_creations;
rrdeng_stats_t datafile_deletions;
rrdeng_stats_t journalfile_creations;
rrdeng_stats_t journalfile_deletions;
- rrdeng_stats_t page_cache_descriptors;
+
rrdeng_stats_t io_errors;
rrdeng_stats_t fs_errors;
- rrdeng_stats_t pg_cache_over_half_dirty_events;
- rrdeng_stats_t flushing_pressure_page_deletions;
};
/* I/O errors global counter */
@@ -227,57 +340,179 @@ extern rrdeng_stats_t rrdeng_reserved_file_descriptors;
extern rrdeng_stats_t global_pg_cache_over_half_dirty_events;
extern rrdeng_stats_t global_flushing_pressure_page_deletions; /* number of deleted pages */
-#define NO_QUIESCE (0) /* initial state when all operations function normally */
-#define SET_QUIESCE (1) /* set it before shutting down the instance, quiesce long running operations */
-#define QUIESCED (2) /* is set after all threads have finished running */
+struct rrdengine_instance {
+ struct {
+ bool legacy; // true when the db is autonomous for a single host
-typedef enum {
- LOAD_ERRORS_PAGE_FLIPPED_TIME = 0,
- LOAD_ERRORS_PAGE_EQUAL_TIME = 1,
- LOAD_ERRORS_PAGE_ZERO_ENTRIES = 2,
- LOAD_ERRORS_PAGE_UPDATE_ZERO = 3,
- LOAD_ERRORS_PAGE_FLEXY_TIME = 4,
- LOAD_ERRORS_DROPPED_EXTENT = 5,
-} INVALID_PAGE_ID;
+ int tier; // the tier of this ctx
+ uint8_t page_type; // default page type for this context
-struct rrdengine_instance {
- struct rrdengine_worker_config worker_config;
- struct completion rrdengine_completion;
- struct page_cache pg_cache;
- uint8_t drop_metrics_under_page_cache_pressure; /* boolean */
- uint8_t global_compress_alg;
- struct transaction_commit_log commit_log;
- struct rrdengine_datafile_list datafiles;
- RRDHOST *host; /* the legacy host, or NULL for multi-host DB */
- char dbfiles_path[FILENAME_MAX + 1];
- char machine_guid[GUID_LEN + 1]; /* the unique ID of the corresponding host, or localhost for multihost DB */
- uint64_t disk_space;
- uint64_t max_disk_space;
- int tier;
- unsigned last_fileno; /* newest index of datafile and journalfile */
- unsigned long max_cache_pages;
- unsigned long cache_pages_low_watermark;
- unsigned long metric_API_max_producers;
-
- uint8_t quiesce; /* set to SET_QUIESCE before shutdown of the engine */
- uint8_t page_type; /* Default page type for this context */
+ uint64_t max_disk_space; // the max disk space this ctx is allowed to use
+ uint8_t global_compress_alg; // the wanted compression algorithm
- struct rrdengine_statistics stats;
+ char dbfiles_path[FILENAME_MAX + 1];
+ } config;
struct {
- size_t counter;
- usec_t latest_end_time_ut;
- } load_errors[6];
+ uv_rwlock_t rwlock; // the linked list of datafiles is protected by this lock
+ struct rrdengine_datafile *first; // oldest - the newest with ->first->prev
+ } datafiles;
+
+ struct {
+ unsigned last_fileno; // newest index of datafile and journalfile
+ unsigned last_flush_fileno; // newest index of datafile received data
+
+ size_t collectors_running;
+ size_t collectors_running_duplicate;
+ size_t inflight_queries; // the number of queries currently running
+ uint64_t current_disk_space; // the current disk space size used
+
+ uint64_t transaction_id; // the transaction id of the next extent flushing
+
+ bool migration_to_v2_running;
+ bool now_deleting_files;
+ unsigned extents_currently_being_flushed; // non-zero until we commit data to disk (both datafile and journal file)
+ } atomic;
+
+ struct {
+ bool exit_mode;
+ bool enabled; // when set (before shutdown), queries are prohibited
+ struct completion completion;
+ } quiesce;
+
+ struct {
+ struct {
+ size_t size;
+ struct completion *array;
+ } populate_mrg;
+
+ bool create_new_datafile_pair;
+ } loading;
+
+ struct rrdengine_statistics stats;
};
-void *dbengine_page_alloc(void);
-void dbengine_page_free(void *page);
+#define ctx_current_disk_space_get(ctx) __atomic_load_n(&(ctx)->atomic.current_disk_space, __ATOMIC_RELAXED)
+#define ctx_current_disk_space_increase(ctx, size) __atomic_add_fetch(&(ctx)->atomic.current_disk_space, size, __ATOMIC_RELAXED)
+#define ctx_current_disk_space_decrease(ctx, size) __atomic_sub_fetch(&(ctx)->atomic.current_disk_space, size, __ATOMIC_RELAXED)
+
+static inline void ctx_io_read_op_bytes(struct rrdengine_instance *ctx, size_t bytes) {
+ __atomic_add_fetch(&ctx->stats.io_read_bytes, bytes, __ATOMIC_RELAXED);
+ __atomic_add_fetch(&ctx->stats.io_read_requests, 1, __ATOMIC_RELAXED);
+}
+
+static inline void ctx_io_write_op_bytes(struct rrdengine_instance *ctx, size_t bytes) {
+ __atomic_add_fetch(&ctx->stats.io_write_bytes, bytes, __ATOMIC_RELAXED);
+ __atomic_add_fetch(&ctx->stats.io_write_requests, 1, __ATOMIC_RELAXED);
+}
+static inline void ctx_io_error(struct rrdengine_instance *ctx) {
+ __atomic_add_fetch(&ctx->stats.io_errors, 1, __ATOMIC_RELAXED);
+ rrd_stat_atomic_add(&global_io_errors, 1);
+}
+
+static inline void ctx_fs_error(struct rrdengine_instance *ctx) {
+ __atomic_add_fetch(&ctx->stats.fs_errors, 1, __ATOMIC_RELAXED);
+ rrd_stat_atomic_add(&global_fs_errors, 1);
+}
+
+#define ctx_last_fileno_get(ctx) __atomic_load_n(&(ctx)->atomic.last_fileno, __ATOMIC_RELAXED)
+#define ctx_last_fileno_increment(ctx) __atomic_add_fetch(&(ctx)->atomic.last_fileno, 1, __ATOMIC_RELAXED)
+
+#define ctx_last_flush_fileno_get(ctx) __atomic_load_n(&(ctx)->atomic.last_flush_fileno, __ATOMIC_RELAXED)
+static inline void ctx_last_flush_fileno_set(struct rrdengine_instance *ctx, unsigned fileno) {
+ unsigned old_fileno = ctx_last_flush_fileno_get(ctx);
+
+ do {
+ if(old_fileno >= fileno)
+ return;
+
+ } while(!__atomic_compare_exchange_n(&ctx->atomic.last_flush_fileno, &old_fileno, fileno, false, __ATOMIC_RELAXED, __ATOMIC_RELAXED));
+}
+
+#define ctx_is_available_for_queries(ctx) (__atomic_load_n(&(ctx)->quiesce.enabled, __ATOMIC_RELAXED) == false && __atomic_load_n(&(ctx)->quiesce.exit_mode, __ATOMIC_RELAXED) == false)
+
+void *dbengine_page_alloc(size_t size);
+void dbengine_page_free(void *page, size_t size);
+
+void *dbengine_extent_alloc(size_t size);
+void dbengine_extent_free(void *extent, size_t size);
+
+bool rrdeng_ctx_exceeded_disk_quota(struct rrdengine_instance *ctx);
int init_rrd_files(struct rrdengine_instance *ctx);
void finalize_rrd_files(struct rrdengine_instance *ctx);
-void rrdeng_test_quota(struct rrdengine_worker_config* wc);
-void rrdeng_worker(void* arg);
-void rrdeng_enq_cmd(struct rrdengine_worker_config* wc, struct rrdeng_cmd *cmd);
-struct rrdeng_cmd rrdeng_deq_cmd(struct rrdengine_worker_config* wc);
+bool rrdeng_dbengine_spawn(struct rrdengine_instance *ctx);
+void dbengine_event_loop(void *arg);
+
+typedef void (*enqueue_callback_t)(struct rrdeng_cmd *cmd);
+typedef void (*dequeue_callback_t)(struct rrdeng_cmd *cmd);
+
+void rrdeng_enqueue_epdl_cmd(struct rrdeng_cmd *cmd);
+void rrdeng_dequeue_epdl_cmd(struct rrdeng_cmd *cmd);
+
+typedef struct rrdeng_cmd *(*requeue_callback_t)(void *data);
+void rrdeng_req_cmd(requeue_callback_t get_cmd_cb, void *data, STORAGE_PRIORITY priority);
+
+void rrdeng_enq_cmd(struct rrdengine_instance *ctx, enum rrdeng_opcode opcode, void *data,
+ struct completion *completion, enum storage_priority priority,
+ enqueue_callback_t enqueue_cb, dequeue_callback_t dequeue_cb);
+
+void pdc_route_asynchronously(struct rrdengine_instance *ctx, struct page_details_control *pdc);
+void pdc_route_synchronously(struct rrdengine_instance *ctx, struct page_details_control *pdc);
+
+void pdc_acquire(PDC *pdc);
+bool pdc_release_and_destroy_if_unreferenced(PDC *pdc, bool worker, bool router);
+
+unsigned rrdeng_target_data_file_size(struct rrdengine_instance *ctx);
+
+struct page_descr_with_data *page_descriptor_get(void);
+
+typedef struct validated_page_descriptor {
+ time_t start_time_s;
+ time_t end_time_s;
+ time_t update_every_s;
+ size_t page_length;
+ size_t point_size;
+ size_t entries;
+ uint8_t type;
+ bool is_valid;
+} VALIDATED_PAGE_DESCRIPTOR;
+
+#define DBENGINE_EMPTY_PAGE (void *)(-1)
+
+#define page_entries_by_time(start_time_s, end_time_s, update_every_s) \
+ ((update_every_s) ? (((end_time_s) - ((start_time_s) - (update_every_s))) / (update_every_s)) : 1)
+
+#define page_entries_by_size(page_length_in_bytes, point_size_in_bytes) \
+ ((page_length_in_bytes) / (point_size_in_bytes))
+
+VALIDATED_PAGE_DESCRIPTOR validate_page(uuid_t *uuid,
+ time_t start_time_s,
+ time_t end_time_s,
+ time_t update_every_s,
+ size_t page_length,
+ uint8_t page_type,
+ size_t entries,
+ time_t now_s,
+ time_t overwrite_zero_update_every_s,
+ bool have_read_error,
+ const char *msg,
+ RRDENG_COLLECT_PAGE_FLAGS flags);
+VALIDATED_PAGE_DESCRIPTOR validate_extent_page_descr(const struct rrdeng_extent_page_descr *descr, time_t now_s, time_t overwrite_zero_update_every_s, bool have_read_error);
+void collect_page_flags_to_buffer(BUFFER *wb, RRDENG_COLLECT_PAGE_FLAGS flags);
+
+typedef enum {
+ PAGE_IS_IN_THE_PAST = -1,
+ PAGE_IS_IN_RANGE = 0,
+ PAGE_IS_IN_THE_FUTURE = 1,
+} TIME_RANGE_COMPARE;
+
+TIME_RANGE_COMPARE is_page_in_time_range(time_t page_first_time_s, time_t page_last_time_s, time_t wanted_start_time_s, time_t wanted_end_time_s);
+
+static inline time_t max_acceptable_collected_time(void) {
+ return now_realtime_sec() + 1;
+}
+
+void datafile_delete(struct rrdengine_instance *ctx, struct rrdengine_datafile *datafile, bool update_retention, bool worker);
#endif /* NETDATA_RRDENGINE_H */
diff --git a/database/engine/rrdengineapi.c b/database/engine/rrdengineapi.c
index 4525b041..27497bbb 100755
--- a/database/engine/rrdengineapi.c
+++ b/database/engine/rrdengineapi.c
@@ -1,6 +1,5 @@
// SPDX-License-Identifier: GPL-3.0-or-later
#include "rrdengine.h"
-#include "../storage_engine.h"
/* Default global database instance */
struct rrdengine_instance multidb_ctx_storage_tier0;
@@ -8,12 +7,21 @@ struct rrdengine_instance multidb_ctx_storage_tier1;
struct rrdengine_instance multidb_ctx_storage_tier2;
struct rrdengine_instance multidb_ctx_storage_tier3;
struct rrdengine_instance multidb_ctx_storage_tier4;
+
+#define mrg_metric_ctx(metric) (struct rrdengine_instance *)mrg_metric_section(main_mrg, metric)
+
#if RRD_STORAGE_TIERS != 5
#error RRD_STORAGE_TIERS is not 5 - you need to add allocations here
#endif
struct rrdengine_instance *multidb_ctx[RRD_STORAGE_TIERS];
uint8_t tier_page_type[RRD_STORAGE_TIERS] = {PAGE_METRICS, PAGE_TIER, PAGE_TIER, PAGE_TIER, PAGE_TIER};
+#if defined(ENV32BIT)
+size_t tier_page_size[RRD_STORAGE_TIERS] = {2048, 1024, 192, 192, 192};
+#else
+size_t tier_page_size[RRD_STORAGE_TIERS] = {4096, 2048, 384, 384, 384};
+#endif
+
#if PAGE_TYPE_MAX != 1
#error PAGE_TYPE_MAX is not 1 - you need to add allocations here
#endif
@@ -27,14 +35,17 @@ __attribute__((constructor)) void initialize_multidb_ctx(void) {
multidb_ctx[4] = &multidb_ctx_storage_tier4;
}
-int db_engine_use_malloc = 0;
int default_rrdeng_page_fetch_timeout = 3;
int default_rrdeng_page_fetch_retries = 3;
-int default_rrdeng_page_cache_mb = 32;
+int db_engine_journal_check = 0;
int default_rrdeng_disk_quota_mb = 256;
int default_multidb_disk_quota_mb = 256;
-/* Default behaviour is to unblock data collection if the page cache is full of dirty pages by dropping metrics */
-uint8_t rrdeng_drop_metrics_under_page_cache_pressure = 1;
+
+#if defined(ENV32BIT)
+int default_rrdeng_page_cache_mb = 16;
+#else
+int default_rrdeng_page_cache_mb = 32;
+#endif
// ----------------------------------------------------------------------------
// metrics groups
@@ -90,161 +101,207 @@ void rrdeng_generate_legacy_uuid(const char *dim_id, const char *chart_id, uuid_
memcpy(ret_uuid, hash_value, sizeof(uuid_t));
}
-/* Transform legacy UUID to be unique across hosts deterministically */
-void rrdeng_convert_legacy_uuid_to_multihost(char machine_guid[GUID_LEN + 1], uuid_t *legacy_uuid, uuid_t *ret_uuid)
-{
- EVP_MD_CTX *evpctx;
- unsigned char hash_value[EVP_MAX_MD_SIZE];
- unsigned int hash_len;
-
- evpctx = EVP_MD_CTX_create();
- EVP_DigestInit_ex(evpctx, EVP_sha256(), NULL);
- EVP_DigestUpdate(evpctx, machine_guid, GUID_LEN);
- EVP_DigestUpdate(evpctx, *legacy_uuid, sizeof(uuid_t));
- EVP_DigestFinal_ex(evpctx, hash_value, &hash_len);
- EVP_MD_CTX_destroy(evpctx);
- fatal_assert(hash_len > sizeof(uuid_t));
- memcpy(ret_uuid, hash_value, sizeof(uuid_t));
-}
-
-STORAGE_METRIC_HANDLE *rrdeng_metric_get_legacy(STORAGE_INSTANCE *db_instance, const char *rd_id, const char *st_id) {
+static METRIC *rrdeng_metric_get_legacy(STORAGE_INSTANCE *db_instance, const char *rd_id, const char *st_id) {
+ struct rrdengine_instance *ctx = (struct rrdengine_instance *)db_instance;
uuid_t legacy_uuid;
rrdeng_generate_legacy_uuid(rd_id, st_id, &legacy_uuid);
- return rrdeng_metric_get(db_instance, &legacy_uuid);
+ return mrg_metric_get_and_acquire(main_mrg, &legacy_uuid, (Word_t) ctx);
}
// ----------------------------------------------------------------------------
// metric handle
void rrdeng_metric_release(STORAGE_METRIC_HANDLE *db_metric_handle) {
- struct pg_cache_page_index *page_index = (struct pg_cache_page_index *)db_metric_handle;
-
- __atomic_sub_fetch(&page_index->refcount, 1, __ATOMIC_SEQ_CST);
+ METRIC *metric = (METRIC *)db_metric_handle;
+ mrg_metric_release(main_mrg, metric);
}
STORAGE_METRIC_HANDLE *rrdeng_metric_dup(STORAGE_METRIC_HANDLE *db_metric_handle) {
- struct pg_cache_page_index *page_index = (struct pg_cache_page_index *)db_metric_handle;
- __atomic_add_fetch(&page_index->refcount, 1, __ATOMIC_SEQ_CST);
- return db_metric_handle;
+ METRIC *metric = (METRIC *)db_metric_handle;
+ return (STORAGE_METRIC_HANDLE *) mrg_metric_dup(main_mrg, metric);
}
STORAGE_METRIC_HANDLE *rrdeng_metric_get(STORAGE_INSTANCE *db_instance, uuid_t *uuid) {
struct rrdengine_instance *ctx = (struct rrdengine_instance *)db_instance;
- struct page_cache *pg_cache = &ctx->pg_cache;
- struct pg_cache_page_index *page_index = NULL;
-
- uv_rwlock_rdlock(&pg_cache->metrics_index.lock);
- Pvoid_t *PValue = JudyHSGet(pg_cache->metrics_index.JudyHS_array, uuid, sizeof(uuid_t));
- if (likely(NULL != PValue))
- page_index = *PValue;
- uv_rwlock_rdunlock(&pg_cache->metrics_index.lock);
-
- if (likely(page_index))
- __atomic_add_fetch(&page_index->refcount, 1, __ATOMIC_SEQ_CST);
-
- return (STORAGE_METRIC_HANDLE *)page_index;
+ return (STORAGE_METRIC_HANDLE *) mrg_metric_get_and_acquire(main_mrg, uuid, (Word_t) ctx);
}
-STORAGE_METRIC_HANDLE *rrdeng_metric_create(STORAGE_INSTANCE *db_instance, uuid_t *uuid) {
+static METRIC *rrdeng_metric_create(STORAGE_INSTANCE *db_instance, uuid_t *uuid) {
internal_fatal(!db_instance, "DBENGINE: db_instance is NULL");
struct rrdengine_instance *ctx = (struct rrdengine_instance *)db_instance;
- struct pg_cache_page_index *page_index;
- struct page_cache *pg_cache = &ctx->pg_cache;
-
- uv_rwlock_wrlock(&pg_cache->metrics_index.lock);
- Pvoid_t *PValue = JudyHSIns(&pg_cache->metrics_index.JudyHS_array, uuid, sizeof(uuid_t), PJE0);
- fatal_assert(NULL == *PValue); /* TODO: figure out concurrency model */
- *PValue = page_index = create_page_index(uuid, ctx);
- page_index->prev = pg_cache->metrics_index.last_page_index;
- pg_cache->metrics_index.last_page_index = page_index;
- page_index->refcount = 1;
- uv_rwlock_wrunlock(&pg_cache->metrics_index.lock);
-
- return (STORAGE_METRIC_HANDLE *)page_index;
+ MRG_ENTRY entry = {
+ .section = (Word_t)ctx,
+ .first_time_s = 0,
+ .last_time_s = 0,
+ .latest_update_every_s = 0,
+ };
+ uuid_copy(entry.uuid, *uuid);
+
+ METRIC *metric = mrg_metric_add_and_acquire(main_mrg, entry, NULL);
+ return metric;
}
STORAGE_METRIC_HANDLE *rrdeng_metric_get_or_create(RRDDIM *rd, STORAGE_INSTANCE *db_instance) {
- STORAGE_METRIC_HANDLE *db_metric_handle;
-
- db_metric_handle = rrdeng_metric_get(db_instance, &rd->metric_uuid);
- if(!db_metric_handle) {
- db_metric_handle = rrdeng_metric_get_legacy(db_instance, rrddim_id(rd), rrdset_id(rd->rrdset));
- if(db_metric_handle) {
- struct pg_cache_page_index *page_index = (struct pg_cache_page_index *)db_metric_handle;
- uuid_copy(rd->metric_uuid, page_index->id);
+ struct rrdengine_instance *ctx = (struct rrdengine_instance *)db_instance;
+ METRIC *metric;
+
+ metric = mrg_metric_get_and_acquire(main_mrg, &rd->metric_uuid, (Word_t) ctx);
+
+ if(unlikely(!metric)) {
+ if(unlikely(ctx->config.legacy)) {
+ // this is a single host database
+ // generate uuid from the chart and dimensions ids
+ // and overwrite the one supplied by rrddim
+ metric = rrdeng_metric_get_legacy(db_instance, rrddim_id(rd), rrdset_id(rd->rrdset));
+ if (metric)
+ uuid_copy(rd->metric_uuid, *mrg_metric_uuid(main_mrg, metric));
}
+
+ if(likely(!metric))
+ metric = rrdeng_metric_create(db_instance, &rd->metric_uuid);
}
- if(!db_metric_handle)
- db_metric_handle = rrdeng_metric_create(db_instance, &rd->metric_uuid);
#ifdef NETDATA_INTERNAL_CHECKS
- struct pg_cache_page_index *page_index = (struct pg_cache_page_index *)db_metric_handle;
- if(uuid_compare(rd->metric_uuid, page_index->id) != 0) {
+ if(uuid_compare(rd->metric_uuid, *mrg_metric_uuid(main_mrg, metric)) != 0) {
char uuid1[UUID_STR_LEN + 1];
char uuid2[UUID_STR_LEN + 1];
uuid_unparse(rd->metric_uuid, uuid1);
- uuid_unparse(page_index->id, uuid2);
- fatal("DBENGINE: uuids do not match, asked for metric '%s', but got page_index of metric '%s'", uuid1, uuid2);
+ uuid_unparse(*mrg_metric_uuid(main_mrg, metric), uuid2);
+ fatal("DBENGINE: uuids do not match, asked for metric '%s', but got metric '%s'", uuid1, uuid2);
}
- struct rrdengine_instance *ctx = (struct rrdengine_instance *)db_instance;
- if(page_index->ctx != ctx)
- fatal("DBENGINE: mixed up rrdengine instances, asked for metric from %p, got from %p", ctx, page_index->ctx);
+ if(mrg_metric_ctx(metric) != ctx)
+ fatal("DBENGINE: mixed up db instances, asked for metric from %p, got from %p",
+ ctx, mrg_metric_ctx(metric));
#endif
- return db_metric_handle;
+ return (STORAGE_METRIC_HANDLE *)metric;
}
// ----------------------------------------------------------------------------
// collect ops
+static inline void check_and_fix_mrg_update_every(struct rrdeng_collect_handle *handle) {
+ if(unlikely((time_t)(handle->update_every_ut / USEC_PER_SEC) != mrg_metric_get_update_every_s(main_mrg, handle->metric))) {
+ internal_error(true, "DBENGINE: collection handle has update every %ld, but the metric registry has %ld. Fixing it.",
+ (time_t)(handle->update_every_ut / USEC_PER_SEC), mrg_metric_get_update_every_s(main_mrg, handle->metric));
+
+ if(unlikely(!handle->update_every_ut))
+ handle->update_every_ut = (usec_t)mrg_metric_get_update_every_s(main_mrg, handle->metric) * USEC_PER_SEC;
+ else
+ mrg_metric_set_update_every(main_mrg, handle->metric, (time_t)(handle->update_every_ut / USEC_PER_SEC));
+ }
+}
+
+static inline bool check_completed_page_consistency(struct rrdeng_collect_handle *handle __maybe_unused) {
+#ifdef NETDATA_INTERNAL_CHECKS
+ if (unlikely(!handle->page || !handle->page_entries_max || !handle->page_position || !handle->page_end_time_ut))
+ return false;
+
+ struct rrdengine_instance *ctx = mrg_metric_ctx(handle->metric);
+
+ uuid_t *uuid = mrg_metric_uuid(main_mrg, handle->metric);
+ time_t start_time_s = pgc_page_start_time_s(handle->page);
+ time_t end_time_s = pgc_page_end_time_s(handle->page);
+ time_t update_every_s = pgc_page_update_every_s(handle->page);
+ size_t page_length = handle->page_position * CTX_POINT_SIZE_BYTES(ctx);
+ size_t entries = handle->page_position;
+ time_t overwrite_zero_update_every_s = (time_t)(handle->update_every_ut / USEC_PER_SEC);
+
+ if(end_time_s > max_acceptable_collected_time())
+ handle->page_flags |= RRDENG_PAGE_COMPLETED_IN_FUTURE;
+
+ VALIDATED_PAGE_DESCRIPTOR vd = validate_page(
+ uuid,
+ start_time_s,
+ end_time_s,
+ update_every_s,
+ page_length,
+ ctx->config.page_type,
+ entries,
+ 0, // do not check for future timestamps - we inherit the timestamps of the children
+ overwrite_zero_update_every_s,
+ false,
+ "collected",
+ handle->page_flags);
+
+ return vd.is_valid;
+#else
+ return true;
+#endif
+}
+
/*
* Gets a handle for storing metrics to the database.
* The handle must be released with rrdeng_store_metric_final().
*/
STORAGE_COLLECT_HANDLE *rrdeng_store_metric_init(STORAGE_METRIC_HANDLE *db_metric_handle, uint32_t update_every, STORAGE_METRICS_GROUP *smg) {
- struct pg_cache_page_index *page_index = (struct pg_cache_page_index *)db_metric_handle;
+ METRIC *metric = (METRIC *)db_metric_handle;
+ struct rrdengine_instance *ctx = mrg_metric_ctx(metric);
+
+ bool is_1st_metric_writer = true;
+ if(!mrg_metric_set_writer(main_mrg, metric)) {
+ is_1st_metric_writer = false;
+ char uuid[UUID_STR_LEN + 1];
+ uuid_unparse(*mrg_metric_uuid(main_mrg, metric), uuid);
+ error("DBENGINE: metric '%s' is already collected and should not be collected twice - expect gaps on the charts", uuid);
+ }
+
+ metric = mrg_metric_dup(main_mrg, metric);
+
struct rrdeng_collect_handle *handle;
handle = callocz(1, sizeof(struct rrdeng_collect_handle));
- handle->page_index = page_index;
- handle->descr = NULL;
- handle->unaligned_page = 0;
- page_index->latest_update_every_s = update_every;
+ handle->metric = metric;
+ handle->page = NULL;
+ handle->page_position = 0;
+ handle->page_entries_max = 0;
+ handle->update_every_ut = (usec_t)update_every * USEC_PER_SEC;
+ handle->options = is_1st_metric_writer ? RRDENG_1ST_METRIC_WRITER : 0;
+
+ __atomic_add_fetch(&ctx->atomic.collectors_running, 1, __ATOMIC_RELAXED);
+ if(!is_1st_metric_writer)
+ __atomic_add_fetch(&ctx->atomic.collectors_running_duplicate, 1, __ATOMIC_RELAXED);
+
+ mrg_metric_set_update_every(main_mrg, metric, update_every);
handle->alignment = (struct pg_alignment *)smg;
rrdeng_page_alignment_acquire(handle->alignment);
- uv_rwlock_wrlock(&page_index->lock);
- ++page_index->writers;
- uv_rwlock_wrunlock(&page_index->lock);
+ // this is important!
+ // if we don't set the page_end_time_ut during the first collection
+ // data collection may be able to go back in time and during the addition of new pages
+ // clean pages may be found matching ours!
+
+ time_t db_first_time_s, db_last_time_s, db_update_every_s;
+ mrg_metric_get_retention(main_mrg, metric, &db_first_time_s, &db_last_time_s, &db_update_every_s);
+ handle->page_end_time_ut = (usec_t)db_last_time_s * USEC_PER_SEC;
return (STORAGE_COLLECT_HANDLE *)handle;
}
/* The page must be populated and referenced */
-static int page_has_only_empty_metrics(struct rrdeng_page_descr *descr)
-{
- switch(descr->type) {
+static bool page_has_only_empty_metrics(struct rrdeng_collect_handle *handle) {
+ switch(handle->type) {
case PAGE_METRICS: {
- size_t slots = descr->page_length / PAGE_POINT_SIZE_BYTES(descr);
- storage_number *array = (storage_number *)descr->pg_cache_descr->page;
+ size_t slots = handle->page_position;
+ storage_number *array = (storage_number *)pgc_page_data(handle->page);
for (size_t i = 0 ; i < slots; ++i) {
if(does_storage_number_exist(array[i]))
- return 0;
+ return false;
}
}
break;
case PAGE_TIER: {
- size_t slots = descr->page_length / PAGE_POINT_SIZE_BYTES(descr);
- storage_number_tier1_t *array = (storage_number_tier1_t *)descr->pg_cache_descr->page;
+ size_t slots = handle->page_position;
+ storage_number_tier1_t *array = (storage_number_tier1_t *)pgc_page_data(handle->page);
for (size_t i = 0 ; i < slots; ++i) {
if(fpclassify(array[i].sum_value) != FP_NAN)
- return 0;
+ return false;
}
}
break;
@@ -252,422 +309,585 @@ static int page_has_only_empty_metrics(struct rrdeng_page_descr *descr)
default: {
static bool logged = false;
if(!logged) {
- error("DBENGINE: cannot check page for nulls on unknown page type id %d", descr->type);
+ error("DBENGINE: cannot check page for nulls on unknown page type id %d", (mrg_metric_ctx(handle->metric))->config.page_type);
logged = true;
}
- return 0;
+ return false;
}
}
- return 1;
+ return true;
}
void rrdeng_store_metric_flush_current_page(STORAGE_COLLECT_HANDLE *collection_handle) {
struct rrdeng_collect_handle *handle = (struct rrdeng_collect_handle *)collection_handle;
- // struct rrdeng_metric_handle *metric_handle = (struct rrdeng_metric_handle *)handle->metric_handle;
- struct rrdengine_instance *ctx = handle->page_index->ctx;
- struct rrdeng_page_descr *descr = handle->descr;
-
- if (unlikely(!ctx)) return;
- if (unlikely(!descr)) return;
-
- if (likely(descr->page_length)) {
- int page_is_empty;
-
- rrd_stat_atomic_add(&ctx->stats.metric_API_producers, -1);
-
- page_is_empty = page_has_only_empty_metrics(descr);
- if (page_is_empty) {
- print_page_cache_descr(descr, "Page has empty metrics only, deleting", true);
- pg_cache_put(ctx, descr);
- pg_cache_punch_hole(ctx, descr, 1, 0, NULL);
- } else
- rrdeng_commit_page(ctx, descr, handle->page_correlation_id);
- } else {
- dbengine_page_free(descr->pg_cache_descr->page);
- rrdeng_destroy_pg_cache_descr(ctx, descr->pg_cache_descr);
- rrdeng_page_descr_freez(descr);
+
+ if (unlikely(!handle->page))
+ return;
+
+ if(!handle->page_position || page_has_only_empty_metrics(handle))
+ pgc_page_to_clean_evict_or_release(main_cache, handle->page);
+
+ else {
+ check_completed_page_consistency(handle);
+ mrg_metric_set_clean_latest_time_s(main_mrg, handle->metric, pgc_page_end_time_s(handle->page));
+ pgc_page_hot_to_dirty_and_release(main_cache, handle->page);
}
- handle->descr = NULL;
-}
-static void rrdeng_store_metric_next_internal(STORAGE_COLLECT_HANDLE *collection_handle,
- usec_t point_in_time_ut,
- NETDATA_DOUBLE n,
- NETDATA_DOUBLE min_value,
- NETDATA_DOUBLE max_value,
- uint16_t count,
- uint16_t anomaly_count,
- SN_FLAGS flags)
-{
- struct rrdeng_collect_handle *handle = (struct rrdeng_collect_handle *)collection_handle;
- struct pg_cache_page_index *page_index = handle->page_index;
- struct rrdengine_instance *ctx = handle->page_index->ctx;
- struct page_cache *pg_cache = &ctx->pg_cache;
- struct rrdeng_page_descr *descr = handle->descr;
+ mrg_metric_set_hot_latest_time_s(main_mrg, handle->metric, 0);
- void *page;
- uint8_t must_flush_unaligned_page = 0, perfect_page_alignment = 0;
+ handle->page = NULL;
+ handle->page_flags = 0;
+ handle->page_position = 0;
+ handle->page_entries_max = 0;
- if (descr) {
- /* Make alignment decisions */
+ // important!
+ // we should never zero page end time ut, because this will allow
+ // collection to go back in time
+ // handle->page_end_time_ut = 0;
+ // handle->page_start_time_ut;
+
+ check_and_fix_mrg_update_every(handle);
+}
+
+static void rrdeng_store_metric_create_new_page(struct rrdeng_collect_handle *handle,
+ struct rrdengine_instance *ctx,
+ usec_t point_in_time_ut,
+ void *data,
+ size_t data_size) {
+ time_t point_in_time_s = (time_t)(point_in_time_ut / USEC_PER_SEC);
+ const time_t update_every_s = (time_t)(handle->update_every_ut / USEC_PER_SEC);
+
+ PGC_ENTRY page_entry = {
+ .section = (Word_t) ctx,
+ .metric_id = mrg_metric_id(main_mrg, handle->metric),
+ .start_time_s = point_in_time_s,
+ .end_time_s = point_in_time_s,
+ .size = data_size,
+ .data = data,
+ .update_every_s = update_every_s,
+ .hot = true
+ };
+
+ size_t conflicts = 0;
+ bool added = true;
+ PGC_PAGE *page = pgc_page_add_and_acquire(main_cache, page_entry, &added);
+ while (unlikely(!added)) {
+ conflicts++;
+
+ char uuid[UUID_STR_LEN + 1];
+ uuid_unparse(*mrg_metric_uuid(main_mrg, handle->metric), uuid);
#ifdef NETDATA_INTERNAL_CHECKS
- if(descr->end_time_ut + page_index->latest_update_every_s * USEC_PER_SEC != point_in_time_ut) {
- char buffer[200 + 1];
- snprintfz(buffer, 200,
- "metrics collected are %s, end_time_ut = %llu, point_in_time_ut = %llu, update_every = %u, delta = %llu",
- (point_in_time_ut / USEC_PER_SEC - descr->end_time_ut / USEC_PER_SEC > page_index->latest_update_every_s)?"far apart":"not aligned",
- descr->end_time_ut / USEC_PER_SEC,
- point_in_time_ut / USEC_PER_SEC,
- page_index->latest_update_every_s,
- point_in_time_ut / USEC_PER_SEC - descr->end_time_ut / USEC_PER_SEC);
- print_page_cache_descr(descr, buffer, false);
- }
+ internal_error(true,
+#else
+ error_limit_static_global_var(erl, 1, 0);
+ error_limit(&erl,
#endif
+ "DBENGINE: metric '%s' new page from %ld to %ld, update every %ld, has a conflict in main cache "
+ "with existing %s%s page from %ld to %ld, update every %ld - "
+ "is it collected more than once?",
+ uuid,
+ page_entry.start_time_s, page_entry.end_time_s, (time_t)page_entry.update_every_s,
+ pgc_is_page_hot(page) ? "hot" : "not-hot",
+ pgc_page_data(page) == DBENGINE_EMPTY_PAGE ? " gap" : "",
+ pgc_page_start_time_s(page), pgc_page_end_time_s(page), pgc_page_update_every_s(page)
+ );
+
+ pgc_page_release(main_cache, page);
+
+ point_in_time_ut -= handle->update_every_ut;
+ point_in_time_s = (time_t)(point_in_time_ut / USEC_PER_SEC);
+ page_entry.start_time_s = point_in_time_s;
+ page_entry.end_time_s = point_in_time_s;
+ page = pgc_page_add_and_acquire(main_cache, page_entry, &added);
+ }
- if (descr->page_length == handle->alignment->page_length) {
- /* this is the leading dimension that defines chart alignment */
- perfect_page_alignment = 1;
- }
- /* is the metric far enough out of alignment with the others? */
- if (unlikely(descr->page_length + PAGE_POINT_SIZE_BYTES(descr) < handle->alignment->page_length)) {
- handle->unaligned_page = 1;
- print_page_cache_descr(descr, "Metric page is not aligned with chart", true);
- }
- if (unlikely(handle->unaligned_page &&
- /* did the other metrics change page? */
- handle->alignment->page_length <= PAGE_POINT_SIZE_BYTES(descr))) {
- print_page_cache_descr(descr, "must_flush_unaligned_page = 1", true);
- must_flush_unaligned_page = 1;
- handle->unaligned_page = 0;
- }
+ handle->page_entries_max = data_size / CTX_POINT_SIZE_BYTES(ctx);
+ handle->page_start_time_ut = point_in_time_ut;
+ handle->page_end_time_ut = point_in_time_ut;
+ handle->page_position = 1; // zero is already in our data
+ handle->page = page;
+ handle->page_flags = conflicts? RRDENG_PAGE_CONFLICT : 0;
+
+ if(point_in_time_s > max_acceptable_collected_time())
+ handle->page_flags |= RRDENG_PAGE_CREATED_IN_FUTURE;
+
+ check_and_fix_mrg_update_every(handle);
+}
+
+static void *rrdeng_alloc_new_metric_data(struct rrdeng_collect_handle *handle, size_t *data_size, usec_t point_in_time_ut) {
+ struct rrdengine_instance *ctx = mrg_metric_ctx(handle->metric);
+ size_t size;
+
+ if(handle->options & RRDENG_FIRST_PAGE_ALLOCATED) {
+ // any page except the first
+ size = tier_page_size[ctx->config.tier];
}
- if (unlikely(NULL == descr ||
- descr->page_length + PAGE_POINT_SIZE_BYTES(descr) > RRDENG_BLOCK_SIZE ||
- must_flush_unaligned_page)) {
+ else {
+ size_t final_slots = 0;
- if(descr) {
- print_page_cache_descr(descr, "flushing metric", true);
- rrdeng_store_metric_flush_current_page(collection_handle);
+ // the first page
+ handle->options |= RRDENG_FIRST_PAGE_ALLOCATED;
+ size_t max_size = tier_page_size[ctx->config.tier];
+ size_t max_slots = max_size / CTX_POINT_SIZE_BYTES(ctx);
+
+ if(handle->alignment->initial_slots) {
+ final_slots = handle->alignment->initial_slots;
}
+ else {
+ max_slots -= 3;
- page = rrdeng_create_page(ctx, &page_index->id, &descr);
- fatal_assert(page);
+ size_t smaller_slot = indexing_partition((Word_t)handle->alignment, max_slots);
+ final_slots = smaller_slot;
- descr->update_every_s = page_index->latest_update_every_s;
- handle->descr = descr;
+ time_t now_s = (time_t)(point_in_time_ut / USEC_PER_SEC);
+ size_t current_pos = (now_s % max_slots);
- handle->page_correlation_id = rrd_atomic_fetch_add(&pg_cache->committed_page_index.latest_corr_id, 1);
+ if(current_pos > final_slots)
+ final_slots += max_slots - current_pos;
- if (0 == handle->alignment->page_length) {
- /* this is the leading dimension that defines chart alignment */
- perfect_page_alignment = 1;
+ else if(current_pos < final_slots)
+ final_slots -= current_pos;
+
+ if(final_slots < 3) {
+ final_slots += 3;
+ smaller_slot += 3;
+
+ if(smaller_slot >= max_slots)
+ smaller_slot -= max_slots;
+ }
+
+ max_slots += 3;
+ handle->alignment->initial_slots = smaller_slot + 3;
+
+ internal_fatal(handle->alignment->initial_slots < 3 || handle->alignment->initial_slots >= max_slots, "ooops! wrong distribution of metrics across time");
+ internal_fatal(final_slots < 3 || final_slots >= max_slots, "ooops! wrong distribution of metrics across time");
}
+
+ size = final_slots * CTX_POINT_SIZE_BYTES(ctx);
}
- page = descr->pg_cache_descr->page;
+ *data_size = size;
+ return dbengine_page_alloc(size);
+}
+
+static void rrdeng_store_metric_append_point(STORAGE_COLLECT_HANDLE *collection_handle,
+ const usec_t point_in_time_ut,
+ const NETDATA_DOUBLE n,
+ const NETDATA_DOUBLE min_value,
+ const NETDATA_DOUBLE max_value,
+ const uint16_t count,
+ const uint16_t anomaly_count,
+ const SN_FLAGS flags)
+{
+ struct rrdeng_collect_handle *handle = (struct rrdeng_collect_handle *)collection_handle;
+ struct rrdengine_instance *ctx = mrg_metric_ctx(handle->metric);
- switch (descr->type) {
+ bool perfect_page_alignment = false;
+ void *data;
+ size_t data_size;
+
+ if(likely(handle->page)) {
+ /* Make alignment decisions */
+ if (handle->page_position == handle->alignment->page_position) {
+ /* this is the leading dimension that defines chart alignment */
+ perfect_page_alignment = true;
+ }
+
+ /* is the metric far enough out of alignment with the others? */
+ if (unlikely(handle->page_position + 1 < handle->alignment->page_position))
+ handle->options |= RRDENG_CHO_UNALIGNED;
+
+ if (unlikely((handle->options & RRDENG_CHO_UNALIGNED) &&
+ /* did the other metrics change page? */
+ handle->alignment->page_position <= 1)) {
+ handle->options &= ~RRDENG_CHO_UNALIGNED;
+ handle->page_flags |= RRDENG_PAGE_UNALIGNED;
+ rrdeng_store_metric_flush_current_page(collection_handle);
+
+ data = rrdeng_alloc_new_metric_data(handle, &data_size, point_in_time_ut);
+ }
+ else {
+ data = pgc_page_data(handle->page);
+ data_size = pgc_page_data_size(main_cache, handle->page);
+ }
+ }
+ else
+ data = rrdeng_alloc_new_metric_data(handle, &data_size, point_in_time_ut);
+
+ switch (ctx->config.page_type) {
case PAGE_METRICS: {
- ((storage_number *)page)[descr->page_length / PAGE_POINT_SIZE_BYTES(descr)] = pack_storage_number(n, flags);
+ storage_number *tier0_metric_data = data;
+ tier0_metric_data[handle->page_position] = pack_storage_number(n, flags);
}
break;
case PAGE_TIER: {
+ storage_number_tier1_t *tier12_metric_data = data;
storage_number_tier1_t number_tier1;
number_tier1.sum_value = (float)n;
number_tier1.min_value = (float)min_value;
number_tier1.max_value = (float)max_value;
number_tier1.anomaly_count = anomaly_count;
number_tier1.count = count;
- ((storage_number_tier1_t *)page)[descr->page_length / PAGE_POINT_SIZE_BYTES(descr)] = number_tier1;
+ tier12_metric_data[handle->page_position] = number_tier1;
}
break;
default: {
static bool logged = false;
if(!logged) {
- error("DBENGINE: cannot store metric on unknown page type id %d", descr->type);
+ error("DBENGINE: cannot store metric on unknown page type id %d", ctx->config.page_type);
logged = true;
}
}
break;
}
- pg_cache_atomic_set_pg_info(descr, point_in_time_ut, descr->page_length + PAGE_POINT_SIZE_BYTES(descr));
+ if(unlikely(!handle->page)){
+ rrdeng_store_metric_create_new_page(handle, ctx, point_in_time_ut, data, data_size);
+ // handle->position is set to 1 already
- if (perfect_page_alignment)
- handle->alignment->page_length = descr->page_length;
- if (unlikely(INVALID_TIME == descr->start_time_ut)) {
- unsigned long new_metric_API_producers, old_metric_API_max_producers, ret_metric_API_max_producers;
- descr->start_time_ut = point_in_time_ut;
-
- new_metric_API_producers = rrd_atomic_add_fetch(&ctx->stats.metric_API_producers, 1);
- while (unlikely(new_metric_API_producers > (old_metric_API_max_producers = ctx->metric_API_max_producers))) {
- /* Increase ctx->metric_API_max_producers */
- ret_metric_API_max_producers = ulong_compare_and_swap(&ctx->metric_API_max_producers,
- old_metric_API_max_producers,
- new_metric_API_producers);
- if (old_metric_API_max_producers == ret_metric_API_max_producers) {
- /* success */
- break;
- }
+ if (0 == handle->alignment->page_position) {
+ /* this is the leading dimension that defines chart alignment */
+ perfect_page_alignment = true;
+ }
+ }
+ else {
+ // update an existing page
+ pgc_page_hot_set_end_time_s(main_cache, handle->page, (time_t) (point_in_time_ut / USEC_PER_SEC));
+ handle->page_end_time_ut = point_in_time_ut;
+
+ if(unlikely(++handle->page_position >= handle->page_entries_max)) {
+ internal_fatal(handle->page_position > handle->page_entries_max, "DBENGINE: exceeded page max number of points");
+ handle->page_flags |= RRDENG_PAGE_FULL;
+ rrdeng_store_metric_flush_current_page(collection_handle);
}
+ }
+
+ if (perfect_page_alignment)
+ handle->alignment->page_position = handle->page_position;
+
+ // update the metric information
+ mrg_metric_set_hot_latest_time_s(main_mrg, handle->metric, (time_t) (point_in_time_ut / USEC_PER_SEC));
+}
+
+static void store_metric_next_error_log(struct rrdeng_collect_handle *handle, usec_t point_in_time_ut, const char *msg) {
+ time_t point_in_time_s = (time_t)(point_in_time_ut / USEC_PER_SEC);
+ char uuid[UUID_STR_LEN + 1];
+ uuid_unparse(*mrg_metric_uuid(main_mrg, handle->metric), uuid);
- pg_cache_insert(ctx, page_index, descr);
- } else {
- pg_cache_add_new_metric_time(page_index, descr);
+ BUFFER *wb = NULL;
+ if(handle->page && handle->page_flags) {
+ wb = buffer_create(0, NULL);
+ collect_page_flags_to_buffer(wb, handle->page_flags);
}
-// {
-// unsigned char u[16] = { 0x0C, 0x0A, 0x40, 0xD6, 0x2A, 0x43, 0x4A, 0x7C, 0x95, 0xF7, 0xD1, 0x1E, 0x0C, 0x9E, 0x8A, 0xE7 };
-// if(uuid_compare(u, page_index->id) == 0) {
-// char buffer[100];
-// snprintfz(buffer, 100, "store system.cpu, collect:%u, page_index first:%u, last:%u",
-// (uint32_t)(point_in_time / USEC_PER_SEC),
-// (uint32_t)(page_index->oldest_time / USEC_PER_SEC),
-// (uint32_t)(page_index->latest_time / USEC_PER_SEC));
-//
-// print_page_cache_descr(descr, buffer, false);
-// }
-// }
+ error_limit_static_global_var(erl, 1, 0);
+ error_limit(&erl,
+ "DBENGINE: metric '%s' collected point at %ld, %s last collection at %ld, "
+ "update every %ld, %s page from %ld to %ld, position %u (of %u), flags: %s",
+ uuid,
+ point_in_time_s,
+ msg,
+ (time_t)(handle->page_end_time_ut / USEC_PER_SEC),
+ (time_t)(handle->update_every_ut / USEC_PER_SEC),
+ handle->page ? "current" : "*LAST*",
+ (time_t)(handle->page_start_time_ut / USEC_PER_SEC),
+ (time_t)(handle->page_end_time_ut / USEC_PER_SEC),
+ handle->page_position, handle->page_entries_max,
+ wb ? buffer_tostring(wb) : ""
+ );
+
+ buffer_free(wb);
}
void rrdeng_store_metric_next(STORAGE_COLLECT_HANDLE *collection_handle,
- usec_t point_in_time_ut,
- NETDATA_DOUBLE n,
- NETDATA_DOUBLE min_value,
- NETDATA_DOUBLE max_value,
- uint16_t count,
- uint16_t anomaly_count,
- SN_FLAGS flags)
+ const usec_t point_in_time_ut,
+ const NETDATA_DOUBLE n,
+ const NETDATA_DOUBLE min_value,
+ const NETDATA_DOUBLE max_value,
+ const uint16_t count,
+ const uint16_t anomaly_count,
+ const SN_FLAGS flags)
{
struct rrdeng_collect_handle *handle = (struct rrdeng_collect_handle *)collection_handle;
- struct pg_cache_page_index *page_index = handle->page_index;
- struct rrdeng_page_descr *descr = handle->descr;
-
- if(likely(descr)) {
- usec_t last_point_in_time_ut = descr->end_time_ut;
- usec_t update_every_ut = page_index->latest_update_every_s * USEC_PER_SEC;
- size_t points_gap = (point_in_time_ut <= last_point_in_time_ut) ?
- (size_t)0 :
- (size_t)((point_in_time_ut - last_point_in_time_ut) / update_every_ut);
-
- if(unlikely(points_gap != 1)) {
- if (unlikely(points_gap <= 0)) {
- time_t now = now_realtime_sec();
- static __thread size_t counter = 0;
- static __thread time_t last_time_logged = 0;
- counter++;
-
- if(now - last_time_logged > 600) {
- error("DBENGINE: collected point is in the past (repeated %zu times in the last %zu secs). Ignoring these data collection points.",
- counter, (size_t)(last_time_logged?(now - last_time_logged):0));
-
- last_time_logged = now;
- counter = 0;
- }
- return;
- }
- size_t point_size = PAGE_POINT_SIZE_BYTES(descr);
- size_t page_size_in_points = RRDENG_BLOCK_SIZE / point_size;
- size_t used_points = descr->page_length / point_size;
- size_t remaining_points_in_page = page_size_in_points - used_points;
+#ifdef NETDATA_INTERNAL_CHECKS
+ if(unlikely(point_in_time_ut > (usec_t)max_acceptable_collected_time() * USEC_PER_SEC))
+ handle->page_flags |= RRDENG_PAGE_FUTURE_POINT;
+#endif
+
+ if(likely(handle->page_end_time_ut + handle->update_every_ut == point_in_time_ut)) {
+ // happy path
+ ;
+ }
+ else if(unlikely(point_in_time_ut < handle->page_end_time_ut)) {
+ handle->page_flags |= RRDENG_PAGE_PAST_COLLECTION;
+ store_metric_next_error_log(handle, point_in_time_ut, "is older than the");
+ return;
+ }
- bool new_point_is_aligned = true;
- if(unlikely((point_in_time_ut - last_point_in_time_ut) / points_gap != update_every_ut))
- new_point_is_aligned = false;
+ else if(unlikely(point_in_time_ut == handle->page_end_time_ut)) {
+ handle->page_flags |= RRDENG_PAGE_REPEATED_COLLECTION;
+ store_metric_next_error_log(handle, point_in_time_ut, "is at the same time as the");
+ return;
+ }
- if(unlikely(points_gap > remaining_points_in_page || !new_point_is_aligned)) {
-// char buffer[200];
-// snprintfz(buffer, 200, "data collection skipped %zu points, last stored point %llu, new point %llu, update every %d. Cutting page.",
-// points_gap, last_point_in_time_ut / USEC_PER_SEC, point_in_time_ut / USEC_PER_SEC, page_index->latest_update_every_s);
-// print_page_cache_descr(descr, buffer, false);
+ else if(handle->page) {
+ usec_t delta_ut = point_in_time_ut - handle->page_end_time_ut;
+ if(unlikely(delta_ut < handle->update_every_ut)) {
+ handle->page_flags |= RRDENG_PAGE_STEP_TOO_SMALL;
+ rrdeng_store_metric_flush_current_page(collection_handle);
+ }
+ else if(unlikely(delta_ut % handle->update_every_ut)) {
+ handle->page_flags |= RRDENG_PAGE_STEP_UNALIGNED;
+ rrdeng_store_metric_flush_current_page(collection_handle);
+ }
+ else {
+ size_t points_gap = delta_ut / handle->update_every_ut;
+ size_t page_remaining_points = handle->page_entries_max - handle->page_position;
+
+ if(points_gap >= page_remaining_points) {
+ handle->page_flags |= RRDENG_PAGE_BIG_GAP;
rrdeng_store_metric_flush_current_page(collection_handle);
}
else {
-// char buffer[200];
-// snprintfz(buffer, 200, "data collection skipped %zu points, last stored point %llu, new point %llu, update every %d. Filling the gap.",
-// points_gap, last_point_in_time_ut / USEC_PER_SEC, point_in_time_ut / USEC_PER_SEC, page_index->latest_update_every_s);
-// print_page_cache_descr(descr, buffer, false);
-
// loop to fill the gap
- usec_t step_ut = page_index->latest_update_every_s * USEC_PER_SEC;
- usec_t last_point_filled_ut = last_point_in_time_ut + step_ut;
-
- while (last_point_filled_ut < point_in_time_ut) {
- rrdeng_store_metric_next_internal(
- collection_handle, last_point_filled_ut, NAN, NAN, NAN,
- 1, 0, SN_EMPTY_SLOT);
-
- last_point_filled_ut += step_ut;
+ handle->page_flags |= RRDENG_PAGE_GAP;
+
+ usec_t stop_ut = point_in_time_ut - handle->update_every_ut;
+ for(usec_t this_ut = handle->page_end_time_ut + handle->update_every_ut;
+ this_ut <= stop_ut ;
+ this_ut = handle->page_end_time_ut + handle->update_every_ut) {
+ rrdeng_store_metric_append_point(
+ collection_handle,
+ this_ut,
+ NAN, NAN, NAN,
+ 1, 0,
+ SN_EMPTY_SLOT);
}
}
}
}
- rrdeng_store_metric_next_internal(collection_handle, point_in_time_ut, n, min_value, max_value, count, anomaly_count, flags);
+ rrdeng_store_metric_append_point(collection_handle,
+ point_in_time_ut,
+ n, min_value, max_value,
+ count, anomaly_count,
+ flags);
}
-
/*
* Releases the database reference from the handle for storing metrics.
* Returns 1 if it's safe to delete the dimension.
*/
int rrdeng_store_metric_finalize(STORAGE_COLLECT_HANDLE *collection_handle) {
struct rrdeng_collect_handle *handle = (struct rrdeng_collect_handle *)collection_handle;
- struct pg_cache_page_index *page_index = handle->page_index;
-
- uint8_t can_delete_metric = 0;
+ struct rrdengine_instance *ctx = mrg_metric_ctx(handle->metric);
+ handle->page_flags |= RRDENG_PAGE_COLLECT_FINALIZE;
rrdeng_store_metric_flush_current_page(collection_handle);
- uv_rwlock_wrlock(&page_index->lock);
+ rrdeng_page_alignment_release(handle->alignment);
- if (!--page_index->writers && !page_index->page_count)
- can_delete_metric = 1;
+ __atomic_sub_fetch(&ctx->atomic.collectors_running, 1, __ATOMIC_RELAXED);
+ if(!(handle->options & RRDENG_1ST_METRIC_WRITER))
+ __atomic_sub_fetch(&ctx->atomic.collectors_running_duplicate, 1, __ATOMIC_RELAXED);
- uv_rwlock_wrunlock(&page_index->lock);
+ if((handle->options & RRDENG_1ST_METRIC_WRITER) && !mrg_metric_clear_writer(main_mrg, handle->metric))
+ internal_fatal(true, "DBENGINE: metric is already released");
- rrdeng_page_alignment_release(handle->alignment);
+ time_t first_time_s, last_time_s, update_every_s;
+ mrg_metric_get_retention(main_mrg, handle->metric, &first_time_s, &last_time_s, &update_every_s);
+
+ mrg_metric_release(main_mrg, handle->metric);
freez(handle);
- return can_delete_metric;
+ if(!first_time_s && !last_time_s)
+ return 1;
+
+ return 0;
}
void rrdeng_store_metric_change_collection_frequency(STORAGE_COLLECT_HANDLE *collection_handle, int update_every) {
struct rrdeng_collect_handle *handle = (struct rrdeng_collect_handle *)collection_handle;
- struct pg_cache_page_index *page_index = handle->page_index;
+ check_and_fix_mrg_update_every(handle);
+
+ METRIC *metric = handle->metric;
+ usec_t update_every_ut = (usec_t)update_every * USEC_PER_SEC;
+
+ if(update_every_ut == handle->update_every_ut)
+ return;
+
+ handle->page_flags |= RRDENG_PAGE_UPDATE_EVERY_CHANGE;
rrdeng_store_metric_flush_current_page(collection_handle);
- uv_rwlock_rdlock(&page_index->lock);
- page_index->latest_update_every_s = update_every;
- uv_rwlock_rdunlock(&page_index->lock);
+ mrg_metric_set_update_every(main_mrg, metric, update_every);
+ handle->update_every_ut = update_every_ut;
}
// ----------------------------------------------------------------------------
// query ops
-//static inline uint32_t *pginfo_to_dt(struct rrdeng_page_info *page_info)
-//{
-// return (uint32_t *)&page_info->scratch[0];
-//}
-//
-//static inline uint32_t *pginfo_to_points(struct rrdeng_page_info *page_info)
-//{
-// return (uint32_t *)&page_info->scratch[sizeof(uint32_t)];
-//}
-//
+#ifdef NETDATA_INTERNAL_CHECKS
+SPINLOCK global_query_handle_spinlock = NETDATA_SPINLOCK_INITIALIZER;
+static struct rrdeng_query_handle *global_query_handle_ll = NULL;
+static void register_query_handle(struct rrdeng_query_handle *handle) {
+ handle->query_pid = gettid();
+ handle->started_time_s = now_realtime_sec();
+
+ netdata_spinlock_lock(&global_query_handle_spinlock);
+ DOUBLE_LINKED_LIST_APPEND_ITEM_UNSAFE(global_query_handle_ll, handle, prev, next);
+ netdata_spinlock_unlock(&global_query_handle_spinlock);
+}
+static void unregister_query_handle(struct rrdeng_query_handle *handle) {
+ netdata_spinlock_lock(&global_query_handle_spinlock);
+ DOUBLE_LINKED_LIST_REMOVE_ITEM_UNSAFE(global_query_handle_ll, handle, prev, next);
+ netdata_spinlock_unlock(&global_query_handle_spinlock);
+}
+#else
+static void register_query_handle(struct rrdeng_query_handle *handle __maybe_unused) {
+ ;
+}
+static void unregister_query_handle(struct rrdeng_query_handle *handle __maybe_unused) {
+ ;
+}
+#endif
+
/*
* Gets a handle for loading metrics from the database.
* The handle must be released with rrdeng_load_metric_final().
*/
-void rrdeng_load_metric_init(STORAGE_METRIC_HANDLE *db_metric_handle, struct storage_engine_query_handle *rrdimm_handle, time_t start_time_s, time_t end_time_s)
+void rrdeng_load_metric_init(STORAGE_METRIC_HANDLE *db_metric_handle,
+ struct storage_engine_query_handle *rrddim_handle,
+ time_t start_time_s,
+ time_t end_time_s,
+ STORAGE_PRIORITY priority)
{
- struct pg_cache_page_index *page_index = (struct pg_cache_page_index *)db_metric_handle;
- struct rrdengine_instance *ctx = page_index->ctx;
+ usec_t started_ut = now_monotonic_usec();
- // fprintf(stderr, "%s: %s/%s start time %ld, end time %ld\n", __FUNCTION__ , rd->rrdset->name, rd->name, start_time, end_time);
+ netdata_thread_disable_cancelability();
+ METRIC *metric = (METRIC *)db_metric_handle;
+ struct rrdengine_instance *ctx = mrg_metric_ctx(metric);
struct rrdeng_query_handle *handle;
- unsigned pages_nr;
- if(!page_index->latest_update_every_s)
- page_index->latest_update_every_s = default_rrd_update_every;
+ handle = rrdeng_query_handle_get();
+ register_query_handle(handle);
- rrdimm_handle->start_time_s = start_time_s;
- rrdimm_handle->end_time_s = end_time_s;
+ if(unlikely(priority < STORAGE_PRIORITY_HIGH))
+ priority = STORAGE_PRIORITY_HIGH;
+ else if(unlikely(priority > STORAGE_PRIORITY_BEST_EFFORT))
+ priority = STORAGE_PRIORITY_BEST_EFFORT;
- handle = callocz(1, sizeof(struct rrdeng_query_handle));
- handle->wanted_start_time_s = start_time_s;
- handle->now_s = start_time_s;
- handle->position = 0;
handle->ctx = ctx;
- handle->descr = NULL;
- handle->dt_s = page_index->latest_update_every_s;
- rrdimm_handle->handle = (STORAGE_QUERY_HANDLE *)handle;
- pages_nr = pg_cache_preload(ctx, &page_index->id, start_time_s * USEC_PER_SEC, end_time_s * USEC_PER_SEC,
- NULL, &handle->page_index);
- if (unlikely(NULL == handle->page_index || 0 == pages_nr))
- // there are no metrics to load
- handle->wanted_start_time_s = INVALID_TIME;
-}
+ handle->metric = metric;
+ handle->priority = priority;
+
+ // IMPORTANT!
+ // It is crucial not to exceed the db boundaries, because dbengine
+ // now has gap caching, so when a gap is detected a negative page
+ // is inserted into the main cache, to avoid scanning the journals
+ // again for pages matching the gap.
+
+ time_t db_first_time_s, db_last_time_s, db_update_every_s;
+ mrg_metric_get_retention(main_mrg, metric, &db_first_time_s, &db_last_time_s, &db_update_every_s);
+
+ if(is_page_in_time_range(start_time_s, end_time_s, db_first_time_s, db_last_time_s) == PAGE_IS_IN_RANGE) {
+ handle->start_time_s = MAX(start_time_s, db_first_time_s);
+ handle->end_time_s = MIN(end_time_s, db_last_time_s);
+ handle->now_s = handle->start_time_s;
+
+ handle->dt_s = db_update_every_s;
+ if (!handle->dt_s) {
+ handle->dt_s = default_rrd_update_every;
+ mrg_metric_set_update_every_s_if_zero(main_mrg, metric, default_rrd_update_every);
+ }
-static int rrdeng_load_page_next(struct storage_engine_query_handle *rrdimm_handle, bool debug_this __maybe_unused) {
- struct rrdeng_query_handle *handle = (struct rrdeng_query_handle *)rrdimm_handle->handle;
+ rrddim_handle->handle = (STORAGE_QUERY_HANDLE *) handle;
+ rrddim_handle->start_time_s = handle->start_time_s;
+ rrddim_handle->end_time_s = handle->end_time_s;
+ rrddim_handle->priority = priority;
- struct rrdengine_instance *ctx = handle->ctx;
- struct rrdeng_page_descr *descr = handle->descr;
+ pg_cache_preload(handle);
- uint32_t page_length;
- usec_t page_end_time_ut;
- unsigned position;
+ __atomic_add_fetch(&rrdeng_cache_efficiency_stats.query_time_init, now_monotonic_usec() - started_ut, __ATOMIC_RELAXED);
+ }
+ else {
+ handle->start_time_s = start_time_s;
+ handle->end_time_s = end_time_s;
+ handle->now_s = start_time_s;
+ handle->dt_s = db_update_every_s;
+
+ rrddim_handle->handle = (STORAGE_QUERY_HANDLE *) handle;
+ rrddim_handle->start_time_s = handle->start_time_s;
+ rrddim_handle->end_time_s = 0;
+ rrddim_handle->priority = priority;
+ }
+}
- if (likely(descr)) {
- // Drop old page's reference
+static bool rrdeng_load_page_next(struct storage_engine_query_handle *rrddim_handle, bool debug_this __maybe_unused) {
+ struct rrdeng_query_handle *handle = (struct rrdeng_query_handle *)rrddim_handle->handle;
+ struct rrdengine_instance *ctx = handle->ctx;
-#ifdef NETDATA_INTERNAL_CHECKS
- rrd_stat_atomic_add(&ctx->stats.metric_API_consumers, -1);
-#endif
+ if (likely(handle->page)) {
+ // we have a page to release
+ pgc_page_release(main_cache, handle->page);
+ handle->page = NULL;
+ }
- pg_cache_put(ctx, descr);
- handle->descr = NULL;
- handle->wanted_start_time_s = (time_t)((handle->page_end_time_ut / USEC_PER_SEC) + handle->dt_s);
+ if (unlikely(handle->now_s > rrddim_handle->end_time_s))
+ return false;
- if (unlikely(handle->wanted_start_time_s > rrdimm_handle->end_time_s))
- return 1;
- }
+ size_t entries;
+ handle->page = pg_cache_lookup_next(ctx, handle->pdc, handle->now_s, handle->dt_s, &entries);
+ if (unlikely(!handle->page))
+ return false;
- usec_t wanted_start_time_ut = handle->wanted_start_time_s * USEC_PER_SEC;
- descr = pg_cache_lookup_next(ctx, handle->page_index, &handle->page_index->id,
- wanted_start_time_ut, rrdimm_handle->end_time_s * USEC_PER_SEC);
- if (NULL == descr)
- return 1;
+ internal_fatal(pgc_page_data(handle->page) == DBENGINE_EMPTY_PAGE, "Empty page returned");
-#ifdef NETDATA_INTERNAL_CHECKS
- rrd_stat_atomic_add(&ctx->stats.metric_API_consumers, 1);
-#endif
+ time_t page_start_time_s = pgc_page_start_time_s(handle->page);
+ time_t page_end_time_s = pgc_page_end_time_s(handle->page);
+ time_t page_update_every_s = pgc_page_update_every_s(handle->page);
- handle->descr = descr;
- pg_cache_atomic_get_pg_info(descr, &page_end_time_ut, &page_length);
- if (unlikely(INVALID_TIME == descr->start_time_ut || INVALID_TIME == page_end_time_ut || 0 == descr->update_every_s)) {
- error("DBENGINE: discarding invalid page descriptor (start_time = %llu, end_time = %llu, update_every_s = %d)",
- descr->start_time_ut, page_end_time_ut, descr->update_every_s);
- return 1;
- }
+ unsigned position;
+ if(likely(handle->now_s >= page_start_time_s && handle->now_s <= page_end_time_s)) {
+
+ if(unlikely(entries == 1 || page_start_time_s == page_end_time_s || !page_update_every_s)) {
+ position = 0;
+ handle->now_s = page_start_time_s;
+ }
+ else {
+ position = (handle->now_s - page_start_time_s) * (entries - 1) / (page_end_time_s - page_start_time_s);
+ time_t point_end_time_s = page_start_time_s + position * page_update_every_s;
+ while(point_end_time_s < handle->now_s && position + 1 < entries) {
+ // https://github.com/netdata/netdata/issues/14411
+ // we really need a while() here, because the delta may be
+ // 2 points at higher tiers
+ position++;
+ point_end_time_s = page_start_time_s + position * page_update_every_s;
+ }
+ handle->now_s = point_end_time_s;
+ }
- if (unlikely(descr->start_time_ut != page_end_time_ut && wanted_start_time_ut > descr->start_time_ut)) {
- // we're in the middle of the page somewhere
- unsigned entries = page_length / PAGE_POINT_SIZE_BYTES(descr);
- position = ((uint64_t)(wanted_start_time_ut - descr->start_time_ut)) * (entries - 1) /
- (page_end_time_ut - descr->start_time_ut);
+ internal_fatal(position >= entries, "DBENGINE: wrong page position calculation");
}
- else
+ else if(handle->now_s < page_start_time_s) {
+ handle->now_s = page_start_time_s;
position = 0;
+ }
+ else {
+ internal_fatal(true, "DBENGINE: this page is entirely in our past and should not be accepted for this query in the first place");
+ handle->now_s = page_end_time_s;
+ position = entries - 1;
+ }
- handle->page_end_time_ut = page_end_time_ut;
- handle->page_length = page_length;
- handle->entries = page_length / PAGE_POINT_SIZE_BYTES(descr);
- handle->page = descr->pg_cache_descr->page;
- handle->dt_s = descr->update_every_s;
+ handle->entries = entries;
handle->position = position;
-
-// if(debug_this)
-// info("DBENGINE: rrdeng_load_page_next(), "
-// "position:%d, "
-// "start_time_ut:%llu, "
-// "page_end_time_ut:%llu, "
-// "next_page_time_ut:%llu, "
-// "in_out:%s"
-// , position
-// , descr->start_time_ut
-// , page_end_time_ut
-// ,
-// wanted_start_time_ut, in_out?"true":"false"
-// );
-
- return 0;
+ handle->metric_data = pgc_page_data((PGC_PAGE *)handle->page);
+ handle->dt_s = page_update_every_s;
+ return true;
}
// Returns the metric and sets its timestamp into current_time
@@ -675,75 +895,28 @@ static int rrdeng_load_page_next(struct storage_engine_query_handle *rrdimm_hand
// IT IS REQUIRED TO **ALWAYS** KEEP TRACK OF TIME, EVEN OUTSIDE THE DATABASE BOUNDARIES
STORAGE_POINT rrdeng_load_metric_next(struct storage_engine_query_handle *rrddim_handle) {
struct rrdeng_query_handle *handle = (struct rrdeng_query_handle *)rrddim_handle->handle;
- // struct rrdeng_metric_handle *metric_handle = handle->metric_handle;
-
- struct rrdeng_page_descr *descr = handle->descr;
- time_t now = handle->now_s + handle->dt_s;
-
-// bool debug_this = false;
-// {
-// unsigned char u[16] = { 0x0C, 0x0A, 0x40, 0xD6, 0x2A, 0x43, 0x4A, 0x7C, 0x95, 0xF7, 0xD1, 0x1E, 0x0C, 0x9E, 0x8A, 0xE7 };
-// if(uuid_compare(u, handle->page_index->id) == 0) {
-// char buffer[100];
-// snprintfz(buffer, 100, "load system.cpu, now:%u, dt:%u, position:%u page_index first:%u, last:%u",
-// (uint32_t)(now),
-// (uint32_t)(handle->dt_s),
-// (uint32_t)(handle->position),
-// (uint32_t)(handle->page_index->oldest_time / USEC_PER_SEC),
-// (uint32_t)(handle->page_index->latest_time / USEC_PER_SEC));
-//
-// print_page_cache_descr(descr, buffer, false);
-// debug_this = true;
-// }
-// }
-
STORAGE_POINT sp;
- unsigned position = handle->position + 1;
- storage_number_tier1_t tier1_value;
-
- if (unlikely(INVALID_TIME == handle->wanted_start_time_s)) {
- handle->wanted_start_time_s = INVALID_TIME;
- handle->now_s = now;
- storage_point_empty(sp, now - handle->dt_s, now);
- return sp;
+
+ if (unlikely(handle->now_s > rrddim_handle->end_time_s)) {
+ storage_point_empty(sp, handle->now_s - handle->dt_s, handle->now_s);
+ goto prepare_for_next_iteration;
}
- if (unlikely(!descr || position >= handle->entries)) {
+ if (unlikely(!handle->page || handle->position >= handle->entries)) {
// We need to get a new page
- if(rrdeng_load_page_next(rrddim_handle, false)) {
- // next calls will not load any more metrics
- handle->wanted_start_time_s = INVALID_TIME;
- handle->now_s = now;
- storage_point_empty(sp, now - handle->dt_s, now);
- return sp;
- }
- descr = handle->descr;
- position = handle->position;
- now = (time_t)((descr->start_time_ut / USEC_PER_SEC) + position * descr->update_every_s);
-
-// if(debug_this) {
-// char buffer[100];
-// snprintfz(buffer, 100, "NEW PAGE system.cpu, now:%u, dt:%u, position:%u page_index first:%u, last:%u",
-// (uint32_t)(now),
-// (uint32_t)(handle->dt_s),
-// (uint32_t)(handle->position),
-// (uint32_t)(handle->page_index->oldest_time / USEC_PER_SEC),
-// (uint32_t)(handle->page_index->latest_time / USEC_PER_SEC));
-//
-// print_page_cache_descr(descr, buffer, false);
-// }
+ if (!rrdeng_load_page_next(rrddim_handle, false)) {
+ storage_point_empty(sp, handle->now_s - handle->dt_s, handle->now_s);
+ goto prepare_for_next_iteration;
+ }
}
- sp.start_time = now - handle->dt_s;
- sp.end_time = now;
-
- handle->position = position;
- handle->now_s = now;
+ sp.start_time_s = handle->now_s - handle->dt_s;
+ sp.end_time_s = handle->now_s;
- switch(descr->type) {
+ switch(handle->ctx->config.page_type) {
case PAGE_METRICS: {
- storage_number n = handle->page[position];
+ storage_number n = handle->metric_data[handle->position];
sp.min = sp.max = sp.sum = unpack_storage_number(n);
sp.flags = n & SN_USER_FLAGS;
sp.count = 1;
@@ -752,7 +925,7 @@ STORAGE_POINT rrdeng_load_metric_next(struct storage_engine_query_handle *rrddim
break;
case PAGE_TIER: {
- tier1_value = ((storage_number_tier1_t *)handle->page)[position];
+ storage_number_tier1_t tier1_value = ((storage_number_tier1_t *)handle->metric_data)[handle->position];
sp.flags = tier1_value.anomaly_count ? SN_FLAG_NONE : SN_FLAG_NOT_ANOMALOUS;
sp.count = tier1_value.count;
sp.anomaly_count = tier1_value.anomaly_count;
@@ -766,204 +939,98 @@ STORAGE_POINT rrdeng_load_metric_next(struct storage_engine_query_handle *rrddim
default: {
static bool logged = false;
if(!logged) {
- error("DBENGINE: unknown page type %d found. Cannot decode it. Ignoring its metrics.", descr->type);
+ error("DBENGINE: unknown page type %d found. Cannot decode it. Ignoring its metrics.", handle->ctx->config.page_type);
logged = true;
}
- storage_point_empty(sp, sp.start_time, sp.end_time);
+ storage_point_empty(sp, sp.start_time_s, sp.end_time_s);
}
break;
}
- if (unlikely(now >= rrddim_handle->end_time_s)) {
- // next calls will not load any more metrics
- handle->wanted_start_time_s = INVALID_TIME;
- }
+prepare_for_next_iteration:
+ internal_fatal(sp.end_time_s < rrddim_handle->start_time_s, "DBENGINE: this point is too old for this query");
+ internal_fatal(sp.end_time_s < handle->now_s, "DBENGINE: this point is too old for this point in time");
-// if(debug_this)
-// info("DBENGINE: returning point: "
-// "time from %ld to %ld // query from %ld to %ld // wanted_start_time_s %ld"
-// , sp.start_time, sp.end_time
-// , rrddim_handle->start_time_s, rrddim_handle->end_time_s
-// , handle->wanted_start_time_s
-// );
+ handle->now_s += handle->dt_s;
+ handle->position++;
return sp;
}
-int rrdeng_load_metric_is_finished(struct storage_engine_query_handle *rrdimm_handle)
-{
- struct rrdeng_query_handle *handle = (struct rrdeng_query_handle *)rrdimm_handle->handle;
- return (INVALID_TIME == handle->wanted_start_time_s);
+int rrdeng_load_metric_is_finished(struct storage_engine_query_handle *rrddim_handle) {
+ struct rrdeng_query_handle *handle = (struct rrdeng_query_handle *)rrddim_handle->handle;
+ return (handle->now_s > rrddim_handle->end_time_s);
}
/*
* Releases the database reference from the handle for loading metrics.
*/
-void rrdeng_load_metric_finalize(struct storage_engine_query_handle *rrdimm_handle)
+void rrdeng_load_metric_finalize(struct storage_engine_query_handle *rrddim_handle)
{
- struct rrdeng_query_handle *handle = (struct rrdeng_query_handle *)rrdimm_handle->handle;
- struct rrdengine_instance *ctx = handle->ctx;
- struct rrdeng_page_descr *descr = handle->descr;
+ struct rrdeng_query_handle *handle = (struct rrdeng_query_handle *)rrddim_handle->handle;
- if (descr) {
-#ifdef NETDATA_INTERNAL_CHECKS
- rrd_stat_atomic_add(&ctx->stats.metric_API_consumers, -1);
-#endif
- pg_cache_put(ctx, descr);
- }
+ if (handle->page)
+ pgc_page_release(main_cache, handle->page);
- // whatever is allocated at rrdeng_load_metric_init() should be freed here
- freez(handle);
- rrdimm_handle->handle = NULL;
-}
+ if(!pdc_release_and_destroy_if_unreferenced(handle->pdc, false, false))
+ __atomic_store_n(&handle->pdc->workers_should_stop, true, __ATOMIC_RELAXED);
-time_t rrdeng_metric_latest_time(STORAGE_METRIC_HANDLE *db_metric_handle) {
- struct pg_cache_page_index *page_index = (struct pg_cache_page_index *)db_metric_handle;
- return (time_t)(page_index->latest_time_ut / USEC_PER_SEC);
-}
-time_t rrdeng_metric_oldest_time(STORAGE_METRIC_HANDLE *db_metric_handle) {
- struct pg_cache_page_index *page_index = (struct pg_cache_page_index *)db_metric_handle;
- return (time_t)(page_index->oldest_time_ut / USEC_PER_SEC);
+ unregister_query_handle(handle);
+ rrdeng_query_handle_release(handle);
+ rrddim_handle->handle = NULL;
+ netdata_thread_enable_cancelability();
}
-int rrdeng_metric_retention_by_uuid(STORAGE_INSTANCE *si, uuid_t *dim_uuid, time_t *first_entry_t, time_t *last_entry_t)
-{
- struct page_cache *pg_cache;
- struct rrdengine_instance *ctx;
- Pvoid_t *PValue;
- struct pg_cache_page_index *page_index = NULL;
+time_t rrdeng_load_align_to_optimal_before(struct storage_engine_query_handle *rrddim_handle) {
+ struct rrdeng_query_handle *handle = (struct rrdeng_query_handle *)rrddim_handle->handle;
- ctx = (struct rrdengine_instance *)si;
- if (unlikely(!ctx)) {
- error("DBENGINE: invalid STORAGE INSTANCE to %s()", __FUNCTION__);
- return 1;
+ if(handle->pdc) {
+ rrdeng_prep_wait(handle->pdc);
+ if (handle->pdc->optimal_end_time_s > rrddim_handle->end_time_s)
+ rrddim_handle->end_time_s = handle->pdc->optimal_end_time_s;
}
- pg_cache = &ctx->pg_cache;
- uv_rwlock_rdlock(&pg_cache->metrics_index.lock);
- PValue = JudyHSGet(pg_cache->metrics_index.JudyHS_array, dim_uuid, sizeof(uuid_t));
- if (likely(NULL != PValue)) {
- page_index = *PValue;
- }
- uv_rwlock_rdunlock(&pg_cache->metrics_index.lock);
+ return rrddim_handle->end_time_s;
+}
- if (likely(page_index)) {
- *first_entry_t = page_index->oldest_time_ut / USEC_PER_SEC;
- *last_entry_t = page_index->latest_time_ut / USEC_PER_SEC;
- return 0;
- }
+time_t rrdeng_metric_latest_time(STORAGE_METRIC_HANDLE *db_metric_handle) {
+ METRIC *metric = (METRIC *)db_metric_handle;
+ time_t latest_time_s = 0;
- return 1;
-}
+ if (metric)
+ latest_time_s = mrg_metric_get_latest_time_s(main_mrg, metric);
-/* Also gets a reference for the page */
-void *rrdeng_create_page(struct rrdengine_instance *ctx, uuid_t *id, struct rrdeng_page_descr **ret_descr)
-{
- struct rrdeng_page_descr *descr;
- struct page_cache_descr *pg_cache_descr;
- void *page;
- /* TODO: check maximum number of pages in page cache limit */
-
- descr = pg_cache_create_descr();
- descr->id = id; /* TODO: add page type: metric, log, something? */
- descr->type = ctx->page_type;
- page = dbengine_page_alloc(); /*TODO: add page size */
- rrdeng_page_descr_mutex_lock(ctx, descr);
- pg_cache_descr = descr->pg_cache_descr;
- pg_cache_descr->page = page;
- pg_cache_descr->flags = RRD_PAGE_DIRTY /*| RRD_PAGE_LOCKED */ | RRD_PAGE_POPULATED /* | BEING_COLLECTED */;
- pg_cache_descr->refcnt = 1;
-
- debug(D_RRDENGINE, "Created new page:");
- if (unlikely(debug_flags & D_RRDENGINE))
- print_page_cache_descr(descr, "", true);
- rrdeng_page_descr_mutex_unlock(ctx, descr);
- *ret_descr = descr;
- return page;
+ return latest_time_s;
}
-/* The page must not be empty */
-void rrdeng_commit_page(struct rrdengine_instance *ctx, struct rrdeng_page_descr *descr,
- Word_t page_correlation_id)
-{
- struct page_cache *pg_cache = &ctx->pg_cache;
- Pvoid_t *PValue;
- unsigned nr_committed_pages;
+time_t rrdeng_metric_oldest_time(STORAGE_METRIC_HANDLE *db_metric_handle) {
+ METRIC *metric = (METRIC *)db_metric_handle;
- if (unlikely(NULL == descr)) {
- debug(D_RRDENGINE, "%s: page descriptor is NULL, page has already been force-committed.", __func__);
- return;
- }
- fatal_assert(descr->page_length);
-
- uv_rwlock_wrlock(&pg_cache->committed_page_index.lock);
- PValue = JudyLIns(&pg_cache->committed_page_index.JudyL_array, page_correlation_id, PJE0);
- *PValue = descr;
- nr_committed_pages = ++pg_cache->committed_page_index.nr_committed_pages;
- uv_rwlock_wrunlock(&pg_cache->committed_page_index.lock);
-
- if (nr_committed_pages >= pg_cache_hard_limit(ctx) / 2) {
- /* over 50% of pages have not been committed yet */
-
- if (ctx->drop_metrics_under_page_cache_pressure &&
- nr_committed_pages >= pg_cache_committed_hard_limit(ctx)) {
- /* 100% of pages are dirty */
- struct rrdeng_cmd cmd;
-
- cmd.opcode = RRDENG_INVALIDATE_OLDEST_MEMORY_PAGE;
- rrdeng_enq_cmd(&ctx->worker_config, &cmd);
- } else {
- if (0 == (unsigned long) ctx->stats.pg_cache_over_half_dirty_events) {
- /* only print the first time */
- errno = 0;
- error("Failed to flush dirty buffers quickly enough in dbengine instance \"%s\". "
- "Metric data at risk of not being stored in the database, "
- "please reduce disk load or use a faster disk.", ctx->dbfiles_path);
- }
- rrd_stat_atomic_add(&ctx->stats.pg_cache_over_half_dirty_events, 1);
- rrd_stat_atomic_add(&global_pg_cache_over_half_dirty_events, 1);
- }
- }
+ time_t oldest_time_s = 0;
+ if (metric)
+ oldest_time_s = mrg_metric_get_first_time_s(main_mrg, metric);
- pg_cache_put(ctx, descr);
+ return oldest_time_s;
}
-/* Gets a reference for the page */
-void *rrdeng_get_latest_page(struct rrdengine_instance *ctx, uuid_t *id, void **handle)
+bool rrdeng_metric_retention_by_uuid(STORAGE_INSTANCE *db_instance, uuid_t *dim_uuid, time_t *first_entry_s, time_t *last_entry_s)
{
- struct rrdeng_page_descr *descr;
- struct page_cache_descr *pg_cache_descr;
-
- debug(D_RRDENGINE, "Reading existing page:");
- descr = pg_cache_lookup(ctx, NULL, id, INVALID_TIME);
- if (NULL == descr) {
- *handle = NULL;
-
- return NULL;
+ struct rrdengine_instance *ctx = (struct rrdengine_instance *)db_instance;
+ if (unlikely(!ctx)) {
+ error("DBENGINE: invalid STORAGE INSTANCE to %s()", __FUNCTION__);
+ return false;
}
- *handle = descr;
- pg_cache_descr = descr->pg_cache_descr;
- return pg_cache_descr->page;
-}
+ METRIC *metric = mrg_metric_get_and_acquire(main_mrg, dim_uuid, (Word_t) ctx);
+ if (unlikely(!metric))
+ return false;
-/* Gets a reference for the page */
-void *rrdeng_get_page(struct rrdengine_instance *ctx, uuid_t *id, usec_t point_in_time_ut, void **handle)
-{
- struct rrdeng_page_descr *descr;
- struct page_cache_descr *pg_cache_descr;
+ time_t update_every_s;
+ mrg_metric_get_retention(main_mrg, metric, first_entry_s, last_entry_s, &update_every_s);
- debug(D_RRDENGINE, "Reading existing page:");
- descr = pg_cache_lookup(ctx, NULL, id, point_in_time_ut);
- if (NULL == descr) {
- *handle = NULL;
-
- return NULL;
- }
- *handle = descr;
- pg_cache_descr = descr->pg_cache_descr;
+ mrg_metric_release(main_mrg, metric);
- return pg_cache_descr->page;
+ return true;
}
/*
@@ -977,62 +1044,126 @@ void rrdeng_get_37_statistics(struct rrdengine_instance *ctx, unsigned long long
if (ctx == NULL)
return;
- struct page_cache *pg_cache = &ctx->pg_cache;
-
- array[0] = (uint64_t)ctx->stats.metric_API_producers;
- array[1] = (uint64_t)ctx->stats.metric_API_consumers;
- array[2] = (uint64_t)pg_cache->page_descriptors;
- array[3] = (uint64_t)pg_cache->populated_pages;
- array[4] = (uint64_t)pg_cache->committed_page_index.nr_committed_pages;
- array[5] = (uint64_t)ctx->stats.pg_cache_insertions;
- array[6] = (uint64_t)ctx->stats.pg_cache_deletions;
- array[7] = (uint64_t)ctx->stats.pg_cache_hits;
- array[8] = (uint64_t)ctx->stats.pg_cache_misses;
- array[9] = (uint64_t)ctx->stats.pg_cache_backfills;
- array[10] = (uint64_t)ctx->stats.pg_cache_evictions;
- array[11] = (uint64_t)ctx->stats.before_compress_bytes;
- array[12] = (uint64_t)ctx->stats.after_compress_bytes;
- array[13] = (uint64_t)ctx->stats.before_decompress_bytes;
- array[14] = (uint64_t)ctx->stats.after_decompress_bytes;
- array[15] = (uint64_t)ctx->stats.io_write_bytes;
- array[16] = (uint64_t)ctx->stats.io_write_requests;
- array[17] = (uint64_t)ctx->stats.io_read_bytes;
- array[18] = (uint64_t)ctx->stats.io_read_requests;
- array[19] = (uint64_t)ctx->stats.io_write_extent_bytes;
- array[20] = (uint64_t)ctx->stats.io_write_extents;
- array[21] = (uint64_t)ctx->stats.io_read_extent_bytes;
- array[22] = (uint64_t)ctx->stats.io_read_extents;
- array[23] = (uint64_t)ctx->stats.datafile_creations;
- array[24] = (uint64_t)ctx->stats.datafile_deletions;
- array[25] = (uint64_t)ctx->stats.journalfile_creations;
- array[26] = (uint64_t)ctx->stats.journalfile_deletions;
- array[27] = (uint64_t)ctx->stats.page_cache_descriptors;
- array[28] = (uint64_t)ctx->stats.io_errors;
- array[29] = (uint64_t)ctx->stats.fs_errors;
- array[30] = (uint64_t)global_io_errors;
- array[31] = (uint64_t)global_fs_errors;
- array[32] = (uint64_t)rrdeng_reserved_file_descriptors;
- array[33] = (uint64_t)ctx->stats.pg_cache_over_half_dirty_events;
- array[34] = (uint64_t)global_pg_cache_over_half_dirty_events;
- array[35] = (uint64_t)ctx->stats.flushing_pressure_page_deletions;
- array[36] = (uint64_t)global_flushing_pressure_page_deletions;
- fatal_assert(RRDENG_NR_STATS == 37);
+ array[0] = (uint64_t)__atomic_load_n(&ctx->atomic.collectors_running, __ATOMIC_RELAXED); // API producers
+ array[1] = (uint64_t)__atomic_load_n(&ctx->atomic.inflight_queries, __ATOMIC_RELAXED); // API consumers
+ array[2] = 0;
+ array[3] = 0;
+ array[4] = 0;
+ array[5] = 0; // (uint64_t)ctx->stats.pg_cache_insertions;
+ array[6] = 0; // (uint64_t)ctx->stats.pg_cache_deletions;
+ array[7] = 0; // (uint64_t)ctx->stats.pg_cache_hits;
+ array[8] = 0; // (uint64_t)ctx->stats.pg_cache_misses;
+ array[9] = 0; // (uint64_t)ctx->stats.pg_cache_backfills;
+ array[10] = 0; // (uint64_t)ctx->stats.pg_cache_evictions;
+ array[11] = (uint64_t)__atomic_load_n(&ctx->stats.before_compress_bytes, __ATOMIC_RELAXED); // used
+ array[12] = (uint64_t)__atomic_load_n(&ctx->stats.after_compress_bytes, __ATOMIC_RELAXED); // used
+ array[13] = (uint64_t)__atomic_load_n(&ctx->stats.before_decompress_bytes, __ATOMIC_RELAXED);
+ array[14] = (uint64_t)__atomic_load_n(&ctx->stats.after_decompress_bytes, __ATOMIC_RELAXED);
+ array[15] = (uint64_t)__atomic_load_n(&ctx->stats.io_write_bytes, __ATOMIC_RELAXED); // used
+ array[16] = (uint64_t)__atomic_load_n(&ctx->stats.io_write_requests, __ATOMIC_RELAXED); // used
+ array[17] = (uint64_t)__atomic_load_n(&ctx->stats.io_read_bytes, __ATOMIC_RELAXED);
+ array[18] = (uint64_t)__atomic_load_n(&ctx->stats.io_read_requests, __ATOMIC_RELAXED); // used
+ array[19] = 0; // (uint64_t)__atomic_load_n(&ctx->stats.io_write_extent_bytes, __ATOMIC_RELAXED);
+ array[20] = 0; // (uint64_t)__atomic_load_n(&ctx->stats.io_write_extents, __ATOMIC_RELAXED);
+ array[21] = 0; // (uint64_t)__atomic_load_n(&ctx->stats.io_read_extent_bytes, __ATOMIC_RELAXED);
+ array[22] = 0; // (uint64_t)__atomic_load_n(&ctx->stats.io_read_extents, __ATOMIC_RELAXED);
+ array[23] = (uint64_t)__atomic_load_n(&ctx->stats.datafile_creations, __ATOMIC_RELAXED);
+ array[24] = (uint64_t)__atomic_load_n(&ctx->stats.datafile_deletions, __ATOMIC_RELAXED);
+ array[25] = (uint64_t)__atomic_load_n(&ctx->stats.journalfile_creations, __ATOMIC_RELAXED);
+ array[26] = (uint64_t)__atomic_load_n(&ctx->stats.journalfile_deletions, __ATOMIC_RELAXED);
+ array[27] = 0; // (uint64_t)__atomic_load_n(&ctx->stats.page_cache_descriptors, __ATOMIC_RELAXED);
+ array[28] = (uint64_t)__atomic_load_n(&ctx->stats.io_errors, __ATOMIC_RELAXED);
+ array[29] = (uint64_t)__atomic_load_n(&ctx->stats.fs_errors, __ATOMIC_RELAXED);
+ array[30] = (uint64_t)__atomic_load_n(&global_io_errors, __ATOMIC_RELAXED); // used
+ array[31] = (uint64_t)__atomic_load_n(&global_fs_errors, __ATOMIC_RELAXED); // used
+ array[32] = (uint64_t)__atomic_load_n(&rrdeng_reserved_file_descriptors, __ATOMIC_RELAXED); // used
+ array[33] = 0; // (uint64_t)__atomic_load_n(&ctx->stats.pg_cache_over_half_dirty_events, __ATOMIC_RELAXED);
+ array[34] = (uint64_t)__atomic_load_n(&global_pg_cache_over_half_dirty_events, __ATOMIC_RELAXED); // used
+ array[35] = 0; // (uint64_t)__atomic_load_n(&ctx->stats.flushing_pressure_page_deletions, __ATOMIC_RELAXED);
+ array[36] = (uint64_t)__atomic_load_n(&global_flushing_pressure_page_deletions, __ATOMIC_RELAXED); // used
+ array[37] = 0; //(uint64_t)pg_cache->active_descriptors;
+
+ fatal_assert(RRDENG_NR_STATS == 38);
}
-/* Releases reference to page */
-void rrdeng_put_page(struct rrdengine_instance *ctx, void *handle)
-{
- (void)ctx;
- pg_cache_put(ctx, (struct rrdeng_page_descr *)handle);
+static void rrdeng_populate_mrg(struct rrdengine_instance *ctx) {
+ uv_rwlock_rdlock(&ctx->datafiles.rwlock);
+ size_t datafiles = 0;
+ for(struct rrdengine_datafile *df = ctx->datafiles.first; df ;df = df->next)
+ datafiles++;
+ uv_rwlock_rdunlock(&ctx->datafiles.rwlock);
+
+ size_t cpus = get_netdata_cpus() / storage_tiers;
+ if(cpus > datafiles)
+ cpus = datafiles;
+
+ if(cpus < 1)
+ cpus = 1;
+
+ if(cpus > (size_t)libuv_worker_threads)
+ cpus = (size_t)libuv_worker_threads;
+
+ if(cpus > MRG_PARTITIONS)
+ cpus = MRG_PARTITIONS;
+
+ info("DBENGINE: populating retention to MRG from %zu journal files of tier %d, using %zu threads...", datafiles, ctx->config.tier, cpus);
+
+ if(datafiles > 2) {
+ struct rrdengine_datafile *datafile;
+
+ datafile = ctx->datafiles.first->prev;
+ if(!(datafile->journalfile->v2.flags & JOURNALFILE_FLAG_IS_AVAILABLE))
+ datafile = datafile->prev;
+
+ if(datafile->journalfile->v2.flags & JOURNALFILE_FLAG_IS_AVAILABLE) {
+ journalfile_v2_populate_retention_to_mrg(ctx, datafile->journalfile);
+ datafile->populate_mrg.populated = true;
+ }
+
+ datafile = ctx->datafiles.first;
+ if(datafile->journalfile->v2.flags & JOURNALFILE_FLAG_IS_AVAILABLE) {
+ journalfile_v2_populate_retention_to_mrg(ctx, datafile->journalfile);
+ datafile->populate_mrg.populated = true;
+ }
+ }
+
+ ctx->loading.populate_mrg.size = cpus;
+ ctx->loading.populate_mrg.array = callocz(ctx->loading.populate_mrg.size, sizeof(struct completion));
+
+ for (size_t i = 0; i < ctx->loading.populate_mrg.size; i++) {
+ completion_init(&ctx->loading.populate_mrg.array[i]);
+ rrdeng_enq_cmd(ctx, RRDENG_OPCODE_CTX_POPULATE_MRG, NULL, &ctx->loading.populate_mrg.array[i],
+ STORAGE_PRIORITY_INTERNAL_DBENGINE, NULL, NULL);
+ }
+}
+
+void rrdeng_readiness_wait(struct rrdengine_instance *ctx) {
+ for (size_t i = 0; i < ctx->loading.populate_mrg.size; i++) {
+ completion_wait_for(&ctx->loading.populate_mrg.array[i]);
+ completion_destroy(&ctx->loading.populate_mrg.array[i]);
+ }
+
+ freez(ctx->loading.populate_mrg.array);
+ ctx->loading.populate_mrg.array = NULL;
+ ctx->loading.populate_mrg.size = 0;
+
+ info("DBENGINE: tier %d is ready for data collection and queries", ctx->config.tier);
+}
+
+bool rrdeng_is_legacy(STORAGE_INSTANCE *db_instance) {
+ struct rrdengine_instance *ctx = (struct rrdengine_instance *)db_instance;
+ return ctx->config.legacy;
}
+void rrdeng_exit_mode(struct rrdengine_instance *ctx) {
+ __atomic_store_n(&ctx->quiesce.exit_mode, true, __ATOMIC_RELAXED);
+}
/*
* Returns 0 on success, negative on error
*/
-int rrdeng_init(RRDHOST *host, struct rrdengine_instance **ctxp, char *dbfiles_path, unsigned page_cache_mb,
+int rrdeng_init(struct rrdengine_instance **ctxp, const char *dbfiles_path,
unsigned disk_space_mb, size_t tier) {
struct rrdengine_instance *ctx;
- int error;
uint32_t max_open_files;
max_open_files = rlimit_nofile.rlim_cur / 4;
@@ -1053,182 +1184,185 @@ int rrdeng_init(RRDHOST *host, struct rrdengine_instance **ctxp, char *dbfiles_p
if(NULL == ctxp) {
ctx = multidb_ctx[tier];
memset(ctx, 0, sizeof(*ctx));
+ ctx->config.legacy = false;
}
else {
*ctxp = ctx = callocz(1, sizeof(*ctx));
+ ctx->config.legacy = true;
}
- ctx->tier = tier;
- ctx->page_type = tier_page_type[tier];
- ctx->global_compress_alg = RRD_LZ4;
- if (page_cache_mb < RRDENG_MIN_PAGE_CACHE_SIZE_MB)
- page_cache_mb = RRDENG_MIN_PAGE_CACHE_SIZE_MB;
- ctx->max_cache_pages = page_cache_mb * (1048576LU / RRDENG_BLOCK_SIZE);
- /* try to keep 5% of the page cache free */
- ctx->cache_pages_low_watermark = (ctx->max_cache_pages * 95LLU) / 100;
+
+ ctx->config.tier = (int)tier;
+ ctx->config.page_type = tier_page_type[tier];
+ ctx->config.global_compress_alg = RRD_LZ4;
if (disk_space_mb < RRDENG_MIN_DISK_SPACE_MB)
disk_space_mb = RRDENG_MIN_DISK_SPACE_MB;
- ctx->max_disk_space = disk_space_mb * 1048576LLU;
- strncpyz(ctx->dbfiles_path, dbfiles_path, sizeof(ctx->dbfiles_path) - 1);
- ctx->dbfiles_path[sizeof(ctx->dbfiles_path) - 1] = '\0';
- if (NULL == host)
- strncpyz(ctx->machine_guid, registry_get_this_machine_guid(), GUID_LEN);
- else
- strncpyz(ctx->machine_guid, host->machine_guid, GUID_LEN);
-
- ctx->drop_metrics_under_page_cache_pressure = rrdeng_drop_metrics_under_page_cache_pressure;
- ctx->metric_API_max_producers = 0;
- ctx->quiesce = NO_QUIESCE;
- ctx->host = host;
-
- memset(&ctx->worker_config, 0, sizeof(ctx->worker_config));
- ctx->worker_config.ctx = ctx;
- init_page_cache(ctx);
- init_commit_log(ctx);
- error = init_rrd_files(ctx);
- if (error) {
- goto error_after_init_rrd_files;
- }
+ ctx->config.max_disk_space = disk_space_mb * 1048576LLU;
+ strncpyz(ctx->config.dbfiles_path, dbfiles_path, sizeof(ctx->config.dbfiles_path) - 1);
+ ctx->config.dbfiles_path[sizeof(ctx->config.dbfiles_path) - 1] = '\0';
- completion_init(&ctx->rrdengine_completion);
- fatal_assert(0 == uv_thread_create(&ctx->worker_config.thread, rrdeng_worker, &ctx->worker_config));
- /* wait for worker thread to initialize */
- completion_wait_for(&ctx->rrdengine_completion);
- completion_destroy(&ctx->rrdengine_completion);
- uv_thread_set_name_np(ctx->worker_config.thread, "LIBUV_WORKER");
- if (ctx->worker_config.error) {
- goto error_after_rrdeng_worker;
- }
-// error = metalog_init(ctx);
-// if (error) {
-// error("Failed to initialize metadata log file event loop.");
-// goto error_after_rrdeng_worker;
-// }
+ ctx->atomic.transaction_id = 1;
+ ctx->quiesce.enabled = false;
- return 0;
+ if (rrdeng_dbengine_spawn(ctx) && !init_rrd_files(ctx)) {
+ // success - we run this ctx too
+ rrdeng_populate_mrg(ctx);
+ return 0;
+ }
-error_after_rrdeng_worker:
- finalize_rrd_files(ctx);
-error_after_init_rrd_files:
- free_page_cache(ctx);
- if (!is_storage_engine_shared((STORAGE_INSTANCE *)ctx)) {
+ if (ctx->config.legacy) {
freez(ctx);
if (ctxp)
*ctxp = NULL;
}
+
rrd_stat_atomic_add(&rrdeng_reserved_file_descriptors, -RRDENG_FD_BUDGET_PER_INSTANCE);
return UV_EIO;
}
+size_t rrdeng_collectors_running(struct rrdengine_instance *ctx) {
+ return __atomic_load_n(&ctx->atomic.collectors_running, __ATOMIC_RELAXED);
+}
+
/*
* Returns 0 on success, 1 on error
*/
-int rrdeng_exit(struct rrdengine_instance *ctx)
-{
- struct rrdeng_cmd cmd;
-
- if (NULL == ctx) {
+int rrdeng_exit(struct rrdengine_instance *ctx) {
+ if (NULL == ctx)
return 1;
+
+ // FIXME - ktsaou - properly cleanup ctx
+ // 1. make sure all collectors are stopped
+ // 2. make new queries will not be accepted (this is quiesce that has already run)
+ // 3. flush this section of the main cache
+ // 4. then wait for completion
+
+ bool logged = false;
+ while(__atomic_load_n(&ctx->atomic.collectors_running, __ATOMIC_RELAXED) && !unittest_running) {
+ if(!logged) {
+ info("DBENGINE: waiting for collectors to finish on tier %d...", (ctx->config.legacy) ? -1 : ctx->config.tier);
+ logged = true;
+ }
+ sleep_usec(100 * USEC_PER_MS);
}
- /* TODO: add page to page cache */
- cmd.opcode = RRDENG_SHUTDOWN;
- rrdeng_enq_cmd(&ctx->worker_config, &cmd);
+ info("DBENGINE: flushing main cache for tier %d", (ctx->config.legacy) ? -1 : ctx->config.tier);
+ pgc_flush_all_hot_and_dirty_pages(main_cache, (Word_t)ctx);
- fatal_assert(0 == uv_thread_join(&ctx->worker_config.thread));
+ info("DBENGINE: shutting down tier %d", (ctx->config.legacy) ? -1 : ctx->config.tier);
+ struct completion completion = {};
+ completion_init(&completion);
+ rrdeng_enq_cmd(ctx, RRDENG_OPCODE_CTX_SHUTDOWN, NULL, &completion, STORAGE_PRIORITY_BEST_EFFORT, NULL, NULL);
+ completion_wait_for(&completion);
+ completion_destroy(&completion);
finalize_rrd_files(ctx);
- //metalog_exit(ctx->metalog_ctx);
- free_page_cache(ctx);
- if(!is_storage_engine_shared((STORAGE_INSTANCE *)ctx))
+ if(ctx->config.legacy)
freez(ctx);
rrd_stat_atomic_add(&rrdeng_reserved_file_descriptors, -RRDENG_FD_BUDGET_PER_INSTANCE);
return 0;
}
-void rrdeng_prepare_exit(struct rrdengine_instance *ctx)
-{
- struct rrdeng_cmd cmd;
-
- if (NULL == ctx) {
+void rrdeng_prepare_exit(struct rrdengine_instance *ctx) {
+ if (NULL == ctx)
return;
- }
-
- completion_init(&ctx->rrdengine_completion);
- cmd.opcode = RRDENG_QUIESCE;
- rrdeng_enq_cmd(&ctx->worker_config, &cmd);
- /* wait for dbengine to quiesce */
- completion_wait_for(&ctx->rrdengine_completion);
- completion_destroy(&ctx->rrdengine_completion);
+ // FIXME - ktsaou - properly cleanup ctx
+ // 1. make sure all collectors are stopped
- //metalog_prepare_exit(ctx->metalog_ctx);
+ completion_init(&ctx->quiesce.completion);
+ rrdeng_enq_cmd(ctx, RRDENG_OPCODE_CTX_QUIESCE, NULL, NULL, STORAGE_PRIORITY_INTERNAL_DBENGINE, NULL, NULL);
}
-RRDENG_SIZE_STATS rrdeng_size_statistics(struct rrdengine_instance *ctx) {
- RRDENG_SIZE_STATS stats = { 0 };
+static void populate_v2_statistics(struct rrdengine_datafile *datafile, RRDENG_SIZE_STATS *stats)
+{
+ struct journal_v2_header *j2_header = journalfile_v2_data_acquire(datafile->journalfile, NULL, 0, 0);
+ void *data_start = (void *)j2_header;
- for(struct pg_cache_page_index *page_index = ctx->pg_cache.metrics_index.last_page_index;
- page_index != NULL ;page_index = page_index->prev) {
- stats.metrics++;
- stats.metrics_pages += page_index->page_count;
+ if(unlikely(!j2_header))
+ return;
+
+ stats->extents += j2_header->extent_count;
+
+ unsigned entries;
+ struct journal_extent_list *extent_list = (void *) (data_start + j2_header->extent_offset);
+ for (entries = 0; entries < j2_header->extent_count; entries++) {
+ stats->extents_compressed_bytes += extent_list->datafile_size;
+ stats->extents_pages += extent_list->pages;
+ extent_list++;
}
- for(struct rrdengine_datafile *df = ctx->datafiles.first; df ;df = df->next) {
- stats.datafiles++;
+ struct journal_metric_list *metric = (void *) (data_start + j2_header->metric_offset);
+ time_t journal_start_time_s = (time_t) (j2_header->start_time_ut / USEC_PER_SEC);
- for(struct extent_info *ei = df->extents.first; ei ; ei = ei->next) {
- stats.extents++;
- stats.extents_compressed_bytes += ei->size;
+ stats->metrics += j2_header->metric_count;
+ for (entries = 0; entries < j2_header->metric_count; entries++) {
- for(int p = 0; p < ei->number_of_pages ;p++) {
- struct rrdeng_page_descr *descr = ei->pages[p];
+ struct journal_page_header *metric_list_header = (void *) (data_start + metric->page_offset);
+ stats->metrics_pages += metric_list_header->entries;
+ struct journal_page_list *descr = (void *) (data_start + metric->page_offset + sizeof(struct journal_page_header));
+ for (uint32_t idx=0; idx < metric_list_header->entries; idx++) {
- usec_t update_every_usec;
+ time_t update_every_s;
- size_t points = descr->page_length / PAGE_POINT_SIZE_BYTES(descr);
+ size_t points = descr->page_length / CTX_POINT_SIZE_BYTES(datafile->ctx);
- if(likely(points > 1))
- update_every_usec = (descr->end_time_ut - descr->start_time_ut) / (points - 1);
- else {
- update_every_usec = default_rrd_update_every * get_tier_grouping(ctx->tier) * USEC_PER_SEC;
- stats.single_point_pages++;
- }
+ time_t start_time_s = journal_start_time_s + descr->delta_start_s;
+ time_t end_time_s = journal_start_time_s + descr->delta_end_s;
- time_t duration_secs = (time_t)((descr->end_time_ut - descr->start_time_ut + update_every_usec)/USEC_PER_SEC);
+ if(likely(points > 1))
+ update_every_s = (time_t) ((end_time_s - start_time_s) / (points - 1));
+ else {
+ update_every_s = (time_t) (default_rrd_update_every * get_tier_grouping(datafile->ctx->config.tier));
+ stats->single_point_pages++;
+ }
- stats.extents_pages++;
- stats.pages_uncompressed_bytes += descr->page_length;
- stats.pages_duration_secs += duration_secs;
- stats.points += points;
+ time_t duration_s = (time_t)((end_time_s - start_time_s + update_every_s));
- stats.page_types[descr->type].pages++;
- stats.page_types[descr->type].pages_uncompressed_bytes += descr->page_length;
- stats.page_types[descr->type].pages_duration_secs += duration_secs;
- stats.page_types[descr->type].points += points;
+ stats->pages_uncompressed_bytes += descr->page_length;
+ stats->pages_duration_secs += duration_s;
+ stats->points += points;
- if(!stats.first_t || (descr->start_time_ut - update_every_usec) < stats.first_t)
- stats.first_t = (descr->start_time_ut - update_every_usec) / USEC_PER_SEC;
+ stats->page_types[descr->type].pages++;
+ stats->page_types[descr->type].pages_uncompressed_bytes += descr->page_length;
+ stats->page_types[descr->type].pages_duration_secs += duration_s;
+ stats->page_types[descr->type].points += points;
- if(!stats.last_t || descr->end_time_ut > stats.last_t)
- stats.last_t = descr->end_time_ut / USEC_PER_SEC;
- }
+ if(!stats->first_time_s || (start_time_s - update_every_s) < stats->first_time_s)
+ stats->first_time_s = (start_time_s - update_every_s);
+
+ if(!stats->last_time_s || end_time_s > stats->last_time_s)
+ stats->last_time_s = end_time_s;
+
+ descr++;
}
+ metric++;
}
+ journalfile_v2_data_release(datafile->journalfile);
+}
+
+RRDENG_SIZE_STATS rrdeng_size_statistics(struct rrdengine_instance *ctx) {
+ RRDENG_SIZE_STATS stats = { 0 };
- stats.currently_collected_metrics = ctx->stats.metric_API_producers;
- stats.max_concurrently_collected_metrics = ctx->metric_API_max_producers;
+ uv_rwlock_rdlock(&ctx->datafiles.rwlock);
+ for(struct rrdengine_datafile *df = ctx->datafiles.first; df ;df = df->next) {
+ stats.datafiles++;
+ populate_v2_statistics(df, &stats);
+ }
+ uv_rwlock_rdunlock(&ctx->datafiles.rwlock);
+
+ stats.currently_collected_metrics = __atomic_load_n(&ctx->atomic.collectors_running, __ATOMIC_RELAXED);
internal_error(stats.metrics_pages != stats.extents_pages + stats.currently_collected_metrics,
"DBENGINE: metrics pages is %zu, but extents pages is %zu and API consumers is %zu",
stats.metrics_pages, stats.extents_pages, stats.currently_collected_metrics);
- stats.disk_space = ctx->disk_space;
- stats.max_disk_space = ctx->max_disk_space;
+ stats.disk_space = ctx_current_disk_space_get(ctx);
+ stats.max_disk_space = ctx->config.max_disk_space;
- stats.database_retention_secs = (time_t)(stats.last_t - stats.first_t);
+ stats.database_retention_secs = (time_t)(stats.last_time_s - stats.first_time_s);
if(stats.extents_pages)
stats.average_page_size_bytes = (double)stats.pages_uncompressed_bytes / (double)stats.extents_pages;
@@ -1252,21 +1386,22 @@ RRDENG_SIZE_STATS rrdeng_size_statistics(struct rrdengine_instance *ctx) {
}
}
- stats.sizeof_metric = struct_natural_alignment(sizeof(struct pg_cache_page_index) + sizeof(struct pg_alignment));
- stats.sizeof_page = struct_natural_alignment(sizeof(struct rrdeng_page_descr));
+// stats.sizeof_metric = 0;
stats.sizeof_datafile = struct_natural_alignment(sizeof(struct rrdengine_datafile)) + struct_natural_alignment(sizeof(struct rrdengine_journalfile));
- stats.sizeof_page_in_cache = struct_natural_alignment(sizeof(struct page_cache_descr));
- stats.sizeof_point_data = page_type_size[ctx->page_type];
- stats.sizeof_page_data = RRDENG_BLOCK_SIZE;
+ stats.sizeof_page_in_cache = 0; // struct_natural_alignment(sizeof(struct page_cache_descr));
+ stats.sizeof_point_data = page_type_size[ctx->config.page_type];
+ stats.sizeof_page_data = tier_page_size[ctx->config.tier];
stats.pages_per_extent = rrdeng_pages_per_extent;
- stats.sizeof_extent = sizeof(struct extent_info);
- stats.sizeof_page_in_extent = sizeof(struct rrdeng_page_descr *);
-
- stats.sizeof_metric_in_index = 40;
- stats.sizeof_page_in_index = 24;
+// stats.sizeof_metric_in_index = 40;
+// stats.sizeof_page_in_index = 24;
- stats.default_granularity_secs = (size_t)default_rrd_update_every * get_tier_grouping(ctx->tier);
+ stats.default_granularity_secs = (size_t)default_rrd_update_every * get_tier_grouping(ctx->config.tier);
return stats;
}
+
+struct rrdeng_cache_efficiency_stats rrdeng_get_cache_efficiency_stats(void) {
+ // FIXME - make cache efficiency stats atomic
+ return rrdeng_cache_efficiency_stats;
+}
diff --git a/database/engine/rrdengineapi.h b/database/engine/rrdengineapi.h
index 3acee4ec..feb79b97 100644
--- a/database/engine/rrdengineapi.h
+++ b/database/engine/rrdengineapi.h
@@ -8,7 +8,7 @@
#define RRDENG_MIN_PAGE_CACHE_SIZE_MB (8)
#define RRDENG_MIN_DISK_SPACE_MB (64)
-#define RRDENG_NR_STATS (37)
+#define RRDENG_NR_STATS (38)
#define RRDENG_FD_BUDGET_PER_INSTANCE (50)
@@ -16,26 +16,15 @@ extern int db_engine_use_malloc;
extern int default_rrdeng_page_fetch_timeout;
extern int default_rrdeng_page_fetch_retries;
extern int default_rrdeng_page_cache_mb;
+extern int db_engine_journal_indexing;
+extern int db_engine_journal_check;
extern int default_rrdeng_disk_quota_mb;
extern int default_multidb_disk_quota_mb;
-extern uint8_t rrdeng_drop_metrics_under_page_cache_pressure;
extern struct rrdengine_instance *multidb_ctx[RRD_STORAGE_TIERS];
extern size_t page_type_size[];
+extern size_t tier_page_size[];
-#define PAGE_POINT_SIZE_BYTES(x) page_type_size[(x)->type]
-
-struct rrdeng_region_info {
- time_t start_time_s;
- int update_every;
- unsigned points;
-};
-
-void *rrdeng_create_page(struct rrdengine_instance *ctx, uuid_t *id, struct rrdeng_page_descr **ret_descr);
-void rrdeng_commit_page(struct rrdengine_instance *ctx, struct rrdeng_page_descr *descr,
- Word_t page_correlation_id);
-void *rrdeng_get_latest_page(struct rrdengine_instance *ctx, uuid_t *id, void **handle);
-void *rrdeng_get_page(struct rrdengine_instance *ctx, uuid_t *id, usec_t point_in_time_ut, void **handle);
-void rrdeng_put_page(struct rrdengine_instance *ctx, void *handle);
+#define CTX_POINT_SIZE_BYTES(ctx) page_type_size[(ctx)->config.page_type]
void rrdeng_generate_legacy_uuid(const char *dim_id, const char *chart_id, uuid_t *ret_uuid);
void rrdeng_convert_legacy_uuid_to_multihost(char machine_guid[GUID_LEN + 1], uuid_t *legacy_uuid,
@@ -44,8 +33,6 @@ void rrdeng_convert_legacy_uuid_to_multihost(char machine_guid[GUID_LEN + 1], uu
STORAGE_METRIC_HANDLE *rrdeng_metric_get_or_create(RRDDIM *rd, STORAGE_INSTANCE *db_instance);
STORAGE_METRIC_HANDLE *rrdeng_metric_get(STORAGE_INSTANCE *db_instance, uuid_t *uuid);
-STORAGE_METRIC_HANDLE *rrdeng_metric_create(STORAGE_INSTANCE *db_instance, uuid_t *uuid);
-STORAGE_METRIC_HANDLE *rrdeng_metric_get_legacy(STORAGE_INSTANCE *db_instance, const char *rd_id, const char *st_id);
void rrdeng_metric_release(STORAGE_METRIC_HANDLE *db_metric_handle);
STORAGE_METRIC_HANDLE *rrdeng_metric_dup(STORAGE_METRIC_HANDLE *db_metric_handle);
@@ -60,25 +47,29 @@ void rrdeng_store_metric_next(STORAGE_COLLECT_HANDLE *collection_handle, usec_t
SN_FLAGS flags);
int rrdeng_store_metric_finalize(STORAGE_COLLECT_HANDLE *collection_handle);
-void rrdeng_load_metric_init(STORAGE_METRIC_HANDLE *db_metric_handle, struct storage_engine_query_handle *rrdimm_handle,
- time_t start_time_s, time_t end_time_s);
+void rrdeng_load_metric_init(STORAGE_METRIC_HANDLE *db_metric_handle, struct storage_engine_query_handle *rrddim_handle,
+ time_t start_time_s, time_t end_time_s, STORAGE_PRIORITY priority);
STORAGE_POINT rrdeng_load_metric_next(struct storage_engine_query_handle *rrddim_handle);
-int rrdeng_load_metric_is_finished(struct storage_engine_query_handle *rrdimm_handle);
-void rrdeng_load_metric_finalize(struct storage_engine_query_handle *rrdimm_handle);
+int rrdeng_load_metric_is_finished(struct storage_engine_query_handle *rrddim_handle);
+void rrdeng_load_metric_finalize(struct storage_engine_query_handle *rrddim_handle);
time_t rrdeng_metric_latest_time(STORAGE_METRIC_HANDLE *db_metric_handle);
time_t rrdeng_metric_oldest_time(STORAGE_METRIC_HANDLE *db_metric_handle);
+time_t rrdeng_load_align_to_optimal_before(struct storage_engine_query_handle *rrddim_handle);
void rrdeng_get_37_statistics(struct rrdengine_instance *ctx, unsigned long long *array);
/* must call once before using anything */
-int rrdeng_init(RRDHOST *host, struct rrdengine_instance **ctxp, char *dbfiles_path, unsigned page_cache_mb,
+int rrdeng_init(struct rrdengine_instance **ctxp, const char *dbfiles_path,
unsigned disk_space_mb, size_t tier);
+void rrdeng_readiness_wait(struct rrdengine_instance *ctx);
+void rrdeng_exit_mode(struct rrdengine_instance *ctx);
+
int rrdeng_exit(struct rrdengine_instance *ctx);
void rrdeng_prepare_exit(struct rrdengine_instance *ctx);
-int rrdeng_metric_retention_by_uuid(STORAGE_INSTANCE *si, uuid_t *dim_uuid, time_t *first_entry_t, time_t *last_entry_t);
+bool rrdeng_metric_retention_by_uuid(STORAGE_INSTANCE *db_instance, uuid_t *dim_uuid, time_t *first_entry_s, time_t *last_entry_s);
extern STORAGE_METRICS_GROUP *rrdeng_metrics_group_get(STORAGE_INSTANCE *db_instance, uuid_t *uuid);
extern void rrdeng_metrics_group_release(STORAGE_INSTANCE *db_instance, STORAGE_METRICS_GROUP *smg);
@@ -86,12 +77,6 @@ extern void rrdeng_metrics_group_release(STORAGE_INSTANCE *db_instance, STORAGE_
typedef struct rrdengine_size_statistics {
size_t default_granularity_secs;
- size_t sizeof_metric;
- size_t sizeof_metric_in_index;
- size_t sizeof_page;
- size_t sizeof_page_in_index;
- size_t sizeof_extent;
- size_t sizeof_page_in_extent;
size_t sizeof_datafile;
size_t sizeof_page_in_cache;
size_t sizeof_point_data;
@@ -119,11 +104,10 @@ typedef struct rrdengine_size_statistics {
size_t single_point_pages;
- usec_t first_t;
- usec_t last_t;
+ time_t first_time_s;
+ time_t last_time_s;
size_t currently_collected_metrics;
- size_t max_concurrently_collected_metrics;
size_t estimated_concurrently_collected_metrics;
size_t disk_space;
@@ -139,6 +123,109 @@ typedef struct rrdengine_size_statistics {
double average_page_size_bytes;
} RRDENG_SIZE_STATS;
+struct rrdeng_cache_efficiency_stats {
+ size_t queries;
+ size_t queries_planned_with_gaps;
+ size_t queries_executed_with_gaps;
+ size_t queries_open;
+ size_t queries_journal_v2;
+
+ size_t currently_running_queries;
+
+ // query planner output of the queries
+ size_t pages_total;
+ size_t pages_to_load_from_disk;
+ size_t extents_loaded_from_disk;
+
+ // pages metadata sources
+ size_t pages_meta_source_main_cache;
+ size_t pages_meta_source_open_cache;
+ size_t pages_meta_source_journal_v2;
+
+ // preloading
+ size_t page_next_wait_failed;
+ size_t page_next_wait_loaded;
+ size_t page_next_nowait_failed;
+ size_t page_next_nowait_loaded;
+
+ // pages data sources
+ size_t pages_data_source_main_cache;
+ size_t pages_data_source_main_cache_at_pass4;
+ size_t pages_data_source_disk;
+ size_t pages_data_source_extent_cache; // loaded by a cached extent
+
+ // cache hits at different points
+ size_t pages_load_ok_loaded_but_cache_hit_while_inserting; // found in cache while inserting it (conflict)
+
+ // loading
+ size_t pages_load_extent_merged;
+ size_t pages_load_ok_uncompressed;
+ size_t pages_load_ok_compressed;
+ size_t pages_load_fail_invalid_page_in_extent;
+ size_t pages_load_fail_cant_mmap_extent;
+ size_t pages_load_fail_datafile_not_available;
+ size_t pages_load_fail_unroutable;
+ size_t pages_load_fail_not_found;
+ size_t pages_load_fail_invalid_extent;
+ size_t pages_load_fail_cancelled;
+
+ // timings for query preparation
+ size_t prep_time_to_route;
+ size_t prep_time_in_main_cache_lookup;
+ size_t prep_time_in_open_cache_lookup;
+ size_t prep_time_in_journal_v2_lookup;
+ size_t prep_time_in_pass4_lookup;
+
+ // timings the query thread experiences
+ size_t query_time_init;
+ size_t query_time_wait_for_prep;
+ size_t query_time_to_slow_disk_next_page;
+ size_t query_time_to_fast_disk_next_page;
+ size_t query_time_to_slow_preload_next_page;
+ size_t query_time_to_fast_preload_next_page;
+
+ // query issues
+ size_t pages_zero_time_skipped;
+ size_t pages_past_time_skipped;
+ size_t pages_overlapping_skipped;
+ size_t pages_invalid_size_skipped;
+ size_t pages_invalid_update_every_fixed;
+ size_t pages_invalid_entries_fixed;
+
+ // database events
+ size_t journal_v2_mapped;
+ size_t journal_v2_unmapped;
+ size_t datafile_creation_started;
+ size_t datafile_deletion_started;
+ size_t datafile_deletion_spin;
+ size_t journal_v2_indexing_started;
+ size_t metrics_retention_started;
+};
+
+struct rrdeng_buffer_sizes {
+ size_t workers;
+ size_t pdc;
+ size_t wal;
+ size_t descriptors;
+ size_t xt_io;
+ size_t xt_buf;
+ size_t handles;
+ size_t opcodes;
+ size_t epdl;
+ size_t deol;
+ size_t pd;
+ size_t pgc;
+ size_t mrg;
+#ifdef PDC_USE_JULYL
+ size_t julyl;
+#endif
+};
+
+struct rrdeng_buffer_sizes rrdeng_get_buffer_sizes(void);
+struct rrdeng_cache_efficiency_stats rrdeng_get_cache_efficiency_stats(void);
+
RRDENG_SIZE_STATS rrdeng_size_statistics(struct rrdengine_instance *ctx);
+size_t rrdeng_collectors_running(struct rrdengine_instance *ctx);
+bool rrdeng_is_legacy(STORAGE_INSTANCE *db_instance);
#endif /* NETDATA_RRDENGINEAPI_H */
diff --git a/database/engine/rrdenginelib.c b/database/engine/rrdenginelib.c
index 58bd9c43..7ec626c5 100644
--- a/database/engine/rrdenginelib.c
+++ b/database/engine/rrdenginelib.c
@@ -4,68 +4,68 @@
#define BUFSIZE (512)
/* Caller must hold descriptor lock */
-void print_page_cache_descr(struct rrdeng_page_descr *descr, const char *msg, bool log_debug)
-{
- if(log_debug && !(debug_flags & D_RRDENGINE))
- return;
-
- BUFFER *wb = buffer_create(512);
-
- if(!descr) {
- buffer_sprintf(wb, "DBENGINE: %s : descr is NULL", msg);
- }
- else {
- struct page_cache_descr *pg_cache_descr = descr->pg_cache_descr;
- char uuid_str[UUID_STR_LEN];
-
- uuid_unparse_lower(*descr->id, uuid_str);
- buffer_sprintf(wb, "DBENGINE: %s : page(%p) metric:%s, len:%"PRIu32", time:%"PRIu64"->%"PRIu64", update_every:%u, type:%u, xt_offset:",
- msg,
- pg_cache_descr->page, uuid_str,
- descr->page_length,
- (uint64_t)descr->start_time_ut,
- (uint64_t)descr->end_time_ut,
- (uint32_t)descr->update_every_s,
- (uint32_t)descr->type
- );
- if (!descr->extent) {
- buffer_strcat(wb, "N/A");
- } else {
- buffer_sprintf(wb, "%"PRIu64, descr->extent->offset);
- }
-
- buffer_sprintf(wb, ", flags:0x%2.2lX refcnt:%u", pg_cache_descr->flags, pg_cache_descr->refcnt);
- }
-
- if(log_debug)
- debug(D_RRDENGINE, "%s", buffer_tostring(wb));
- else
- internal_error(true, "%s", buffer_tostring(wb));
-
- buffer_free(wb);
-}
-
-void print_page_descr(struct rrdeng_page_descr *descr)
-{
- char uuid_str[UUID_STR_LEN];
- char str[BUFSIZE + 1];
- int pos = 0;
-
- uuid_unparse_lower(*descr->id, uuid_str);
- pos += snprintfz(str, BUFSIZE - pos, "id=%s\n"
- "--->len:%"PRIu32" time:%"PRIu64"->%"PRIu64" xt_offset:",
- uuid_str,
- descr->page_length,
- (uint64_t)descr->start_time_ut,
- (uint64_t)descr->end_time_ut);
- if (!descr->extent) {
- pos += snprintfz(str + pos, BUFSIZE - pos, "N/A");
- } else {
- pos += snprintfz(str + pos, BUFSIZE - pos, "%"PRIu64, descr->extent->offset);
- }
- snprintfz(str + pos, BUFSIZE - pos, "\n\n");
- fputs(str, stderr);
-}
+//void print_page_cache_descr(struct rrdeng_page_descr *descr, const char *msg, bool log_debug)
+//{
+// if(log_debug && !(debug_flags & D_RRDENGINE))
+// return;
+//
+// BUFFER *wb = buffer_create(512);
+//
+// if(!descr) {
+// buffer_sprintf(wb, "DBENGINE: %s : descr is NULL", msg);
+// }
+// else {
+// struct page_cache_descr *pg_cache_descr = descr->pg_cache_descr;
+// char uuid_str[UUID_STR_LEN];
+//
+// uuid_unparse_lower(*descr->id, uuid_str);
+// buffer_sprintf(wb, "DBENGINE: %s : page(%p) metric:%s, len:%"PRIu32", time:%"PRIu64"->%"PRIu64", update_every:%u, type:%u, xt_offset:",
+// msg,
+// pg_cache_descr->page, uuid_str,
+// descr->page_length,
+// (uint64_t)descr->start_time_ut,
+// (uint64_t)descr->end_time_ut,
+// (uint32_t)descr->update_every_s,
+// (uint32_t)descr->type
+// );
+// if (!descr->extent) {
+// buffer_strcat(wb, "N/A");
+// } else {
+// buffer_sprintf(wb, "%"PRIu64, descr->extent->offset);
+// }
+//
+// buffer_sprintf(wb, ", flags:0x%2.2lX refcnt:%u", pg_cache_descr->flags, pg_cache_descr->refcnt);
+// }
+//
+// if(log_debug)
+// debug(D_RRDENGINE, "%s", buffer_tostring(wb));
+// else
+// internal_error(true, "%s", buffer_tostring(wb));
+//
+// buffer_free(wb);
+//}
+//
+//void print_page_descr(struct rrdeng_page_descr *descr)
+//{
+// char uuid_str[UUID_STR_LEN];
+// char str[BUFSIZE + 1];
+// int pos = 0;
+//
+// uuid_unparse_lower(*descr->id, uuid_str);
+// pos += snprintfz(str, BUFSIZE - pos, "id=%s\n"
+// "--->len:%"PRIu32" time:%"PRIu64"->%"PRIu64" xt_offset:",
+// uuid_str,
+// descr->page_length,
+// (uint64_t)descr->start_time_ut,
+// (uint64_t)descr->end_time_ut);
+// if (!descr->extent) {
+// pos += snprintfz(str + pos, BUFSIZE - pos, "N/A");
+// } else {
+// pos += snprintfz(str + pos, BUFSIZE - pos, "%"PRIu64, descr->extent->offset);
+// }
+// snprintfz(str + pos, BUFSIZE - pos, "\n\n");
+// fputs(str, stderr);
+//}
int check_file_properties(uv_file file, uint64_t *file_size, size_t min_size)
{
@@ -142,90 +142,6 @@ int open_file_for_io(char *path, int flags, uv_file *file, int direct)
return fd;
}
-char *get_rrdeng_statistics(struct rrdengine_instance *ctx, char *str, size_t size)
-{
- struct page_cache *pg_cache;
-
- pg_cache = &ctx->pg_cache;
- snprintfz(str, size,
- "metric_API_producers: %ld\n"
- "metric_API_consumers: %ld\n"
- "page_cache_total_pages: %ld\n"
- "page_cache_descriptors: %ld\n"
- "page_cache_populated_pages: %ld\n"
- "page_cache_committed_pages: %ld\n"
- "page_cache_insertions: %ld\n"
- "page_cache_deletions: %ld\n"
- "page_cache_hits: %ld\n"
- "page_cache_misses: %ld\n"
- "page_cache_backfills: %ld\n"
- "page_cache_evictions: %ld\n"
- "compress_before_bytes: %ld\n"
- "compress_after_bytes: %ld\n"
- "decompress_before_bytes: %ld\n"
- "decompress_after_bytes: %ld\n"
- "io_write_bytes: %ld\n"
- "io_write_requests: %ld\n"
- "io_read_bytes: %ld\n"
- "io_read_requests: %ld\n"
- "io_write_extent_bytes: %ld\n"
- "io_write_extents: %ld\n"
- "io_read_extent_bytes: %ld\n"
- "io_read_extents: %ld\n"
- "datafile_creations: %ld\n"
- "datafile_deletions: %ld\n"
- "journalfile_creations: %ld\n"
- "journalfile_deletions: %ld\n"
- "io_errors: %ld\n"
- "fs_errors: %ld\n"
- "global_io_errors: %ld\n"
- "global_fs_errors: %ld\n"
- "rrdeng_reserved_file_descriptors: %ld\n"
- "pg_cache_over_half_dirty_events: %ld\n"
- "global_pg_cache_over_half_dirty_events: %ld\n"
- "flushing_pressure_page_deletions: %ld\n"
- "global_flushing_pressure_page_deletions: %ld\n",
- (long)ctx->stats.metric_API_producers,
- (long)ctx->stats.metric_API_consumers,
- (long)pg_cache->page_descriptors,
- (long)ctx->stats.page_cache_descriptors,
- (long)pg_cache->populated_pages,
- (long)pg_cache->committed_page_index.nr_committed_pages,
- (long)ctx->stats.pg_cache_insertions,
- (long)ctx->stats.pg_cache_deletions,
- (long)ctx->stats.pg_cache_hits,
- (long)ctx->stats.pg_cache_misses,
- (long)ctx->stats.pg_cache_backfills,
- (long)ctx->stats.pg_cache_evictions,
- (long)ctx->stats.before_compress_bytes,
- (long)ctx->stats.after_compress_bytes,
- (long)ctx->stats.before_decompress_bytes,
- (long)ctx->stats.after_decompress_bytes,
- (long)ctx->stats.io_write_bytes,
- (long)ctx->stats.io_write_requests,
- (long)ctx->stats.io_read_bytes,
- (long)ctx->stats.io_read_requests,
- (long)ctx->stats.io_write_extent_bytes,
- (long)ctx->stats.io_write_extents,
- (long)ctx->stats.io_read_extent_bytes,
- (long)ctx->stats.io_read_extents,
- (long)ctx->stats.datafile_creations,
- (long)ctx->stats.datafile_deletions,
- (long)ctx->stats.journalfile_creations,
- (long)ctx->stats.journalfile_deletions,
- (long)ctx->stats.io_errors,
- (long)ctx->stats.fs_errors,
- (long)global_io_errors,
- (long)global_fs_errors,
- (long)rrdeng_reserved_file_descriptors,
- (long)ctx->stats.pg_cache_over_half_dirty_events,
- (long)global_pg_cache_over_half_dirty_events,
- (long)ctx->stats.flushing_pressure_page_deletions,
- (long)global_flushing_pressure_page_deletions
- );
- return str;
-}
-
int is_legacy_child(const char *machine_guid)
{
uuid_t uuid;
diff --git a/database/engine/rrdenginelib.h b/database/engine/rrdenginelib.h
index 6b1a15fb..ca8eacae 100644
--- a/database/engine/rrdenginelib.h
+++ b/database/engine/rrdenginelib.h
@@ -6,7 +6,6 @@
#include "libnetdata/libnetdata.h"
/* Forward declarations */
-struct rrdeng_page_descr;
struct rrdengine_instance;
#define STR_HELPER(x) #x
@@ -83,8 +82,6 @@ static inline void crc32set(void *crcp, uLong crc)
*(uint32_t *)crcp = crc;
}
-void print_page_cache_descr(struct rrdeng_page_descr *descr, const char *msg, bool log_debug);
-void print_page_descr(struct rrdeng_page_descr *descr);
int check_file_properties(uv_file file, uint64_t *file_size, size_t min_size);
int open_file_for_io(char *path, int flags, uv_file *file, int direct);
static inline int open_file_direct_io(char *path, int flags, uv_file *file)
@@ -95,7 +92,6 @@ static inline int open_file_buffered_io(char *path, int flags, uv_file *file)
{
return open_file_for_io(path, flags, file, 0);
}
-char *get_rrdeng_statistics(struct rrdengine_instance *ctx, char *str, size_t size);
int compute_multidb_diskspace();
int is_legacy_child(const char *machine_guid);
diff --git a/database/engine/rrdenglocking.c b/database/engine/rrdenglocking.c
deleted file mode 100644
index a23abf30..00000000
--- a/database/engine/rrdenglocking.c
+++ /dev/null
@@ -1,241 +0,0 @@
-// SPDX-License-Identifier: GPL-3.0-or-later
-#include "rrdengine.h"
-
-struct page_cache_descr *rrdeng_create_pg_cache_descr(struct rrdengine_instance *ctx)
-{
- struct page_cache_descr *pg_cache_descr;
-
- pg_cache_descr = mallocz(sizeof(*pg_cache_descr));
- rrd_stat_atomic_add(&ctx->stats.page_cache_descriptors, 1);
- pg_cache_descr->page = NULL;
- pg_cache_descr->flags = 0;
- pg_cache_descr->prev = pg_cache_descr->next = NULL;
- pg_cache_descr->refcnt = 0;
- pg_cache_descr->waiters = 0;
- fatal_assert(0 == uv_cond_init(&pg_cache_descr->cond));
- fatal_assert(0 == uv_mutex_init(&pg_cache_descr->mutex));
-
- return pg_cache_descr;
-}
-
-void rrdeng_destroy_pg_cache_descr(struct rrdengine_instance *ctx, struct page_cache_descr *pg_cache_descr)
-{
- uv_cond_destroy(&pg_cache_descr->cond);
- uv_mutex_destroy(&pg_cache_descr->mutex);
- freez(pg_cache_descr);
- rrd_stat_atomic_add(&ctx->stats.page_cache_descriptors, -1);
-}
-
-/* also allocates page cache descriptor if missing */
-void rrdeng_page_descr_mutex_lock(struct rrdengine_instance *ctx, struct rrdeng_page_descr *descr)
-{
- unsigned long old_state, old_users, new_state, ret_state;
- struct page_cache_descr *pg_cache_descr = NULL;
- uint8_t we_locked;
-
- we_locked = 0;
- while (1) { /* spin */
- old_state = descr->pg_cache_descr_state;
- old_users = old_state >> PG_CACHE_DESCR_SHIFT;
-
- if (unlikely(we_locked)) {
- fatal_assert(old_state & PG_CACHE_DESCR_LOCKED);
- new_state = (1 << PG_CACHE_DESCR_SHIFT) | PG_CACHE_DESCR_ALLOCATED;
- ret_state = ulong_compare_and_swap(&descr->pg_cache_descr_state, old_state, new_state);
- if (old_state == ret_state) {
- /* success */
- break;
- }
- continue; /* spin */
- }
- if (old_state & PG_CACHE_DESCR_LOCKED) {
- fatal_assert(0 == old_users);
- continue; /* spin */
- }
- if (0 == old_state) {
- /* no page cache descriptor has been allocated */
-
- if (NULL == pg_cache_descr) {
- pg_cache_descr = rrdeng_create_pg_cache_descr(ctx);
- }
- new_state = PG_CACHE_DESCR_LOCKED;
- ret_state = ulong_compare_and_swap(&descr->pg_cache_descr_state, 0, new_state);
- if (0 == ret_state) {
- we_locked = 1;
- descr->pg_cache_descr = pg_cache_descr;
- pg_cache_descr->descr = descr;
- pg_cache_descr = NULL; /* make sure we don't free pg_cache_descr */
- /* retry */
- continue;
- }
- continue; /* spin */
- }
- /* page cache descriptor is already allocated */
- if (unlikely(!(old_state & PG_CACHE_DESCR_ALLOCATED))) {
- fatal("Invalid page cache descriptor locking state:%#lX", old_state);
- }
- new_state = (old_users + 1) << PG_CACHE_DESCR_SHIFT;
- new_state |= old_state & PG_CACHE_DESCR_FLAGS_MASK;
-
- ret_state = ulong_compare_and_swap(&descr->pg_cache_descr_state, old_state, new_state);
- if (old_state == ret_state) {
- /* success */
- break;
- }
- /* spin */
- }
-
- if (pg_cache_descr) {
- rrdeng_destroy_pg_cache_descr(ctx, pg_cache_descr);
- }
- pg_cache_descr = descr->pg_cache_descr;
- uv_mutex_lock(&pg_cache_descr->mutex);
-}
-
-void rrdeng_page_descr_mutex_unlock(struct rrdengine_instance *ctx, struct rrdeng_page_descr *descr)
-{
- unsigned long old_state, new_state, ret_state, old_users;
- struct page_cache_descr *pg_cache_descr, *delete_pg_cache_descr = NULL;
- uint8_t we_locked;
-
- uv_mutex_unlock(&descr->pg_cache_descr->mutex);
-
- we_locked = 0;
- while (1) { /* spin */
- old_state = descr->pg_cache_descr_state;
- old_users = old_state >> PG_CACHE_DESCR_SHIFT;
-
- if (unlikely(we_locked)) {
- fatal_assert(0 == old_users);
-
- ret_state = ulong_compare_and_swap(&descr->pg_cache_descr_state, old_state, 0);
- if (old_state == ret_state) {
- /* success */
- rrdeng_destroy_pg_cache_descr(ctx, delete_pg_cache_descr);
- return;
- }
- continue; /* spin */
- }
- if (old_state & PG_CACHE_DESCR_LOCKED) {
- fatal_assert(0 == old_users);
- continue; /* spin */
- }
- fatal_assert(old_state & PG_CACHE_DESCR_ALLOCATED);
- pg_cache_descr = descr->pg_cache_descr;
- /* caller is the only page cache descriptor user and there are no pending references on the page */
- if ((old_state & PG_CACHE_DESCR_DESTROY) && (1 == old_users) &&
- !pg_cache_descr->flags && !pg_cache_descr->refcnt) {
- fatal_assert(!pg_cache_descr->waiters);
-
- new_state = PG_CACHE_DESCR_LOCKED;
- ret_state = ulong_compare_and_swap(&descr->pg_cache_descr_state, old_state, new_state);
- if (old_state == ret_state) {
- we_locked = 1;
- delete_pg_cache_descr = pg_cache_descr;
- descr->pg_cache_descr = NULL;
- /* retry */
- continue;
- }
- continue; /* spin */
- }
- fatal_assert(old_users > 0);
- new_state = (old_users - 1) << PG_CACHE_DESCR_SHIFT;
- new_state |= old_state & PG_CACHE_DESCR_FLAGS_MASK;
-
- ret_state = ulong_compare_and_swap(&descr->pg_cache_descr_state, old_state, new_state);
- if (old_state == ret_state) {
- /* success */
- break;
- }
- /* spin */
- }
-}
-
-/*
- * Tries to deallocate page cache descriptor. If it fails, it postpones deallocation by setting the
- * PG_CACHE_DESCR_DESTROY flag which will be eventually cleared by a different context after doing
- * the deallocation.
- */
-void rrdeng_try_deallocate_pg_cache_descr(struct rrdengine_instance *ctx, struct rrdeng_page_descr *descr)
-{
- unsigned long old_state, new_state, ret_state, old_users;
- struct page_cache_descr *pg_cache_descr = NULL;
- uint8_t just_locked, can_free, must_unlock;
-
- just_locked = 0;
- can_free = 0;
- must_unlock = 0;
- while (1) { /* spin */
- old_state = descr->pg_cache_descr_state;
- old_users = old_state >> PG_CACHE_DESCR_SHIFT;
-
- if (unlikely(just_locked)) {
- fatal_assert(0 == old_users);
-
- must_unlock = 1;
- just_locked = 0;
- /* Try deallocate if there are no pending references on the page */
- if (!pg_cache_descr->flags && !pg_cache_descr->refcnt) {
- fatal_assert(!pg_cache_descr->waiters);
-
- descr->pg_cache_descr = NULL;
- can_free = 1;
- /* success */
- continue;
- }
- continue; /* spin */
- }
- if (unlikely(must_unlock)) {
- fatal_assert(0 == old_users);
-
- if (can_free) {
- /* success */
- new_state = 0;
- } else {
- new_state = old_state | PG_CACHE_DESCR_DESTROY;
- new_state &= ~PG_CACHE_DESCR_LOCKED;
- }
- ret_state = ulong_compare_and_swap(&descr->pg_cache_descr_state, old_state, new_state);
- if (old_state == ret_state) {
- /* unlocked */
- if (can_free)
- rrdeng_destroy_pg_cache_descr(ctx, pg_cache_descr);
- return;
- }
- continue; /* spin */
- }
- if (!(old_state & PG_CACHE_DESCR_ALLOCATED)) {
- /* don't do anything */
- return;
- }
- if (old_state & PG_CACHE_DESCR_LOCKED) {
- fatal_assert(0 == old_users);
- continue; /* spin */
- }
- /* caller is the only page cache descriptor user */
- if (0 == old_users) {
- new_state = old_state | PG_CACHE_DESCR_LOCKED;
- ret_state = ulong_compare_and_swap(&descr->pg_cache_descr_state, old_state, new_state);
- if (old_state == ret_state) {
- just_locked = 1;
- pg_cache_descr = descr->pg_cache_descr;
- /* retry */
- continue;
- }
- continue; /* spin */
- }
- if (old_state & PG_CACHE_DESCR_DESTROY) {
- /* don't do anything */
- return;
- }
- /* plant PG_CACHE_DESCR_DESTROY so that other contexts eventually free the page cache descriptor */
- new_state = old_state | PG_CACHE_DESCR_DESTROY;
-
- ret_state = ulong_compare_and_swap(&descr->pg_cache_descr_state, old_state, new_state);
- if (old_state == ret_state) {
- /* success */
- return;
- }
- /* spin */
- }
-} \ No newline at end of file
diff --git a/database/engine/rrdenglocking.h b/database/engine/rrdenglocking.h
deleted file mode 100644
index 078eab38..00000000
--- a/database/engine/rrdenglocking.h
+++ /dev/null
@@ -1,17 +0,0 @@
-// SPDX-License-Identifier: GPL-3.0-or-later
-
-#ifndef NETDATA_RRDENGLOCKING_H
-#define NETDATA_RRDENGLOCKING_H
-
-#include "rrdengine.h"
-
-/* Forward declarations */
-struct page_cache_descr;
-
-struct page_cache_descr *rrdeng_create_pg_cache_descr(struct rrdengine_instance *ctx);
-void rrdeng_destroy_pg_cache_descr(struct rrdengine_instance *ctx, struct page_cache_descr *pg_cache_descr);
-void rrdeng_page_descr_mutex_lock(struct rrdengine_instance *ctx, struct rrdeng_page_descr *descr);
-void rrdeng_page_descr_mutex_unlock(struct rrdengine_instance *ctx, struct rrdeng_page_descr *descr);
-void rrdeng_try_deallocate_pg_cache_descr(struct rrdengine_instance *ctx, struct rrdeng_page_descr *descr);
-
-#endif /* NETDATA_RRDENGLOCKING_H */ \ No newline at end of file
diff --git a/database/ram/rrddim_mem.c b/database/ram/rrddim_mem.c
index 299b6557..0f17d6cb 100644
--- a/database/ram/rrddim_mem.c
+++ b/database/ram/rrddim_mem.c
@@ -21,129 +21,263 @@ void rrddim_metrics_group_release(STORAGE_INSTANCE *db_instance __maybe_unused,
// ----------------------------------------------------------------------------
// RRDDIM legacy data collection functions
+struct mem_metric_handle {
+ RRDDIM *rd;
+
+ size_t counter;
+ size_t entries;
+ size_t current_entry;
+ time_t last_updated_s;
+ time_t update_every_s;
+
+ int32_t refcount;
+};
+
+static void update_metric_handle_from_rrddim(struct mem_metric_handle *mh, RRDDIM *rd) {
+ mh->counter = rd->rrdset->counter;
+ mh->entries = rd->rrdset->entries;
+ mh->current_entry = rd->rrdset->current_entry;
+ mh->last_updated_s = rd->rrdset->last_updated.tv_sec;
+ mh->update_every_s = rd->rrdset->update_every;
+}
+
+static void check_metric_handle_from_rrddim(struct mem_metric_handle *mh) {
+ RRDDIM *rd = mh->rd; (void)rd;
+ internal_fatal(mh->entries != (size_t)rd->rrdset->entries, "RRDDIM: entries do not match");
+ internal_fatal(mh->update_every_s != rd->rrdset->update_every, "RRDDIM: update every does not match");
+}
+
STORAGE_METRIC_HANDLE *
rrddim_metric_get_or_create(RRDDIM *rd, STORAGE_INSTANCE *db_instance __maybe_unused) {
- STORAGE_METRIC_HANDLE *t = rrddim_metric_get(db_instance, &rd->metric_uuid);
- if(!t) {
+ struct mem_metric_handle *mh = (struct mem_metric_handle *)rrddim_metric_get(db_instance, &rd->metric_uuid);
+ while(!mh) {
netdata_rwlock_wrlock(&rrddim_JudyHS_rwlock);
Pvoid_t *PValue = JudyHSIns(&rrddim_JudyHS_array, &rd->metric_uuid, sizeof(uuid_t), PJE0);
- fatal_assert(NULL == *PValue);
- *PValue = rd;
- t = (STORAGE_METRIC_HANDLE *)rd;
+ mh = *PValue;
+ if(!mh) {
+ mh = callocz(1, sizeof(struct mem_metric_handle));
+ mh->rd = rd;
+ mh->refcount = 1;
+ update_metric_handle_from_rrddim(mh, rd);
+ *PValue = mh;
+ __atomic_add_fetch(&rrddim_db_memory_size, sizeof(struct mem_metric_handle) + JUDYHS_INDEX_SIZE_ESTIMATE(sizeof(uuid_t)), __ATOMIC_RELAXED);
+ }
+ else {
+ if(__atomic_add_fetch(&mh->refcount, 1, __ATOMIC_RELAXED) <= 0)
+ mh = NULL;
+ }
netdata_rwlock_unlock(&rrddim_JudyHS_rwlock);
}
- if((RRDDIM *)t != rd)
- fatal("RRDDIM_MEM: incorrect pointer returned from index.");
+ internal_fatal(mh->rd != rd, "RRDDIM_MEM: incorrect pointer returned from index.");
- return (STORAGE_METRIC_HANDLE *)rd;
+ return (STORAGE_METRIC_HANDLE *)mh;
}
STORAGE_METRIC_HANDLE *
rrddim_metric_get(STORAGE_INSTANCE *db_instance __maybe_unused, uuid_t *uuid) {
- RRDDIM *rd = NULL;
+ struct mem_metric_handle *mh = NULL;
netdata_rwlock_rdlock(&rrddim_JudyHS_rwlock);
Pvoid_t *PValue = JudyHSGet(rrddim_JudyHS_array, uuid, sizeof(uuid_t));
- if (likely(NULL != PValue))
- rd = *PValue;
+ if (likely(NULL != PValue)) {
+ mh = *PValue;
+ if(__atomic_add_fetch(&mh->refcount, 1, __ATOMIC_RELAXED) <= 0)
+ mh = NULL;
+ }
netdata_rwlock_unlock(&rrddim_JudyHS_rwlock);
- return (STORAGE_METRIC_HANDLE *)rd;
+ return (STORAGE_METRIC_HANDLE *)mh;
}
STORAGE_METRIC_HANDLE *rrddim_metric_dup(STORAGE_METRIC_HANDLE *db_metric_handle) {
+ struct mem_metric_handle *mh = (struct mem_metric_handle *)db_metric_handle;
+ __atomic_add_fetch(&mh->refcount, 1, __ATOMIC_RELAXED);
return db_metric_handle;
}
void rrddim_metric_release(STORAGE_METRIC_HANDLE *db_metric_handle __maybe_unused) {
- RRDDIM *rd = (RRDDIM *)db_metric_handle;
+ struct mem_metric_handle *mh = (struct mem_metric_handle *)db_metric_handle;
- netdata_rwlock_wrlock(&rrddim_JudyHS_rwlock);
- JudyHSDel(&rrddim_JudyHS_array, &rd->metric_uuid, sizeof(uuid_t), PJE0);
- netdata_rwlock_unlock(&rrddim_JudyHS_rwlock);
+ if(__atomic_sub_fetch(&mh->refcount, 1, __ATOMIC_RELAXED) == 0) {
+ // we are the last one holding this
+
+ int32_t expected = 0;
+ if(__atomic_compare_exchange_n(&mh->refcount, &expected, -99999, false, __ATOMIC_RELAXED, __ATOMIC_RELAXED)) {
+ // we can delete it
+
+ RRDDIM *rd = mh->rd;
+ netdata_rwlock_wrlock(&rrddim_JudyHS_rwlock);
+ JudyHSDel(&rrddim_JudyHS_array, &rd->metric_uuid, sizeof(uuid_t), PJE0);
+ netdata_rwlock_unlock(&rrddim_JudyHS_rwlock);
+
+ freez(mh);
+ __atomic_sub_fetch(&rrddim_db_memory_size, sizeof(struct mem_metric_handle) + JUDYHS_INDEX_SIZE_ESTIMATE(sizeof(uuid_t)), __ATOMIC_RELAXED);
+ }
+ }
}
-void rrddim_store_metric_change_collection_frequency(STORAGE_COLLECT_HANDLE *collection_handle, int update_every __maybe_unused) {
+bool rrddim_metric_retention_by_uuid(STORAGE_INSTANCE *db_instance __maybe_unused, uuid_t *uuid, time_t *first_entry_s, time_t *last_entry_s) {
+ STORAGE_METRIC_HANDLE *db_metric_handle = rrddim_metric_get(db_instance, uuid);
+ if(!db_metric_handle)
+ return false;
+
+ *first_entry_s = rrddim_query_oldest_time_s(db_metric_handle);
+ *last_entry_s = rrddim_query_latest_time_s(db_metric_handle);
+
+ return true;
+}
+
+void rrddim_store_metric_change_collection_frequency(STORAGE_COLLECT_HANDLE *collection_handle, int update_every) {
+ struct mem_collect_handle *ch = (struct mem_collect_handle *)collection_handle;
+ struct mem_metric_handle *mh = (struct mem_metric_handle *)ch->db_metric_handle;
+
rrddim_store_metric_flush(collection_handle);
+ mh->update_every_s = update_every;
}
STORAGE_COLLECT_HANDLE *rrddim_collect_init(STORAGE_METRIC_HANDLE *db_metric_handle, uint32_t update_every __maybe_unused, STORAGE_METRICS_GROUP *smg __maybe_unused) {
- RRDDIM *rd = (RRDDIM *)db_metric_handle;
- rd->db[rd->rrdset->current_entry] = pack_storage_number(NAN, SN_FLAG_NONE);
+ struct mem_metric_handle *mh = (struct mem_metric_handle *)db_metric_handle;
+ RRDDIM *rd = mh->rd;
+
+ update_metric_handle_from_rrddim(mh, rd);
+ internal_fatal((uint32_t)mh->update_every_s != update_every, "RRDDIM: update requested does not match the dimension");
+
struct mem_collect_handle *ch = callocz(1, sizeof(struct mem_collect_handle));
ch->rd = rd;
+ ch->db_metric_handle = db_metric_handle;
+
+ __atomic_add_fetch(&rrddim_db_memory_size, sizeof(struct mem_collect_handle), __ATOMIC_RELAXED);
+
return (STORAGE_COLLECT_HANDLE *)ch;
}
-void rrddim_collect_store_metric(STORAGE_COLLECT_HANDLE *collection_handle, usec_t point_in_time, NETDATA_DOUBLE number,
- NETDATA_DOUBLE min_value,
- NETDATA_DOUBLE max_value,
- uint16_t count,
- uint16_t anomaly_count,
- SN_FLAGS flags)
-{
- UNUSED(point_in_time);
- UNUSED(min_value);
- UNUSED(max_value);
- UNUSED(count);
- UNUSED(anomaly_count);
+void rrddim_store_metric_flush(STORAGE_COLLECT_HANDLE *collection_handle) {
+ struct mem_collect_handle *ch = (struct mem_collect_handle *)collection_handle;
+ struct mem_metric_handle *mh = (struct mem_metric_handle *)ch->db_metric_handle;
+
+ RRDDIM *rd = mh->rd;
+ size_t entries = mh->entries;
+ storage_number empty = pack_storage_number(NAN, SN_FLAG_NONE);
+
+ for(size_t i = 0; i < entries ;i++)
+ rd->db[i] = empty;
+ mh->counter = 0;
+ mh->last_updated_s = 0;
+ mh->current_entry = 0;
+}
+
+static inline void rrddim_fill_the_gap(STORAGE_COLLECT_HANDLE *collection_handle, time_t now_collect_s) {
struct mem_collect_handle *ch = (struct mem_collect_handle *)collection_handle;
- RRDDIM *rd = ch->rd;
- rd->db[rd->rrdset->current_entry] = pack_storage_number(number, flags);
+ struct mem_metric_handle *mh = (struct mem_metric_handle *)ch->db_metric_handle;
+
+ RRDDIM *rd = mh->rd;
+
+ internal_fatal(ch->rd != mh->rd, "RRDDIM: dimensions do not match");
+ check_metric_handle_from_rrddim(mh);
+
+ size_t entries = mh->entries;
+ time_t update_every_s = mh->update_every_s;
+ time_t last_stored_s = mh->last_updated_s;
+ size_t gap_entries = (now_collect_s - last_stored_s) / update_every_s;
+ if(gap_entries >= entries)
+ rrddim_store_metric_flush(collection_handle);
+
+ else {
+ storage_number empty = pack_storage_number(NAN, SN_FLAG_NONE);
+ size_t current_entry = mh->current_entry;
+ time_t now_store_s = last_stored_s + update_every_s;
+
+ // fill the dimension
+ size_t c;
+ for(c = 0; c < entries && now_store_s <= now_collect_s ; now_store_s += update_every_s, c++) {
+ rd->db[current_entry++] = empty;
+
+ if(unlikely(current_entry >= entries))
+ current_entry = 0;
+ }
+ mh->counter += c;
+ mh->current_entry = current_entry;
+ mh->last_updated_s = now_store_s;
+ }
}
-void rrddim_store_metric_flush(STORAGE_COLLECT_HANDLE *collection_handle) {
+void rrddim_collect_store_metric(STORAGE_COLLECT_HANDLE *collection_handle,
+ usec_t point_in_time_ut,
+ NETDATA_DOUBLE number,
+ NETDATA_DOUBLE min_value __maybe_unused,
+ NETDATA_DOUBLE max_value __maybe_unused,
+ uint16_t count __maybe_unused,
+ uint16_t anomaly_count __maybe_unused,
+ SN_FLAGS flags)
+{
struct mem_collect_handle *ch = (struct mem_collect_handle *)collection_handle;
+ struct mem_metric_handle *mh = (struct mem_metric_handle *)ch->db_metric_handle;
RRDDIM *rd = ch->rd;
- for(int i = 0; i < rd->rrdset->entries ;i++)
- rd->db[i] = SN_EMPTY_SLOT;
+ time_t point_in_time_s = (time_t)(point_in_time_ut / USEC_PER_SEC);
+
+ internal_fatal(ch->rd != mh->rd, "RRDDIM: dimensions do not match");
+ check_metric_handle_from_rrddim(mh);
+ if(unlikely(point_in_time_s <= mh->last_updated_s))
+ return;
+
+ if(unlikely(mh->last_updated_s && point_in_time_s - mh->update_every_s > mh->last_updated_s))
+ rrddim_fill_the_gap(collection_handle, point_in_time_s);
+
+ rd->db[mh->current_entry] = pack_storage_number(number, flags);
+ mh->counter++;
+ mh->current_entry = (mh->current_entry + 1) >= mh->entries ? 0 : mh->current_entry + 1;
+ mh->last_updated_s = point_in_time_s;
}
int rrddim_collect_finalize(STORAGE_COLLECT_HANDLE *collection_handle) {
freez(collection_handle);
+ __atomic_sub_fetch(&rrddim_db_memory_size, sizeof(struct mem_collect_handle), __ATOMIC_RELAXED);
return 0;
}
// ----------------------------------------------------------------------------
-// get the total duration in seconds of the round robin database
-#define rrddim_duration(st) (( (time_t)(rd)->rrdset->counter >= (time_t)(rd)->rrdset->entries ? (time_t)(rd)->rrdset->entries : (time_t)(rd)->rrdset->counter ) * (time_t)(rd)->rrdset->update_every)
+// get the total duration in seconds of the round-robin database
+#define metric_duration(mh) (( (time_t)(mh)->counter >= (time_t)(mh)->entries ? (time_t)(mh)->entries : (time_t)(mh)->counter ) * (time_t)(mh)->update_every_s)
-// get the last slot updated in the round robin database
-#define rrddim_last_slot(rd) ((size_t)(((rd)->rrdset->current_entry == 0) ? (rd)->rrdset->entries - 1 : (rd)->rrdset->current_entry - 1))
+// get the last slot updated in the round-robin database
+#define rrddim_last_slot(mh) ((size_t)(((mh)->current_entry == 0) ? (mh)->entries - 1 : (mh)->current_entry - 1))
// return the slot that has the oldest value
-#define rrddim_first_slot(rd) ((size_t)((rd)->rrdset->counter >= (size_t)(rd)->rrdset->entries ? (rd)->rrdset->current_entry : 0))
+#define rrddim_first_slot(mh) ((size_t)((mh)->counter >= (size_t)(mh)->entries ? (mh)->current_entry : 0))
-// get the slot of the round robin database, for the given timestamp (t)
-// it always returns a valid slot, although may not be for the time requested if the time is outside the round robin database
+// get the slot of the round-robin database, for the given timestamp (t)
+// it always returns a valid slot, although it may not be for the time requested if the time is outside the round-robin database
// only valid when not using dbengine
-static inline size_t rrddim_time2slot(RRDDIM *rd, time_t t) {
+static inline size_t rrddim_time2slot(STORAGE_METRIC_HANDLE *db_metric_handle, time_t t) {
+ struct mem_metric_handle *mh = (struct mem_metric_handle *)db_metric_handle;
+ RRDDIM *rd = mh->rd;
+
size_t ret = 0;
- time_t last_entry_t = rrddim_query_latest_time((STORAGE_METRIC_HANDLE *)rd);
- time_t first_entry_t = rrddim_query_oldest_time((STORAGE_METRIC_HANDLE *)rd);
- size_t entries = rd->rrdset->entries;
- size_t first_slot = rrddim_first_slot(rd);
- size_t last_slot = rrddim_last_slot(rd);
- size_t update_every = rd->rrdset->update_every;
-
- if(t >= last_entry_t) {
+ time_t last_entry_s = rrddim_query_latest_time_s(db_metric_handle);
+ time_t first_entry_s = rrddim_query_oldest_time_s(db_metric_handle);
+ size_t entries = mh->entries;
+ size_t first_slot = rrddim_first_slot(mh);
+ size_t last_slot = rrddim_last_slot(mh);
+ size_t update_every = mh->update_every_s;
+
+ if(t >= last_entry_s) {
// the requested time is after the last entry we have
ret = last_slot;
}
else {
- if(t <= first_entry_t) {
+ if(t <= first_entry_s) {
// the requested time is before the first entry we have
ret = first_slot;
}
else {
- if(last_slot >= (size_t)((last_entry_t - t) / update_every))
- ret = last_slot - ((last_entry_t - t) / update_every);
+ if(last_slot >= (size_t)((last_entry_s - t) / update_every))
+ ret = last_slot - ((last_entry_s - t) / update_every);
else
- ret = last_slot - ((last_entry_t - t) / update_every) + entries;
+ ret = last_slot - ((last_entry_s - t) / update_every) + entries;
}
}
@@ -155,15 +289,18 @@ static inline size_t rrddim_time2slot(RRDDIM *rd, time_t t) {
return ret;
}
-// get the timestamp of a specific slot in the round robin database
+// get the timestamp of a specific slot in the round-robin database
// only valid when not using dbengine
-static inline time_t rrddim_slot2time(RRDDIM *rd, size_t slot) {
+static inline time_t rrddim_slot2time(STORAGE_METRIC_HANDLE *db_metric_handle, size_t slot) {
+ struct mem_metric_handle *mh = (struct mem_metric_handle *)db_metric_handle;
+ RRDDIM *rd = mh->rd;
+
time_t ret;
- time_t last_entry_t = rrddim_query_latest_time((STORAGE_METRIC_HANDLE *)rd);
- time_t first_entry_t = rrddim_query_oldest_time((STORAGE_METRIC_HANDLE *)rd);
- size_t entries = rd->rrdset->entries;
- size_t last_slot = rrddim_last_slot(rd);
- size_t update_every = rd->rrdset->update_every;
+ time_t last_entry_s = rrddim_query_latest_time_s(db_metric_handle);
+ time_t first_entry_s = rrddim_query_oldest_time_s(db_metric_handle);
+ size_t entries = mh->entries;
+ size_t last_slot = rrddim_last_slot(mh);
+ size_t update_every = mh->update_every_s;
if(slot >= entries) {
error("INTERNAL ERROR: caller of rrddim_slot2time() gives invalid slot %zu", slot);
@@ -171,18 +308,22 @@ static inline time_t rrddim_slot2time(RRDDIM *rd, size_t slot) {
}
if(slot > last_slot)
- ret = last_entry_t - (time_t)(update_every * (last_slot - slot + entries));
+ ret = last_entry_s - (time_t)(update_every * (last_slot - slot + entries));
else
- ret = last_entry_t - (time_t)(update_every * (last_slot - slot));
+ ret = last_entry_s - (time_t)(update_every * (last_slot - slot));
- if(unlikely(ret < first_entry_t)) {
- error("INTERNAL ERROR: rrddim_slot2time() on %s returns time too far in the past", rrddim_name(rd));
- ret = first_entry_t;
+ if(unlikely(ret < first_entry_s)) {
+ error("INTERNAL ERROR: rrddim_slot2time() on dimension '%s' of chart '%s' returned time (%ld) too far in the past (before first_entry_s %ld) for slot %zu",
+ rrddim_name(rd), rrdset_id(rd->rrdset), ret, first_entry_s, slot);
+
+ ret = first_entry_s;
}
- if(unlikely(ret > last_entry_t)) {
- error("INTERNAL ERROR: rrddim_slot2time() on %s returns time into the future", rrddim_name(rd));
- ret = last_entry_t;
+ if(unlikely(ret > last_entry_s)) {
+ error("INTERNAL ERROR: rrddim_slot2time() on dimension '%s' of chart '%s' returned time (%ld) too far into the future (after last_entry_s %ld) for slot %zu",
+ rrddim_name(rd), rrdset_id(rd->rrdset), ret, last_entry_s, slot);
+
+ ret = last_entry_s;
}
return ret;
@@ -191,23 +332,28 @@ static inline time_t rrddim_slot2time(RRDDIM *rd, size_t slot) {
// ----------------------------------------------------------------------------
// RRDDIM legacy database query functions
-void rrddim_query_init(STORAGE_METRIC_HANDLE *db_metric_handle, struct storage_engine_query_handle *handle, time_t start_time, time_t end_time) {
- RRDDIM *rd = (RRDDIM *)db_metric_handle;
+void rrddim_query_init(STORAGE_METRIC_HANDLE *db_metric_handle, struct storage_engine_query_handle *handle, time_t start_time_s, time_t end_time_s, STORAGE_PRIORITY priority __maybe_unused) {
+ struct mem_metric_handle *mh = (struct mem_metric_handle *)db_metric_handle;
+
+ check_metric_handle_from_rrddim(mh);
- handle->rd = rd;
- handle->start_time_s = start_time;
- handle->end_time_s = end_time;
+ handle->start_time_s = start_time_s;
+ handle->end_time_s = end_time_s;
+ handle->priority = priority;
struct mem_query_handle* h = mallocz(sizeof(struct mem_query_handle));
- h->slot = rrddim_time2slot(rd, start_time);
- h->last_slot = rrddim_time2slot(rd, end_time);
- h->dt = rd->rrdset->update_every;
+ h->db_metric_handle = db_metric_handle;
- h->next_timestamp = start_time;
- h->slot_timestamp = rrddim_slot2time(rd, h->slot);
- h->last_timestamp = rrddim_slot2time(rd, h->last_slot);
+ h->slot = rrddim_time2slot(db_metric_handle, start_time_s);
+ h->last_slot = rrddim_time2slot(db_metric_handle, end_time_s);
+ h->dt = mh->update_every_s;
+
+ h->next_timestamp = start_time_s;
+ h->slot_timestamp = rrddim_slot2time(db_metric_handle, h->slot);
+ h->last_timestamp = rrddim_slot2time(db_metric_handle, h->last_slot);
// info("RRDDIM QUERY INIT: start %ld, end %ld, next %ld, first %ld, last %ld, dt %ld", start_time, end_time, h->next_timestamp, h->slot_timestamp, h->last_timestamp, h->dt);
+ __atomic_add_fetch(&rrddim_db_memory_size, sizeof(struct mem_query_handle), __ATOMIC_RELAXED);
handle->handle = (STORAGE_QUERY_HANDLE *)h;
}
@@ -215,9 +361,11 @@ void rrddim_query_init(STORAGE_METRIC_HANDLE *db_metric_handle, struct storage_e
// IT IS REQUIRED TO **ALWAYS** SET ALL RETURN VALUES (current_time, end_time, flags)
// IT IS REQUIRED TO **ALWAYS** KEEP TRACK OF TIME, EVEN OUTSIDE THE DATABASE BOUNDARIES
STORAGE_POINT rrddim_query_next_metric(struct storage_engine_query_handle *handle) {
- RRDDIM *rd = handle->rd;
struct mem_query_handle* h = (struct mem_query_handle*)handle->handle;
- size_t entries = rd->rrdset->entries;
+ struct mem_metric_handle *mh = (struct mem_metric_handle *)h->db_metric_handle;
+ RRDDIM *rd = mh->rd;
+
+ size_t entries = mh->entries;
size_t slot = h->slot;
STORAGE_POINT sp;
@@ -227,16 +375,16 @@ STORAGE_POINT rrddim_query_next_metric(struct storage_engine_query_handle *handl
h->next_timestamp += h->dt;
// set this timestamp for our caller
- sp.start_time = this_timestamp - h->dt;
- sp.end_time = this_timestamp;
+ sp.start_time_s = this_timestamp - h->dt;
+ sp.end_time_s = this_timestamp;
if(unlikely(this_timestamp < h->slot_timestamp)) {
- storage_point_empty(sp, sp.start_time, sp.end_time);
+ storage_point_empty(sp, sp.start_time_s, sp.end_time_s);
return sp;
}
if(unlikely(this_timestamp > h->last_timestamp)) {
- storage_point_empty(sp, sp.start_time, sp.end_time);
+ storage_point_empty(sp, sp.start_time_s, sp.end_time_s);
return sp;
}
@@ -254,24 +402,34 @@ STORAGE_POINT rrddim_query_next_metric(struct storage_engine_query_handle *handl
}
int rrddim_query_is_finished(struct storage_engine_query_handle *handle) {
- struct mem_query_handle* h = (struct mem_query_handle*)handle->handle;
+ struct mem_query_handle *h = (struct mem_query_handle*)handle->handle;
return (h->next_timestamp > handle->end_time_s);
}
void rrddim_query_finalize(struct storage_engine_query_handle *handle) {
#ifdef NETDATA_INTERNAL_CHECKS
- if(!rrddim_query_is_finished(handle))
- error("QUERY: query for chart '%s' dimension '%s' has been stopped unfinished", rrdset_id(handle->rd->rrdset), rrddim_name(handle->rd));
+ struct mem_query_handle *h = (struct mem_query_handle*)handle->handle;
+ struct mem_metric_handle *mh = (struct mem_metric_handle *)h->db_metric_handle;
+
+ internal_error(!rrddim_query_is_finished(handle),
+ "QUERY: query for chart '%s' dimension '%s' has been stopped unfinished",
+ rrdset_id(mh->rd->rrdset), rrddim_name(mh->rd));
+
#endif
freez(handle->handle);
+ __atomic_sub_fetch(&rrddim_db_memory_size, sizeof(struct mem_query_handle), __ATOMIC_RELAXED);
+}
+
+time_t rrddim_query_align_to_optimal_before(struct storage_engine_query_handle *rrddim_handle) {
+ return rrddim_handle->end_time_s;
}
-time_t rrddim_query_latest_time(STORAGE_METRIC_HANDLE *db_metric_handle) {
- RRDDIM *rd = (RRDDIM *)db_metric_handle;
- return rd->rrdset->last_updated.tv_sec;
+time_t rrddim_query_latest_time_s(STORAGE_METRIC_HANDLE *db_metric_handle) {
+ struct mem_metric_handle *mh = (struct mem_metric_handle *)db_metric_handle;
+ return mh->last_updated_s;
}
-time_t rrddim_query_oldest_time(STORAGE_METRIC_HANDLE *db_metric_handle) {
- RRDDIM *rd = (RRDDIM *)db_metric_handle;
- return (time_t)(rd->rrdset->last_updated.tv_sec - rrddim_duration(rd));
+time_t rrddim_query_oldest_time_s(STORAGE_METRIC_HANDLE *db_metric_handle) {
+ struct mem_metric_handle *mh = (struct mem_metric_handle *)db_metric_handle;
+ return (time_t)(mh->last_updated_s - metric_duration(mh));
}
diff --git a/database/ram/rrddim_mem.h b/database/ram/rrddim_mem.h
index 79c59f11..373a2bd7 100644
--- a/database/ram/rrddim_mem.h
+++ b/database/ram/rrddim_mem.h
@@ -6,12 +6,12 @@
#include "database/rrd.h"
struct mem_collect_handle {
+ STORAGE_METRIC_HANDLE *db_metric_handle;
RRDDIM *rd;
- long slot;
- long entries;
};
struct mem_query_handle {
+ STORAGE_METRIC_HANDLE *db_metric_handle;
time_t dt;
time_t next_timestamp;
time_t last_timestamp;
@@ -25,12 +25,14 @@ STORAGE_METRIC_HANDLE *rrddim_metric_get(STORAGE_INSTANCE *db_instance, uuid_t *
STORAGE_METRIC_HANDLE *rrddim_metric_dup(STORAGE_METRIC_HANDLE *db_metric_handle);
void rrddim_metric_release(STORAGE_METRIC_HANDLE *db_metric_handle);
+bool rrddim_metric_retention_by_uuid(STORAGE_INSTANCE *db_instance, uuid_t *uuid, time_t *first_entry_s, time_t *last_entry_s);
+
STORAGE_METRICS_GROUP *rrddim_metrics_group_get(STORAGE_INSTANCE *db_instance, uuid_t *uuid);
void rrddim_metrics_group_release(STORAGE_INSTANCE *db_instance, STORAGE_METRICS_GROUP *smg);
STORAGE_COLLECT_HANDLE *rrddim_collect_init(STORAGE_METRIC_HANDLE *db_metric_handle, uint32_t update_every, STORAGE_METRICS_GROUP *smg);
void rrddim_store_metric_change_collection_frequency(STORAGE_COLLECT_HANDLE *collection_handle, int update_every);
-void rrddim_collect_store_metric(STORAGE_COLLECT_HANDLE *collection_handle, usec_t point_in_time, NETDATA_DOUBLE number,
+void rrddim_collect_store_metric(STORAGE_COLLECT_HANDLE *collection_handle, usec_t point_in_time_ut, NETDATA_DOUBLE number,
NETDATA_DOUBLE min_value,
NETDATA_DOUBLE max_value,
uint16_t count,
@@ -39,11 +41,12 @@ void rrddim_collect_store_metric(STORAGE_COLLECT_HANDLE *collection_handle, usec
void rrddim_store_metric_flush(STORAGE_COLLECT_HANDLE *collection_handle);
int rrddim_collect_finalize(STORAGE_COLLECT_HANDLE *collection_handle);
-void rrddim_query_init(STORAGE_METRIC_HANDLE *db_metric_handle, struct storage_engine_query_handle *handle, time_t start_time, time_t end_time);
+void rrddim_query_init(STORAGE_METRIC_HANDLE *db_metric_handle, struct storage_engine_query_handle *handle, time_t start_time_s, time_t end_time_s, STORAGE_PRIORITY priority);
STORAGE_POINT rrddim_query_next_metric(struct storage_engine_query_handle *handle);
int rrddim_query_is_finished(struct storage_engine_query_handle *handle);
void rrddim_query_finalize(struct storage_engine_query_handle *handle);
-time_t rrddim_query_latest_time(STORAGE_METRIC_HANDLE *db_metric_handle);
-time_t rrddim_query_oldest_time(STORAGE_METRIC_HANDLE *db_metric_handle);
+time_t rrddim_query_latest_time_s(STORAGE_METRIC_HANDLE *db_metric_handle);
+time_t rrddim_query_oldest_time_s(STORAGE_METRIC_HANDLE *db_metric_handle);
+time_t rrddim_query_align_to_optimal_before(struct storage_engine_query_handle *rrddim_handle);
#endif
diff --git a/database/rrd.c b/database/rrd.c
index df364419..d489ddb8 100644
--- a/database/rrd.c
+++ b/database/rrd.c
@@ -135,7 +135,7 @@ const char *rrdset_type_name(RRDSET_TYPE chart_type) {
// ----------------------------------------------------------------------------
// RRD - cache directory
-char *rrdset_cache_dir(RRDHOST *host, const char *id) {
+char *rrdhost_cache_dir_for_rrdset_alloc(RRDHOST *host, const char *id) {
char *ret = NULL;
char b[FILENAME_MAX + 1];
diff --git a/database/rrd.h b/database/rrd.h
index 0796ff90..42eeb165 100644
--- a/database/rrd.h
+++ b/database/rrd.h
@@ -30,11 +30,12 @@ typedef struct rrdhost_acquired RRDHOST_ACQUIRED;
typedef struct rrdset_acquired RRDSET_ACQUIRED;
typedef struct rrddim_acquired RRDDIM_ACQUIRED;
-typedef void *ml_host_t;
-typedef void *ml_dimension_t;
+typedef struct ml_host ml_host_t;
+typedef struct ml_chart ml_chart_t;
+typedef struct ml_dimension ml_dimension_t;
-typedef enum {
- QUERY_SOURCE_UNKNOWN,
+typedef enum __attribute__ ((__packed__)) {
+ QUERY_SOURCE_UNKNOWN = 0,
QUERY_SOURCE_API_DATA,
QUERY_SOURCE_API_BADGE,
QUERY_SOURCE_API_WEIGHTS,
@@ -43,6 +44,19 @@ typedef enum {
QUERY_SOURCE_UNITTEST,
} QUERY_SOURCE;
+typedef enum __attribute__ ((__packed__)) storage_priority {
+ STORAGE_PRIORITY_INTERNAL_DBENGINE = 0,
+ STORAGE_PRIORITY_INTERNAL_QUERY_PREP,
+
+ // query priorities
+ STORAGE_PRIORITY_HIGH,
+ STORAGE_PRIORITY_NORMAL,
+ STORAGE_PRIORITY_LOW,
+ STORAGE_PRIORITY_BEST_EFFORT,
+
+ STORAGE_PRIORITY_INTERNAL_MAX_DONT_USE,
+} STORAGE_PRIORITY;
+
// forward declarations
struct rrddim_tier;
@@ -52,6 +66,32 @@ struct rrdengine_instance;
struct pg_cache_page_index;
#endif
+// ----------------------------------------------------------------------------
+// memory mode
+
+typedef enum __attribute__ ((__packed__)) rrd_memory_mode {
+ RRD_MEMORY_MODE_NONE = 0,
+ RRD_MEMORY_MODE_RAM = 1,
+ RRD_MEMORY_MODE_MAP = 2,
+ RRD_MEMORY_MODE_SAVE = 3,
+ RRD_MEMORY_MODE_ALLOC = 4,
+ RRD_MEMORY_MODE_DBENGINE = 5,
+
+ // this is 8-bit
+} RRD_MEMORY_MODE;
+
+#define RRD_MEMORY_MODE_NONE_NAME "none"
+#define RRD_MEMORY_MODE_RAM_NAME "ram"
+#define RRD_MEMORY_MODE_MAP_NAME "map"
+#define RRD_MEMORY_MODE_SAVE_NAME "save"
+#define RRD_MEMORY_MODE_ALLOC_NAME "alloc"
+#define RRD_MEMORY_MODE_DBENGINE_NAME "dbengine"
+
+extern RRD_MEMORY_MODE default_rrd_memory_mode;
+
+const char *rrd_memory_mode_name(RRD_MEMORY_MODE id);
+RRD_MEMORY_MODE rrd_memory_mode_id(const char *name);
+
#include "daemon/common.h"
#include "web/api/queries/query.h"
#include "web/api/queries/rrdr.h"
@@ -63,34 +103,48 @@ struct pg_cache_page_index;
#include "streaming/rrdpush.h"
#include "aclk/aclk_rrdhost_state.h"
#include "sqlite/sqlite_health.h"
+
+typedef struct storage_query_handle STORAGE_QUERY_HANDLE;
+
+// iterator state for RRD dimension data queries
+struct storage_engine_query_handle {
+ time_t start_time_s;
+ time_t end_time_s;
+ STORAGE_PRIORITY priority;
+ STORAGE_QUERY_HANDLE* handle;
+};
+
+typedef struct storage_point {
+ NETDATA_DOUBLE min; // when count > 1, this is the minimum among them
+ NETDATA_DOUBLE max; // when count > 1, this is the maximum among them
+ NETDATA_DOUBLE sum; // the point sum - divided by count gives the average
+
+ // end_time - start_time = point duration
+ time_t start_time_s; // the time the point starts
+ time_t end_time_s; // the time the point ends
+
+ size_t count; // the number of original points aggregated
+ size_t anomaly_count; // the number of original points found anomalous
+
+ SN_FLAGS flags; // flags stored with the point
+} STORAGE_POINT;
+
#include "rrdcontext.h"
extern bool unittest_running;
extern bool dbengine_enabled;
extern size_t storage_tiers;
+extern bool use_direct_io;
extern size_t storage_tiers_grouping_iterations[RRD_STORAGE_TIERS];
-typedef enum {
- RRD_BACKFILL_NONE,
+typedef enum __attribute__ ((__packed__)) {
+ RRD_BACKFILL_NONE = 0,
RRD_BACKFILL_FULL,
RRD_BACKFILL_NEW
} RRD_BACKFILL;
extern RRD_BACKFILL storage_tiers_backfill[RRD_STORAGE_TIERS];
-enum {
- CONTEXT_FLAGS_ARCHIVE = 0x01,
- CONTEXT_FLAGS_CHART = 0x02,
- CONTEXT_FLAGS_CONTEXT = 0x04
-};
-
-struct context_param {
- RRDDIM *rd;
- time_t first_entry_t;
- time_t last_entry_t;
- uint8_t flags;
-};
-
#define UPDATE_EVERY 1
#define UPDATE_EVERY_MAX 3600
@@ -100,7 +154,19 @@ struct context_param {
extern int default_rrd_update_every;
extern int default_rrd_history_entries;
extern int gap_when_lost_iterations_above;
-extern time_t rrdset_free_obsolete_time;
+extern time_t rrdset_free_obsolete_time_s;
+
+#if defined(ENV32BIT)
+#define MIN_LIBUV_WORKER_THREADS 8
+#define MAX_LIBUV_WORKER_THREADS 64
+#define RESERVED_LIBUV_WORKER_THREADS 3
+#else
+#define MIN_LIBUV_WORKER_THREADS 16
+#define MAX_LIBUV_WORKER_THREADS 128
+#define RESERVED_LIBUV_WORKER_THREADS 6
+#endif
+
+extern int libuv_worker_threads;
#define RRD_ID_LENGTH_MAX 200
@@ -110,10 +176,10 @@ typedef long long total_number;
// ----------------------------------------------------------------------------
// chart types
-typedef enum rrdset_type {
+typedef enum __attribute__ ((__packed__)) rrdset_type {
RRDSET_TYPE_LINE = 0,
RRDSET_TYPE_AREA = 1,
- RRDSET_TYPE_STACKED = 2
+ RRDSET_TYPE_STACKED = 2,
} RRDSET_TYPE;
#define RRDSET_TYPE_LINE_NAME "line"
@@ -125,36 +191,9 @@ const char *rrdset_type_name(RRDSET_TYPE chart_type);
// ----------------------------------------------------------------------------
-// memory mode
-
-typedef enum rrd_memory_mode {
- RRD_MEMORY_MODE_NONE = 0,
- RRD_MEMORY_MODE_RAM = 1,
- RRD_MEMORY_MODE_MAP = 2,
- RRD_MEMORY_MODE_SAVE = 3,
- RRD_MEMORY_MODE_ALLOC = 4,
- RRD_MEMORY_MODE_DBENGINE = 5,
-
- // this is 8-bit
-} RRD_MEMORY_MODE;
-
-#define RRD_MEMORY_MODE_NONE_NAME "none"
-#define RRD_MEMORY_MODE_RAM_NAME "ram"
-#define RRD_MEMORY_MODE_MAP_NAME "map"
-#define RRD_MEMORY_MODE_SAVE_NAME "save"
-#define RRD_MEMORY_MODE_ALLOC_NAME "alloc"
-#define RRD_MEMORY_MODE_DBENGINE_NAME "dbengine"
-
-extern RRD_MEMORY_MODE default_rrd_memory_mode;
-
-const char *rrd_memory_mode_name(RRD_MEMORY_MODE id);
-RRD_MEMORY_MODE rrd_memory_mode_id(const char *name);
-
-
-// ----------------------------------------------------------------------------
// algorithms types
-typedef enum rrd_algorithm {
+typedef enum __attribute__ ((__packed__)) rrd_algorithm {
RRD_ALGORITHM_ABSOLUTE = 0,
RRD_ALGORITHM_INCREMENTAL = 1,
RRD_ALGORITHM_PCENT_OVER_DIFF_TOTAL = 2,
@@ -185,7 +224,7 @@ DICTIONARY *rrdfamily_rrdvars_dict(const RRDFAMILY_ACQUIRED *rf);
// flags & options
// options are permanent configuration options (no atomics to alter/access them)
-typedef enum rrddim_options {
+typedef enum __attribute__ ((__packed__)) rrddim_options {
RRDDIM_OPTION_NONE = 0,
RRDDIM_OPTION_HIDDEN = (1 << 0), // this dimension will not be offered to callers
RRDDIM_OPTION_DONT_DETECT_RESETS_OR_OVERFLOWS = (1 << 1), // do not offer RESET or OVERFLOW info to callers
@@ -199,7 +238,7 @@ typedef enum rrddim_options {
#define rrddim_option_clear(rd, option) (rd)->options &= ~(option)
// flags are runtime changing status flags (atomics are required to alter/access them)
-typedef enum rrddim_flags {
+typedef enum __attribute__ ((__packed__)) rrddim_flags {
RRDDIM_FLAG_NONE = 0,
RRDDIM_FLAG_PENDING_HEALTH_INITIALIZATION = (1 << 0),
@@ -218,7 +257,7 @@ typedef enum rrddim_flags {
#define rrddim_flag_set(rd, flag) __atomic_or_fetch(&((rd)->flags), (flag), __ATOMIC_SEQ_CST)
#define rrddim_flag_clear(rd, flag) __atomic_and_fetch(&((rd)->flags), ~(flag), __ATOMIC_SEQ_CST)
-typedef enum rrdlabel_source {
+typedef enum __attribute__ ((__packed__)) rrdlabel_source {
RRDLABEL_SRC_AUTO = (1 << 0), // set when Netdata found the label by some automation
RRDLABEL_SRC_CONFIG = (1 << 1), // set when the user configured the label
RRDLABEL_SRC_K8S = (1 << 2), // set when this label is found from k8s (RRDLABEL_SRC_AUTO should also be set)
@@ -267,6 +306,25 @@ int rrdlabels_unittest(void);
bool exporting_labels_filter_callback(const char *name, const char *value, RRDLABEL_SRC ls, void *data);
// ----------------------------------------------------------------------------
+// engine-specific iterator state for dimension data collection
+typedef struct storage_collect_handle STORAGE_COLLECT_HANDLE;
+
+// ----------------------------------------------------------------------------
+// Storage tier data for every dimension
+
+struct rrddim_tier {
+ STORAGE_POINT virtual_point;
+ size_t tier_grouping;
+ time_t next_point_end_time_s;
+ STORAGE_METRIC_HANDLE *db_metric_handle; // the metric handle inside the database
+ STORAGE_COLLECT_HANDLE *db_collection_handle; // the data collection handle
+ struct storage_engine_collect_ops *collect_ops;
+ struct storage_engine_query_ops *query_ops;
+};
+
+void rrdr_fill_tier_gap_from_smaller_tiers(RRDDIM *rd, size_t tier, time_t now_s);
+
+// ----------------------------------------------------------------------------
// RRD DIMENSION - this is a metric
struct rrddim {
@@ -278,10 +336,10 @@ struct rrddim {
STRING *id; // the id of this dimension (for internal identification)
STRING *name; // the name of this dimension (as presented to user)
- RRD_ALGORITHM algorithm:8; // the algorithm that is applied to add new collected values
- RRDDIM_OPTIONS options:8; // permanent configuration options
- RRD_MEMORY_MODE rrd_memory_mode:8; // the memory mode for this dimension
- /*RRDDIM_FLAGS*/ uint8_t flags; // run time changing status flags
+ RRD_ALGORITHM algorithm; // the algorithm that is applied to add new collected values
+ RRDDIM_OPTIONS options; // permanent configuration options
+ RRD_MEMORY_MODE rrd_memory_mode; // the memory mode for this dimension
+ RRDDIM_FLAGS flags; // run time changing status flags
bool updated; // 1 when the dimension has been updated since the last processing
bool exposed; // 1 when set what have sent this dimension to the central netdata
@@ -296,7 +354,7 @@ struct rrddim {
// ------------------------------------------------------------------------
// operational state members
- ml_dimension_t ml_dimension; // machine learning data about this dimension
+ ml_dimension_t *ml_dimension; // machine learning data about this dimension
// ------------------------------------------------------------------------
// linking to siblings and parents
@@ -308,7 +366,7 @@ struct rrddim {
// ------------------------------------------------------------------------
// data collection members
- struct rrddim_tier *tiers[RRD_STORAGE_TIERS]; // our tiers of databases
+ struct rrddim_tier tiers[RRD_STORAGE_TIERS]; // our tiers of databases
struct timeval last_collected_time; // when was this dimension last updated
// this is actual date time we updated the last_collected_value
@@ -361,49 +419,28 @@ void rrddim_memory_file_save(RRDDIM *rd);
// ----------------------------------------------------------------------------
-typedef struct storage_point {
- NETDATA_DOUBLE min; // when count > 1, this is the minimum among them
- NETDATA_DOUBLE max; // when count > 1, this is the maximum among them
- NETDATA_DOUBLE sum; // the point sum - divided by count gives the average
-
- // end_time - start_time = point duration
- time_t start_time; // the time the point starts
- time_t end_time; // the time the point ends
-
- unsigned count; // the number of original points aggregated
- unsigned anomaly_count; // the number of original points found anomalous
-
- SN_FLAGS flags; // flags stored with the point
-} STORAGE_POINT;
-
#define storage_point_unset(x) do { \
(x).min = (x).max = (x).sum = NAN; \
(x).count = 0; \
(x).anomaly_count = 0; \
(x).flags = SN_FLAG_NONE; \
- (x).start_time = 0; \
- (x).end_time = 0; \
+ (x).start_time_s = 0; \
+ (x).end_time_s = 0; \
} while(0)
-#define storage_point_empty(x, start_t, end_t) do { \
+#define storage_point_empty(x, start_s, end_s) do { \
(x).min = (x).max = (x).sum = NAN; \
(x).count = 1; \
(x).anomaly_count = 0; \
(x).flags = SN_FLAG_NONE; \
- (x).start_time = start_t; \
- (x).end_time = end_t; \
+ (x).start_time_s = start_s; \
+ (x).end_time_s = end_s; \
} while(0)
-#define storage_point_is_unset(x) (!(x).count)
-#define storage_point_is_empty(x) (!netdata_double_isnumber((x).sum))
-
-// ----------------------------------------------------------------------------
-// engine-specific iterator state for dimension data collection
-typedef struct storage_collect_handle STORAGE_COLLECT_HANDLE;
+#define STORAGE_POINT_UNSET { .min = NAN, .max = NAN, .sum = NAN, .count = 0, .anomaly_count = 0, .flags = SN_FLAG_NONE, .start_time_s = 0, .end_time_s = 0 }
-// ----------------------------------------------------------------------------
-// engine-specific iterator state for dimension data queries
-typedef struct storage_query_handle STORAGE_QUERY_HANDLE;
+#define storage_point_is_unset(x) (!(x).count)
+#define storage_point_is_gap(x) (!netdata_double_isnumber((x).sum))
// ------------------------------------------------------------------------
// function pointers that handle data collection
@@ -429,18 +466,11 @@ struct storage_engine_collect_ops {
};
// ----------------------------------------------------------------------------
-// iterator state for RRD dimension data queries
-struct storage_engine_query_handle {
- RRDDIM *rd;
- time_t start_time_s;
- time_t end_time_s;
- STORAGE_QUERY_HANDLE* handle;
-};
// function pointers that handle database queries
struct storage_engine_query_ops {
// run this before starting a series of next_metric() database queries
- void (*init)(STORAGE_METRIC_HANDLE *db_metric_handle, struct storage_engine_query_handle *handle, time_t start_time, time_t end_time);
+ void (*init)(STORAGE_METRIC_HANDLE *db_metric_handle, struct storage_engine_query_handle *handle, time_t start_time_s, time_t end_time_s, STORAGE_PRIORITY priority);
// run this to load each metric number from the database
STORAGE_POINT (*next_metric)(struct storage_engine_query_handle *handle);
@@ -452,10 +482,14 @@ struct storage_engine_query_ops {
void (*finalize)(struct storage_engine_query_handle *handle);
// get the timestamp of the last entry of this metric
- time_t (*latest_time)(STORAGE_METRIC_HANDLE *db_metric_handle);
+ time_t (*latest_time_s)(STORAGE_METRIC_HANDLE *db_metric_handle);
// get the timestamp of the first entry of this metric
- time_t (*oldest_time)(STORAGE_METRIC_HANDLE *db_metric_handle);
+ time_t (*oldest_time_s)(STORAGE_METRIC_HANDLE *db_metric_handle);
+
+ // adapt 'before' timestamp to the optimal for the query
+ // can only move 'before' ahead (to the future)
+ time_t (*align_to_optimal_before)(struct storage_engine_query_handle *handle);
};
typedef struct storage_engine STORAGE_ENGINE;
@@ -468,6 +502,7 @@ typedef struct storage_engine_api {
STORAGE_METRIC_HANDLE *(*metric_get_or_create)(RRDDIM *rd, STORAGE_INSTANCE *instance);
void (*metric_release)(STORAGE_METRIC_HANDLE *);
STORAGE_METRIC_HANDLE *(*metric_dup)(STORAGE_METRIC_HANDLE *);
+ bool (*metric_retention_by_uuid)(STORAGE_INSTANCE *db_instance, uuid_t *uuid, time_t *first_entry_s, time_t *last_entry_s);
// operations
struct storage_engine_collect_ops collect_ops;
@@ -484,21 +519,6 @@ STORAGE_ENGINE* storage_engine_get(RRD_MEMORY_MODE mmode);
STORAGE_ENGINE* storage_engine_find(const char* name);
// ----------------------------------------------------------------------------
-// Storage tier data for every dimension
-
-struct rrddim_tier {
- size_t tier_grouping;
- STORAGE_METRIC_HANDLE *db_metric_handle; // the metric handle inside the database
- STORAGE_COLLECT_HANDLE *db_collection_handle; // the data collection handle
- STORAGE_POINT virtual_point;
- time_t next_point_time;
- struct storage_engine_collect_ops *collect_ops;
- struct storage_engine_query_ops *query_ops;
-};
-
-void rrdr_fill_tier_gap_from_smaller_tiers(RRDDIM *rd, size_t tier, time_t now);
-
-// ----------------------------------------------------------------------------
// these loop macros make sure the linked list is accessed with the right lock
#define rrddim_foreach_read(rd, st) \
@@ -520,7 +540,7 @@ void rrdr_fill_tier_gap_from_smaller_tiers(RRDDIM *rd, size_t tier, time_t now);
// flags are set/unset in a manner that is not thread safe
// and may lead to missing information.
-typedef enum rrdset_flags {
+typedef enum __attribute__ ((__packed__)) rrdset_flags {
RRDSET_FLAG_DETAIL = (1 << 1), // if set, the data set should be considered as a detail of another
// (the master data set should be the one that has the same family and is not detail)
RRDSET_FLAG_DEBUG = (1 << 2), // enables or disables debugging for a chart
@@ -554,6 +574,8 @@ typedef enum rrdset_flags {
RRDSET_FLAG_RECEIVER_REPLICATION_FINISHED = (1 << 25), // the receiving side has completed replication
RRDSET_FLAG_UPSTREAM_SEND_VARIABLES = (1 << 26), // a custom variable has been updated and needs to be exposed to parent
+
+ RRDSET_FLAG_COLLECTION_FINISHED = (1 << 27), // when set, data collection is not available for this chart
} RRDSET_FLAGS;
#define rrdset_flag_check(st, flag) (__atomic_load_n(&((st)->flags), __ATOMIC_SEQ_CST) & (flag))
@@ -595,6 +617,8 @@ struct rrdset {
DICTIONARY *rrddimvar_root_index; // dimension variables
// we use this dictionary to manage their allocation
+ ml_chart_t *ml_chart;
+
// ------------------------------------------------------------------------
// operational state members
@@ -603,10 +627,6 @@ struct rrdset {
DICTIONARY *rrddim_root_index; // dimensions index
- int gap_when_lost_iterations_above; // after how many lost iterations a gap should be stored
- // netdata will interpolate values for gaps lower than this
- // TODO - use the global - all charts have the same value
-
STORAGE_METRICS_GROUP *storage_metrics_groups[RRD_STORAGE_TIERS];
// ------------------------------------------------------------------------
@@ -620,10 +640,12 @@ struct rrdset {
// ------------------------------------------------------------------------
// data collection members
+ SPINLOCK data_collection_lock;
+
size_t counter; // the number of times we added values to this database
size_t counter_done; // the number of times rrdset_done() has been called
- time_t last_accessed_time; // the last time this RRDSET has been accessed
+ time_t last_accessed_time_s; // the last time this RRDSET has been accessed
usec_t usec_since_last_update; // the time in microseconds since the last collection of data
@@ -637,7 +659,7 @@ struct rrdset {
// ------------------------------------------------------------------------
// data collection - streaming to parents, temp variables
- time_t upstream_resync_time; // the timestamp up to which we should resync clock upstream
+ time_t upstream_resync_time_s; // the timestamp up to which we should resync clock upstream
// ------------------------------------------------------------------------
// db mode SAVE, MAP specifics
@@ -645,7 +667,6 @@ struct rrdset {
// (RRDSET_DB_STATE ptr to an undefined structure, and a call to clean this up during destruction)
char *cache_dir; // the directory to store dimensions
- unsigned long memsize; // how much mem we have allocated for this (without dimensions)
void *st_on_file; // compatibility with V019 RRDSET files
// ------------------------------------------------------------------------
@@ -735,7 +756,7 @@ bool rrdset_memory_load_or_create_map_save(RRDSET *st_on_file, RRD_MEMORY_MODE m
// flags are set/unset in a manner that is not thread safe
// and may lead to missing information.
-typedef enum rrdhost_flags {
+typedef enum __attribute__ ((__packed__)) rrdhost_flags {
// Orphan, Archived and Obsolete flags
RRDHOST_FLAG_ORPHAN = (1 << 10), // this host is orphan (not receiving data)
RRDHOST_FLAG_ARCHIVED = (1 << 11), // The host is archived, no collected charts yet
@@ -748,7 +769,6 @@ typedef enum rrdhost_flags {
RRDHOST_FLAG_RRDPUSH_SENDER_CONNECTED = (1 << 16), // When set, the host is connected to a parent
RRDHOST_FLAG_RRDPUSH_SENDER_READY_4_METRICS = (1 << 17), // when set, rrdset_done() should push metrics to parent
RRDHOST_FLAG_RRDPUSH_SENDER_LOGGED_STATUS = (1 << 18), // when set, we have logged the status of metrics streaming
- RRDHOST_FLAG_RRDPUSH_SENDER_JOIN = (1 << 19), // When set, we want to join the sender thread
// Health
RRDHOST_FLAG_PENDING_HEALTH_INITIALIZATION = (1 << 20), // contains charts and dims with uninitialized variables
@@ -762,8 +782,11 @@ typedef enum rrdhost_flags {
RRDHOST_FLAG_ACLK_STREAM_CONTEXTS = (1 << 24), // when set, we should send ACLK stream context updates
// Metadata
RRDHOST_FLAG_METADATA_UPDATE = (1 << 25), // metadata needs to be stored in the database
+ RRDHOST_FLAG_METADATA_LABELS = (1 << 26), // metadata needs to be stored in the database
+ RRDHOST_FLAG_METADATA_INFO = (1 << 27), // metadata needs to be stored in the database
+ RRDHOST_FLAG_METADATA_CLAIMID = (1 << 28), // metadata needs to be stored in the database
- RRDHOST_FLAG_RRDPUSH_RECEIVER_DISCONNECTED = ( 1 << 26), // set when the receiver part is disconnected
+ RRDHOST_FLAG_RRDPUSH_RECEIVER_DISCONNECTED = (1 << 29), // set when the receiver part is disconnected
} RRDHOST_FLAGS;
#define rrdhost_flag_check(host, flag) (__atomic_load_n(&((host)->flags), __ATOMIC_SEQ_CST) & (flag))
@@ -777,7 +800,7 @@ typedef enum rrdhost_flags {
#define rrdset_debug(st, fmt, args...) debug_dummy()
#endif
-typedef enum {
+typedef enum __attribute__ ((__packed__)) {
// Indexing
RRDHOST_OPTION_INDEXED_MACHINE_GUID = (1 << 0), // when set, we have indexed its machine guid
RRDHOST_OPTION_INDEXED_HOSTNAME = (1 << 1), // when set, we have indexed its hostname
@@ -880,6 +903,15 @@ typedef struct alarm_log {
netdata_rwlock_t alarm_log_rwlock;
} ALARM_LOG;
+typedef struct health {
+ unsigned int health_enabled; // 1 when this host has health enabled
+ time_t health_delay_up_to; // a timestamp to delay alarms processing up to
+ STRING *health_default_exec; // the full path of the alarms notifications program
+ STRING *health_default_recipient; // the default recipient for all alarms
+ size_t health_log_entries_written; // the number of alarm events written to the alarms event log
+ uint32_t health_default_warn_repeat_every; // the default value for the interval between repeating warning notifications
+ uint32_t health_default_crit_repeat_every; // the default value for the interval between repeating critical notifications
+} HEALTH;
// ----------------------------------------------------------------------------
// RRD HOST
@@ -984,10 +1016,10 @@ struct rrdhost {
// ------------------------------------------------------------------------
// streaming of data from remote hosts - rrdpush receiver
- time_t senders_connect_time; // the time the last sender was connected
- time_t senders_last_chart_command; // the time of the last CHART streaming command
- time_t senders_disconnected_time; // the time the last sender was disconnected
- int senders_count; // number of senders currently streaming
+ time_t child_connect_time; // the time the last sender was connected
+ time_t child_last_chart_command; // the time of the last CHART streaming command
+ time_t child_disconnected_time; // the time the last sender was disconnected
+ int connected_children_count; // number of senders currently streaming
struct receiver_state *receiver;
netdata_mutex_t receiver_lock;
@@ -997,18 +1029,8 @@ struct rrdhost {
// ------------------------------------------------------------------------
// health monitoring options
- unsigned int health_enabled; // 1 when this host has health enabled
- bool health_spawn; // true when health thread is running
- netdata_thread_t health_thread; // the health thread
- unsigned int aclk_alert_reloaded; // 1 on thread start and health reload, 0 after removed are sent
- time_t health_delay_up_to; // a timestamp to delay alarms processing up to
- STRING *health_default_exec; // the full path of the alarms notifications program
- STRING *health_default_recipient; // the default recipient for all alarms
- char *health_log_filename; // the alarms event log filename
- size_t health_log_entries_written; // the number of alarm events written to the alarms event log
- FILE *health_log_fp; // the FILE pointer to the open alarms event log file
- uint32_t health_default_warn_repeat_every; // the default value for the interval between repeating warning notifications
- uint32_t health_default_crit_repeat_every; // the default value for the interval between repeating critical notifications
+ // health variables
+ HEALTH health;
// all RRDCALCs are primarily allocated and linked here
DICTIONARY *rrdcalc_root_index;
@@ -1024,11 +1046,11 @@ struct rrdhost {
// ------------------------------------------------------------------------
// locks
- netdata_rwlock_t rrdhost_rwlock; // lock for this RRDHOST (protects rrdset_root linked list)
+ SPINLOCK rrdhost_update_lock;
// ------------------------------------------------------------------------
// ML handle
- ml_host_t ml_host;
+ ml_host_t *ml_host;
// ------------------------------------------------------------------------
// Support for host-level labels
@@ -1072,10 +1094,6 @@ extern RRDHOST *localhost;
#define rrdhost_program_name(host) string2str((host)->program_name)
#define rrdhost_program_version(host) string2str((host)->program_version)
-#define rrdhost_rdlock(host) netdata_rwlock_rdlock(&((host)->rrdhost_rwlock))
-#define rrdhost_wrlock(host) netdata_rwlock_wrlock(&((host)->rrdhost_rwlock))
-#define rrdhost_unlock(host) netdata_rwlock_unlock(&((host)->rrdhost_rwlock))
-
#define rrdhost_aclk_state_lock(host) netdata_mutex_lock(&((host)->aclk_state_lock))
#define rrdhost_aclk_state_unlock(host) netdata_mutex_unlock(&((host)->aclk_state_lock))
@@ -1090,16 +1108,15 @@ extern RRDHOST *localhost;
#define rrdhost_sender_replicating_charts_zero(host) (__atomic_store_n(&((host)->rrdpush_sender_replicating_charts), 0, __ATOMIC_RELAXED))
extern DICTIONARY *rrdhost_root_index;
-long rrdhost_hosts_available(void);
+size_t rrdhost_hosts_available(void);
// ----------------------------------------------------------------------------
-// these loop macros make sure the linked list is accessed with the right lock
#define rrdhost_foreach_read(var) \
- for((var) = localhost, rrd_check_rdlock(); var ; (var) = (var)->next)
+ for((var) = localhost; var ; (var) = (var)->next)
#define rrdhost_foreach_write(var) \
- for((var) = localhost, rrd_check_wrlock(); var ; (var) = (var)->next)
+ for((var) = localhost; var ; (var) = (var)->next)
// ----------------------------------------------------------------------------
@@ -1122,10 +1139,9 @@ void rrddim_index_destroy(RRDSET *st);
// ----------------------------------------------------------------------------
-extern size_t rrd_hosts_available;
-extern time_t rrdhost_free_orphan_time;
+extern time_t rrdhost_free_orphan_time_s;
-int rrd_init(char *hostname, struct rrdhost_system_info *system_info);
+int rrd_init(char *hostname, struct rrdhost_system_info *system_info, bool unittest);
RRDHOST *rrdhost_find_by_hostname(const char *hostname);
RRDHOST *rrdhost_find_by_guid(const char *guid);
@@ -1156,57 +1172,8 @@ RRDHOST *rrdhost_find_or_create(
, bool is_archived
);
-void rrdhost_update(RRDHOST *host
- , const char *hostname
- , const char *registry_hostname
- , const char *guid
- , const char *os
- , const char *timezone
- , const char *abbrev_timezone
- , int32_t utc_offset
- , const char *tags
- , const char *program_name
- , const char *program_version
- , int update_every
- , long history
- , RRD_MEMORY_MODE mode
- , unsigned int health_enabled
- , unsigned int rrdpush_enabled
- , char *rrdpush_destination
- , char *rrdpush_api_key
- , char *rrdpush_send_charts_matching
- , bool rrdpush_enable_replication
- , time_t rrdpush_seconds_to_replicate
- , time_t rrdpush_replication_step
- , struct rrdhost_system_info *system_info
-);
-
int rrdhost_set_system_info_variable(struct rrdhost_system_info *system_info, char *name, char *value);
-#if defined(NETDATA_INTERNAL_CHECKS) && defined(NETDATA_VERIFY_LOCKS)
-void __rrdhost_check_wrlock(RRDHOST *host, const char *file, const char *function, const unsigned long line);
-void __rrdhost_check_rdlock(RRDHOST *host, const char *file, const char *function, const unsigned long line);
-void __rrdset_check_rdlock(RRDSET *st, const char *file, const char *function, const unsigned long line);
-void __rrdset_check_wrlock(RRDSET *st, const char *file, const char *function, const unsigned long line);
-void __rrd_check_rdlock(const char *file, const char *function, const unsigned long line);
-void __rrd_check_wrlock(const char *file, const char *function, const unsigned long line);
-
-#define rrdhost_check_rdlock(host) __rrdhost_check_rdlock(host, __FILE__, __FUNCTION__, __LINE__)
-#define rrdhost_check_wrlock(host) __rrdhost_check_wrlock(host, __FILE__, __FUNCTION__, __LINE__)
-#define rrdset_check_rdlock(st) __rrdset_check_rdlock(st, __FILE__, __FUNCTION__, __LINE__)
-#define rrdset_check_wrlock(st) __rrdset_check_wrlock(st, __FILE__, __FUNCTION__, __LINE__)
-#define rrd_check_rdlock() __rrd_check_rdlock(__FILE__, __FUNCTION__, __LINE__)
-#define rrd_check_wrlock() __rrd_check_wrlock(__FILE__, __FUNCTION__, __LINE__)
-
-#else
-#define rrdhost_check_rdlock(host) (void)0
-#define rrdhost_check_wrlock(host) (void)0
-#define rrdset_check_rdlock(st) (void)0
-#define rrdset_check_wrlock(st) (void)0
-#define rrd_check_rdlock() (void)0
-#define rrd_check_wrlock() (void)0
-#endif
-
// ----------------------------------------------------------------------------
// RRDSET functions
@@ -1239,15 +1206,15 @@ void rrdhost_save_all(void);
void rrdhost_cleanup_all(void);
void rrdhost_system_info_free(struct rrdhost_system_info *system_info);
-void rrdhost_free(RRDHOST *host, bool force);
+void rrdhost_free___while_having_rrd_wrlock(RRDHOST *host, bool force);
void rrdhost_save_charts(RRDHOST *host);
void rrdhost_delete_charts(RRDHOST *host);
-int rrdhost_should_be_removed(RRDHOST *host, RRDHOST *protected_host, time_t now);
+int rrdhost_should_be_removed(RRDHOST *host, RRDHOST *protected_host, time_t now_s);
void rrdset_update_heterogeneous_flag(RRDSET *st);
-time_t rrdset_set_update_every(RRDSET *st, time_t update_every);
+time_t rrdset_set_update_every_s(RRDSET *st, time_t update_every_s);
RRDSET *rrdset_find(RRDHOST *host, const char *id);
#define rrdset_find_localhost(id) rrdset_find(localhost, id)
@@ -1298,13 +1265,17 @@ void rrdset_isnot_obsolete(RRDSET *st);
#define rrdset_is_available_for_exporting_and_alarms(st) (!rrdset_flag_check(st, RRDSET_FLAG_OBSOLETE) && !rrdset_flag_check(st, RRDSET_FLAG_ARCHIVED) && rrdset_number_of_dimensions(st))
#define rrdset_is_archived(st) (rrdset_flag_check(st, RRDSET_FLAG_ARCHIVED) && rrdset_number_of_dimensions(st))
-time_t rrddim_first_entry_t(RRDDIM *rd);
-time_t rrddim_first_entry_t_of_tier(RRDDIM *rd, size_t tier);
-time_t rrddim_last_entry_t(RRDDIM *rd);
-time_t rrdset_last_entry_t(RRDSET *st);
-time_t rrdset_first_entry_t_of_tier(RRDSET *st, size_t tier);
-time_t rrdset_first_entry_t(RRDSET *st);
-time_t rrdhost_last_entry_t(RRDHOST *h);
+time_t rrddim_first_entry_s(RRDDIM *rd);
+time_t rrddim_first_entry_s_of_tier(RRDDIM *rd, size_t tier);
+time_t rrddim_last_entry_s(RRDDIM *rd);
+time_t rrddim_last_entry_s_of_tier(RRDDIM *rd, size_t tier);
+
+time_t rrdset_first_entry_s(RRDSET *st);
+time_t rrdset_first_entry_s_of_tier(RRDSET *st, size_t tier);
+time_t rrdset_last_entry_s(RRDSET *st);
+time_t rrdset_last_entry_s_of_tier(RRDSET *st, size_t tier);
+
+void rrdset_get_retention_of_tier_for_collected_chart(RRDSET *st, time_t *first_time_s, time_t *last_time_s, time_t now_s, size_t tier);
// ----------------------------------------------------------------------------
// RRD DIMENSION functions
@@ -1342,9 +1313,11 @@ collected_number rrddim_timed_set_by_pointer(RRDSET *st, RRDDIM *rd, struct time
collected_number rrddim_set_by_pointer(RRDSET *st, RRDDIM *rd, collected_number value);
collected_number rrddim_set(RRDSET *st, const char *id, collected_number value);
-#ifdef ENABLE_ACLK
-time_t calc_dimension_liveness(RRDDIM *rd, time_t now);
-#endif
+bool rrddim_finalize_collection_and_check_retention(RRDDIM *rd);
+void rrdset_finalize_collection(RRDSET *st, bool dimensions_too);
+void rrdhost_finalize_collection(RRDHOST *host);
+void rrd_finalize_collection_for_all_hosts(void);
+
long align_entries_to_pagesize(RRD_MEMORY_MODE mode, long entries);
#ifdef NETDATA_LOG_COLLECTION_ERRORS
@@ -1368,21 +1341,14 @@ void rrdset_free(RRDSET *st);
#ifdef NETDATA_RRD_INTERNALS
-char *rrdset_cache_dir(RRDHOST *host, const char *id);
+char *rrdhost_cache_dir_for_rrdset_alloc(RRDHOST *host, const char *id);
+const char *rrdset_cache_dir(RRDSET *st);
void rrddim_free(RRDSET *st, RRDDIM *rd);
void rrdset_reset(RRDSET *st);
void rrdset_delete_obsolete_dimensions(RRDSET *st);
-RRDHOST *rrdhost_create(
- const char *hostname, const char *registry_hostname, const char *guid, const char *os, const char *timezone,
- const char *abbrev_timezone, int32_t utc_offset,const char *tags, const char *program_name, const char *program_version,
- int update_every, long entries, RRD_MEMORY_MODE memory_mode, unsigned int health_enabled, unsigned int rrdpush_enabled,
- char *rrdpush_destination, char *rrdpush_api_key, char *rrdpush_send_charts_matching,
- bool rrdpush_enable_replication, time_t rrdpush_seconds_to_replicate, time_t rrdpush_replication_step,
- struct rrdhost_system_info *system_info, int is_localhost, bool is_archived);
-
#endif /* NETDATA_RRD_INTERNALS */
void set_host_properties(
@@ -1391,6 +1357,7 @@ void set_host_properties(
const char *program_name, const char *program_version);
size_t get_tier_grouping(size_t tier);
+void store_metric_collection_completed(void);
// ----------------------------------------------------------------------------
// RRD DB engine declarations
diff --git a/database/rrdcalc.c b/database/rrdcalc.c
index aad945a9..76263582 100644
--- a/database/rrdcalc.c
+++ b/database/rrdcalc.c
@@ -74,18 +74,16 @@ static STRING *rrdcalc_replace_variables_with_rrdset_labels(const char *line, RR
char var[RRDCALC_VAR_MAX];
char *m, *lbl_value = NULL;
- while ((m = strchr(temp + pos, '$'))) {
+ while ((m = strchr(temp + pos, '$')) && *(m+1) == '{') {
int i = 0;
char *e = m;
while (*e) {
+ var[i++] = *e;
- if (*e == ' ' || i == RRDCALC_VAR_MAX - 1)
+ if (*e == '}' || i == RRDCALC_VAR_MAX - 1)
break;
- else
- var[i] = *e;
e++;
- i++;
}
var[i] = '\0';
@@ -97,8 +95,12 @@ static STRING *rrdcalc_replace_variables_with_rrdset_labels(const char *line, RR
temp = buf;
}
else if (!strncmp(var, RRDCALC_VAR_LABEL, RRDCALC_VAR_LABEL_LEN)) {
+ char label_val[RRDCALC_VAR_MAX + 1] = { 0 };
+ strcpy(label_val, var+RRDCALC_VAR_LABEL_LEN);
+ label_val[i - RRDCALC_VAR_LABEL_LEN - 1] = '\0';
+
if(likely(rc->rrdset && rc->rrdset->rrdlabels)) {
- rrdlabels_get_value_to_char_or_null(rc->rrdset->rrdlabels, &lbl_value, var+RRDCALC_VAR_LABEL_LEN);
+ rrdlabels_get_value_to_char_or_null(rc->rrdset->rrdlabels, &lbl_value, label_val);
if (lbl_value) {
char *buf = find_and_replace(temp, var, lbl_value, m);
freez(temp);
@@ -179,7 +181,7 @@ static void rrdcalc_link_to_rrdset(RRDSET *st, RRDCALC *rc) {
rc->rrdset = st;
netdata_rwlock_wrlock(&st->alerts.rwlock);
- DOUBLE_LINKED_LIST_APPEND_UNSAFE(st->alerts.base, rc, prev, next);
+ DOUBLE_LINKED_LIST_APPEND_ITEM_UNSAFE(st->alerts.base, rc, prev, next);
netdata_rwlock_unlock(&st->alerts.rwlock);
if(rc->update_every < rc->rrdset->update_every) {
@@ -326,7 +328,7 @@ static void rrdcalc_unlink_from_rrdset(RRDCALC *rc, bool having_ll_wrlock) {
if(!having_ll_wrlock)
netdata_rwlock_wrlock(&st->alerts.rwlock);
- DOUBLE_LINKED_LIST_REMOVE_UNSAFE(st->alerts.base, rc, prev, next);
+ DOUBLE_LINKED_LIST_REMOVE_ITEM_UNSAFE(st->alerts.base, rc, prev, next);
if(!having_ll_wrlock)
netdata_rwlock_unlock(&st->alerts.rwlock);
@@ -625,7 +627,8 @@ static void rrdcalc_rrdhost_delete_callback(const DICTIONARY_ITEM *item __maybe_
void rrdcalc_rrdhost_index_init(RRDHOST *host) {
if(!host->rrdcalc_root_index) {
- host->rrdcalc_root_index = dictionary_create(DICT_OPTION_DONT_OVERWRITE_VALUE);
+ host->rrdcalc_root_index = dictionary_create_advanced(DICT_OPTION_DONT_OVERWRITE_VALUE | DICT_OPTION_FIXED_SIZE,
+ &dictionary_stats_category_rrdhealth, sizeof(RRDCALC));
dictionary_register_insert_callback(host->rrdcalc_root_index, rrdcalc_rrdhost_insert_callback, NULL);
dictionary_register_conflict_callback(host->rrdcalc_root_index, rrdcalc_rrdhost_conflict_callback, NULL);
@@ -737,7 +740,7 @@ void rrdcalc_delete_alerts_not_matching_host_labels_from_this_host(RRDHOST *host
continue;
if(!rrdlabels_match_simple_pattern_parsed(host->rrdlabels, rc->host_labels_pattern, '=')) {
- info("Health configuration for alarm '%s' cannot be applied, because the host %s does not have the label(s) '%s'",
+ log_health("Health configuration for alarm '%s' cannot be applied, because the host %s does not have the label(s) '%s'",
rrdcalc_name(rc),
rrdhost_hostname(host),
rrdcalc_host_labels(rc));
@@ -753,7 +756,7 @@ void rrdcalc_delete_alerts_not_matching_host_labels_from_all_hosts() {
RRDHOST *host;
rrdhost_foreach_read(host) {
- if (unlikely(!host->health_enabled))
+ if (unlikely(!host->health.health_enabled))
continue;
if (host->rrdlabels)
diff --git a/database/rrdcalc.h b/database/rrdcalc.h
index a25c05cc..08d8beee 100644
--- a/database/rrdcalc.h
+++ b/database/rrdcalc.h
@@ -251,8 +251,8 @@ void rrdcalc_rrdhost_index_init(RRDHOST *host);
void rrdcalc_rrdhost_index_destroy(RRDHOST *host);
#define RRDCALC_VAR_MAX 100
-#define RRDCALC_VAR_FAMILY "$family"
-#define RRDCALC_VAR_LABEL "$label:"
+#define RRDCALC_VAR_FAMILY "${family}"
+#define RRDCALC_VAR_LABEL "${label:"
#define RRDCALC_VAR_LABEL_LEN (sizeof(RRDCALC_VAR_LABEL)-1)
#endif //NETDATA_RRDCALC_H
diff --git a/database/rrdcalctemplate.c b/database/rrdcalctemplate.c
index 87e085c9..4d7352b2 100644
--- a/database/rrdcalctemplate.c
+++ b/database/rrdcalctemplate.c
@@ -189,7 +189,8 @@ static void rrdcalctemplate_delete_callback(const DICTIONARY_ITEM *item __maybe_
void rrdcalctemplate_index_init(RRDHOST *host) {
if(!host->rrdcalctemplate_root_index) {
- host->rrdcalctemplate_root_index = dictionary_create(DICT_OPTION_DONT_OVERWRITE_VALUE);
+ host->rrdcalctemplate_root_index = dictionary_create_advanced(DICT_OPTION_DONT_OVERWRITE_VALUE | DICT_OPTION_FIXED_SIZE,
+ &dictionary_stats_category_rrdhealth, sizeof(RRDCALCTEMPLATE));
dictionary_register_insert_callback(host->rrdcalctemplate_root_index, rrdcalctemplate_insert_callback, NULL);
dictionary_register_delete_callback(host->rrdcalctemplate_root_index, rrdcalctemplate_delete_callback, host);
diff --git a/database/rrdcontext.c b/database/rrdcontext.c
index 3413d1ea..9fc605f3 100644
--- a/database/rrdcontext.c
+++ b/database/rrdcontext.c
@@ -221,8 +221,8 @@ typedef struct rrdmetric {
RRDDIM *rrddim;
- time_t first_time_t;
- time_t last_time_t;
+ time_t first_time_s;
+ time_t last_time_s;
RRD_FLAGS flags;
struct rrdinstance *ri;
@@ -240,10 +240,10 @@ typedef struct rrdinstance {
RRDSET_TYPE chart_type;
RRD_FLAGS flags; // flags related to this instance
- time_t first_time_t;
- time_t last_time_t;
+ time_t first_time_s;
+ time_t last_time_s;
- int update_every; // data collection frequency
+ time_t update_every_s; // data collection frequency
RRDSET *rrdset; // pointer to RRDSET when collected, or NULL
DICTIONARY *rrdlabels; // linked to RRDSET->chart_labels or own version
@@ -269,8 +269,8 @@ typedef struct rrdcontext {
RRDSET_TYPE chart_type;
RRD_FLAGS flags;
- time_t first_time_t;
- time_t last_time_t;
+ time_t first_time_s;
+ time_t last_time_s;
VERSIONED_CONTEXT_DATA hub;
@@ -522,20 +522,20 @@ static bool rrdmetric_conflict_callback(const DICTIONARY_ITEM *item __maybe_unus
uuid_unparse(rm->uuid, uuid1);
uuid_unparse(rm_new->uuid, uuid2);
- time_t old_first_time_t = 0;
- time_t old_last_time_t = 0;
+ time_t old_first_time_s = 0;
+ time_t old_last_time_s = 0;
if(rrdmetric_update_retention(rm)) {
- old_first_time_t = rm->first_time_t;
- old_last_time_t = rm->last_time_t;
+ old_first_time_s = rm->first_time_s;
+ old_last_time_s = rm->last_time_s;
}
uuid_copy(rm->uuid, rm_new->uuid);
- time_t new_first_time_t = 0;
- time_t new_last_time_t = 0;
+ time_t new_first_time_s = 0;
+ time_t new_last_time_s = 0;
if(rrdmetric_update_retention(rm)) {
- new_first_time_t = rm->first_time_t;
- new_last_time_t = rm->last_time_t;
+ new_first_time_s = rm->first_time_s;
+ new_last_time_s = rm->last_time_s;
}
internal_error(true,
@@ -543,8 +543,8 @@ static bool rrdmetric_conflict_callback(const DICTIONARY_ITEM *item __maybe_unus
, string2str(rm->id)
, string2str(rm->ri->id)
, rrdhost_hostname(rm->ri->rc->rrdhost)
- , uuid1, old_first_time_t, old_last_time_t, old_last_time_t - old_first_time_t
- , uuid2, new_first_time_t, new_last_time_t, new_last_time_t - new_first_time_t
+ , uuid1, old_first_time_s, old_last_time_s, old_last_time_s - old_first_time_s
+ , uuid2, new_first_time_s, new_last_time_s, new_last_time_s - new_first_time_s
);
#else
uuid_copy(rm->uuid, rm_new->uuid);
@@ -576,13 +576,13 @@ static bool rrdmetric_conflict_callback(const DICTIONARY_ITEM *item __maybe_unus
rrd_flag_set_updated(rm, RRD_FLAG_UPDATE_REASON_CHANGED_METADATA);
}
- if(!rm->first_time_t || (rm_new->first_time_t && rm_new->first_time_t < rm->first_time_t)) {
- rm->first_time_t = rm_new->first_time_t;
+ if(!rm->first_time_s || (rm_new->first_time_s && rm_new->first_time_s < rm->first_time_s)) {
+ rm->first_time_s = rm_new->first_time_s;
rrd_flag_set_updated(rm, RRD_FLAG_UPDATE_REASON_CHANGED_FIRST_TIME_T);
}
- if(!rm->last_time_t || (rm_new->last_time_t && rm_new->last_time_t > rm->last_time_t)) {
- rm->last_time_t = rm_new->last_time_t;
+ if(!rm->last_time_s || (rm_new->last_time_s && rm_new->last_time_s > rm->last_time_s)) {
+ rm->last_time_s = rm_new->last_time_s;
rrd_flag_set_updated(rm, RRD_FLAG_UPDATE_REASON_CHANGED_LAST_TIME_T);
}
@@ -611,7 +611,9 @@ static void rrdmetrics_create_in_rrdinstance(RRDINSTANCE *ri) {
if(unlikely(!ri)) return;
if(likely(ri->rrdmetrics)) return;
- ri->rrdmetrics = dictionary_create(DICT_OPTION_DONT_OVERWRITE_VALUE);
+ ri->rrdmetrics = dictionary_create_advanced(DICT_OPTION_DONT_OVERWRITE_VALUE | DICT_OPTION_FIXED_SIZE,
+ &dictionary_stats_category_rrdcontext, sizeof(RRDMETRIC));
+
dictionary_register_insert_callback(ri->rrdmetrics, rrdmetric_insert_callback, ri);
dictionary_register_delete_callback(ri->rrdmetrics, rrdmetric_delete_callback, ri);
dictionary_register_conflict_callback(ri->rrdmetrics, rrdmetric_conflict_callback, ri);
@@ -750,11 +752,6 @@ static void rrdinstance_free(RRDINSTANCE *ri) {
}
static void rrdinstance_insert_callback(const DICTIONARY_ITEM *item __maybe_unused, void *value, void *rrdcontext) {
- static STRING *ml_anomaly_rates_id = NULL;
-
- if(unlikely(!ml_anomaly_rates_id))
- ml_anomaly_rates_id = string_strdupz(ML_ANOMALY_RATES_CHART_ID);
-
RRDINSTANCE *ri = value;
// link it to its parent
@@ -781,10 +778,6 @@ static void rrdinstance_insert_callback(const DICTIONARY_ITEM *item __maybe_unus
ri->flags &= ~RRD_FLAG_HIDDEN; // no need of atomics at the constructor
}
- // we need this when loading from SQL
- if(unlikely(ri->id == ml_anomaly_rates_id))
- ri->flags |= RRD_FLAG_HIDDEN; // no need of atomics at the constructor
-
rrdmetrics_create_in_rrdinstance(ri);
// signal the react callback to do the job
@@ -872,8 +865,8 @@ static bool rrdinstance_conflict_callback(const DICTIONARY_ITEM *item __maybe_un
rrd_flag_set_updated(ri, RRD_FLAG_UPDATE_REASON_CHANGED_METADATA);
}
- if(ri->update_every != ri_new->update_every) {
- ri->update_every = ri_new->update_every;
+ if(ri->update_every_s != ri_new->update_every_s) {
+ ri->update_every_s = ri_new->update_every_s;
rrd_flag_set_updated(ri, RRD_FLAG_UPDATE_REASON_CHANGED_METADATA);
}
@@ -923,7 +916,9 @@ static void rrdinstance_react_callback(const DICTIONARY_ITEM *item __maybe_unuse
void rrdinstances_create_in_rrdcontext(RRDCONTEXT *rc) {
if(unlikely(!rc || rc->rrdinstances)) return;
- rc->rrdinstances = dictionary_create(DICT_OPTION_DONT_OVERWRITE_VALUE);
+ rc->rrdinstances = dictionary_create_advanced(DICT_OPTION_DONT_OVERWRITE_VALUE | DICT_OPTION_FIXED_SIZE,
+ &dictionary_stats_category_rrdcontext, sizeof(RRDINSTANCE));
+
dictionary_register_insert_callback(rc->rrdinstances, rrdinstance_insert_callback, rc);
dictionary_register_delete_callback(rc->rrdinstances, rrdinstance_delete_callback, rc);
dictionary_register_conflict_callback(rc->rrdinstances, rrdinstance_conflict_callback, rc);
@@ -945,8 +940,8 @@ static void rrdinstance_trigger_updates(RRDINSTANCE *ri, const char *function) {
ri->priority = st->priority;
rrd_flag_set_updated(ri, RRD_FLAG_UPDATE_REASON_CHANGED_METADATA);
}
- if(unlikely(st->update_every != ri->update_every)) {
- ri->update_every = st->update_every;
+ if(unlikely(st->update_every != ri->update_every_s)) {
+ ri->update_every_s = st->update_every;
rrd_flag_set_updated(ri, RRD_FLAG_UPDATE_REASON_CHANGED_METADATA);
}
}
@@ -989,7 +984,7 @@ static inline void rrdinstance_from_rrdset(RRDSET *st) {
.title = string_dup(st->title),
.chart_type = st->chart_type,
.priority = st->priority,
- .update_every = st->update_every,
+ .update_every_s = st->update_every,
.flags = RRD_FLAG_NONE, // no need for atomics
.rrdset = st,
};
@@ -1027,8 +1022,8 @@ static inline void rrdinstance_from_rrdset(RRDSET *st) {
RRDMETRIC *rm_old = rrdmetric_acquired_value(rd->rrdmetric);
rrd_flags_replace(rm_old, RRD_FLAG_DELETED|RRD_FLAG_UPDATED|RRD_FLAG_LIVE_RETENTION|RRD_FLAG_UPDATE_REASON_UNUSED|RRD_FLAG_UPDATE_REASON_ZERO_RETENTION);
rm_old->rrddim = NULL;
- rm_old->first_time_t = 0;
- rm_old->last_time_t = 0;
+ rm_old->first_time_s = 0;
+ rm_old->last_time_s = 0;
rrdmetric_release(rd->rrdmetric);
rd->rrdmetric = NULL;
@@ -1043,8 +1038,8 @@ static inline void rrdinstance_from_rrdset(RRDSET *st) {
rrd_flags_replace(ri_old, RRD_FLAG_OWN_LABELS|RRD_FLAG_DELETED|RRD_FLAG_UPDATED|RRD_FLAG_LIVE_RETENTION|RRD_FLAG_UPDATE_REASON_UNUSED|RRD_FLAG_UPDATE_REASON_ZERO_RETENTION);
ri_old->rrdset = NULL;
- ri_old->first_time_t = 0;
- ri_old->last_time_t = 0;
+ ri_old->first_time_s = 0;
+ ri_old->last_time_s = 0;
rrdinstance_trigger_updates(ri_old, __FUNCTION__ );
rrdinstance_release(ria_old);
@@ -1054,8 +1049,8 @@ static inline void rrdinstance_from_rrdset(RRDSET *st) {
if(!dictionary_entries(rc_old->rrdinstances) && !dictionary_stats_referenced_items(rc_old->rrdinstances)) {
rrdcontext_lock(rc_old);
rc_old->flags = ((rc_old->flags & RRD_FLAG_QUEUED)?RRD_FLAG_QUEUED:RRD_FLAG_NONE)|RRD_FLAG_DELETED|RRD_FLAG_UPDATED|RRD_FLAG_LIVE_RETENTION|RRD_FLAG_UPDATE_REASON_UNUSED|RRD_FLAG_UPDATE_REASON_ZERO_RETENTION;
- rc_old->first_time_t = 0;
- rc_old->last_time_t = 0;
+ rc_old->first_time_s = 0;
+ rc_old->last_time_s = 0;
rrdcontext_unlock(rc_old);
rrdcontext_trigger_updates(rc_old, __FUNCTION__ );
}
@@ -1233,13 +1228,13 @@ static void rrdcontext_insert_callback(const DICTIONARY_ITEM *item __maybe_unuse
rc->version = rc->hub.version;
rc->priority = rc->hub.priority;
- rc->first_time_t = (time_t)rc->hub.first_time_t;
- rc->last_time_t = (time_t)rc->hub.last_time_t;
+ rc->first_time_s = (time_t)rc->hub.first_time_s;
+ rc->last_time_s = (time_t)rc->hub.last_time_s;
- if(rc->hub.deleted || !rc->hub.first_time_t)
+ if(rc->hub.deleted || !rc->hub.first_time_s)
rrd_flag_set_deleted(rc, RRD_FLAG_NONE);
else {
- if (rc->last_time_t == 0)
+ if (rc->last_time_s == 0)
rrd_flag_set_collected(rc);
else
rrd_flag_set_archived(rc);
@@ -1401,18 +1396,20 @@ void rrdhost_create_rrdcontexts(RRDHOST *host) {
if(unlikely(!host)) return;
if(likely(host->rrdctx)) return;
- host->rrdctx = (RRDCONTEXTS *)dictionary_create(DICT_OPTION_DONT_OVERWRITE_VALUE);
+ host->rrdctx = (RRDCONTEXTS *)dictionary_create_advanced(DICT_OPTION_DONT_OVERWRITE_VALUE | DICT_OPTION_FIXED_SIZE,
+ &dictionary_stats_category_rrdcontext, sizeof(RRDCONTEXT));
+
dictionary_register_insert_callback((DICTIONARY *)host->rrdctx, rrdcontext_insert_callback, host);
dictionary_register_delete_callback((DICTIONARY *)host->rrdctx, rrdcontext_delete_callback, host);
dictionary_register_conflict_callback((DICTIONARY *)host->rrdctx, rrdcontext_conflict_callback, host);
dictionary_register_react_callback((DICTIONARY *)host->rrdctx, rrdcontext_react_callback, host);
- host->rrdctx_hub_queue = (RRDCONTEXTS *)dictionary_create(DICT_OPTION_DONT_OVERWRITE_VALUE | DICT_OPTION_VALUE_LINK_DONT_CLONE);
+ host->rrdctx_hub_queue = (RRDCONTEXTS *)dictionary_create_advanced(DICT_OPTION_DONT_OVERWRITE_VALUE | DICT_OPTION_VALUE_LINK_DONT_CLONE, &dictionary_stats_category_rrdcontext, 0);
dictionary_register_insert_callback((DICTIONARY *)host->rrdctx_hub_queue, rrdcontext_hub_queue_insert_callback, NULL);
dictionary_register_delete_callback((DICTIONARY *)host->rrdctx_hub_queue, rrdcontext_hub_queue_delete_callback, NULL);
dictionary_register_conflict_callback((DICTIONARY *)host->rrdctx_hub_queue, rrdcontext_hub_queue_conflict_callback, NULL);
- host->rrdctx_post_processing_queue = (RRDCONTEXTS *)dictionary_create(DICT_OPTION_DONT_OVERWRITE_VALUE | DICT_OPTION_VALUE_LINK_DONT_CLONE);
+ host->rrdctx_post_processing_queue = (RRDCONTEXTS *)dictionary_create_advanced(DICT_OPTION_DONT_OVERWRITE_VALUE | DICT_OPTION_VALUE_LINK_DONT_CLONE, &dictionary_stats_category_rrdcontext, 0);
dictionary_register_insert_callback((DICTIONARY *)host->rrdctx_post_processing_queue, rrdcontext_post_processing_queue_insert_callback, NULL);
dictionary_register_delete_callback((DICTIONARY *)host->rrdctx_post_processing_queue, rrdcontext_post_processing_queue_delete_callback, NULL);
dictionary_register_conflict_callback((DICTIONARY *)host->rrdctx_post_processing_queue, rrdcontext_post_processing_queue_conflict_callback, NULL);
@@ -1743,8 +1740,8 @@ struct rrdcontext_to_json {
SIMPLE_PATTERN *chart_dimensions;
size_t written;
time_t now;
- time_t combined_first_time_t;
- time_t combined_last_time_t;
+ time_t combined_first_time_s;
+ time_t combined_last_time_s;
RRD_FLAGS combined_flags;
};
@@ -1760,10 +1757,10 @@ static inline int rrdmetric_to_json_callback(const DICTIONARY_ITEM *item, void *
if(unlikely(rrd_flag_is_deleted(rm) && !(options & RRDCONTEXT_OPTION_SHOW_DELETED)))
return 0;
- if(after && (!rm->last_time_t || after > rm->last_time_t))
+ if(after && (!rm->last_time_s || after > rm->last_time_s))
return 0;
- if(before && (!rm->first_time_t || before < rm->first_time_t))
+ if(before && (!rm->first_time_s || before < rm->first_time_s))
return 0;
if(t->chart_dimensions
@@ -1773,14 +1770,14 @@ static inline int rrdmetric_to_json_callback(const DICTIONARY_ITEM *item, void *
if(t->written) {
buffer_strcat(wb, ",\n");
- t->combined_first_time_t = MIN(t->combined_first_time_t, rm->first_time_t);
- t->combined_last_time_t = MAX(t->combined_last_time_t, rm->last_time_t);
+ t->combined_first_time_s = MIN(t->combined_first_time_s, rm->first_time_s);
+ t->combined_last_time_s = MAX(t->combined_last_time_s, rm->last_time_s);
t->combined_flags |= rrd_flags_get(rm);
}
else {
buffer_strcat(wb, "\n");
- t->combined_first_time_t = rm->first_time_t;
- t->combined_last_time_t = rm->last_time_t;
+ t->combined_first_time_s = rm->first_time_s;
+ t->combined_last_time_s = rm->last_time_s;
t->combined_flags = rrd_flags_get(rm);
}
@@ -1798,8 +1795,8 @@ static inline int rrdmetric_to_json_callback(const DICTIONARY_ITEM *item, void *
",\n\t\t\t\t\t\t\t\"last_time_t\":%lld"
",\n\t\t\t\t\t\t\t\"collected\":%s"
, string2str(rm->name)
- , (long long)rm->first_time_t
- , rrd_flag_is_collected(rm) ? (long long)t->now : (long long)rm->last_time_t
+ , (long long)rm->first_time_s
+ , rrd_flag_is_collected(rm) ? (long long)t->now : (long long)rm->last_time_s
, rrd_flag_is_collected(rm) ? "true" : "false"
);
@@ -1835,10 +1832,10 @@ static inline int rrdinstance_to_json_callback(const DICTIONARY_ITEM *item, void
if(unlikely(rrd_flag_is_deleted(ri) && !(options & RRDCONTEXT_OPTION_SHOW_DELETED)))
return 0;
- if(after && (!ri->last_time_t || after > ri->last_time_t))
+ if(after && (!ri->last_time_s || after > ri->last_time_s))
return 0;
- if(before && (!ri->first_time_t || before < ri->first_time_t))
+ if(before && (!ri->first_time_s || before < ri->first_time_s))
return 0;
if(t_parent->chart_label_key && !rrdlabels_match_simple_pattern_parsed(ri->rrdlabels, t_parent->chart_label_key, '\0'))
@@ -1847,14 +1844,14 @@ static inline int rrdinstance_to_json_callback(const DICTIONARY_ITEM *item, void
if(t_parent->chart_labels_filter && !rrdlabels_match_simple_pattern_parsed(ri->rrdlabels, t_parent->chart_labels_filter, ':'))
return 0;
- time_t first_time_t = ri->first_time_t;
- time_t last_time_t = ri->last_time_t;
+ time_t first_time_s = ri->first_time_s;
+ time_t last_time_s = ri->last_time_s;
RRD_FLAGS flags = rrd_flags_get(ri);
BUFFER *wb_metrics = NULL;
if(options & RRDCONTEXT_OPTION_SHOW_METRICS || t_parent->chart_dimensions) {
- wb_metrics = buffer_create(4096);
+ wb_metrics = buffer_create(4096, &netdata_buffers_statistics.buffers_api);
struct rrdcontext_to_json t_metrics = {
.wb = wb_metrics,
@@ -1874,21 +1871,21 @@ static inline int rrdinstance_to_json_callback(const DICTIONARY_ITEM *item, void
return 0;
}
- first_time_t = t_metrics.combined_first_time_t;
- last_time_t = t_metrics.combined_last_time_t;
+ first_time_s = t_metrics.combined_first_time_s;
+ last_time_s = t_metrics.combined_last_time_s;
flags = t_metrics.combined_flags;
}
if(t_parent->written) {
buffer_strcat(wb, ",\n");
- t_parent->combined_first_time_t = MIN(t_parent->combined_first_time_t, first_time_t);
- t_parent->combined_last_time_t = MAX(t_parent->combined_last_time_t, last_time_t);
+ t_parent->combined_first_time_s = MIN(t_parent->combined_first_time_s, first_time_s);
+ t_parent->combined_last_time_s = MAX(t_parent->combined_last_time_s, last_time_s);
t_parent->combined_flags |= flags;
}
else {
buffer_strcat(wb, "\n");
- t_parent->combined_first_time_t = first_time_t;
- t_parent->combined_last_time_t = last_time_t;
+ t_parent->combined_first_time_s = first_time_s;
+ t_parent->combined_last_time_s = last_time_s;
t_parent->combined_flags = flags;
}
@@ -1908,7 +1905,7 @@ static inline int rrdinstance_to_json_callback(const DICTIONARY_ITEM *item, void
",\n\t\t\t\t\t\"family\":\"%s\""
",\n\t\t\t\t\t\"chart_type\":\"%s\""
",\n\t\t\t\t\t\"priority\":%u"
- ",\n\t\t\t\t\t\"update_every\":%d"
+ ",\n\t\t\t\t\t\"update_every\":%ld"
",\n\t\t\t\t\t\"first_time_t\":%lld"
",\n\t\t\t\t\t\"last_time_t\":%lld"
",\n\t\t\t\t\t\"collected\":%s"
@@ -1919,9 +1916,9 @@ static inline int rrdinstance_to_json_callback(const DICTIONARY_ITEM *item, void
, string2str(ri->family)
, rrdset_type_name(ri->chart_type)
, ri->priority
- , ri->update_every
- , (long long)first_time_t
- , (flags & RRD_FLAG_COLLECTED) ? (long long)t_parent->now : (long long)last_time_t
+ , ri->update_every_s
+ , (long long)first_time_s
+ , (flags & RRD_FLAG_COLLECTED) ? (long long)t_parent->now : (long long)last_time_s
, (flags & RRD_FLAG_COLLECTED) ? "true" : "false"
);
@@ -1976,14 +1973,14 @@ static inline int rrdcontext_to_json_callback(const DICTIONARY_ITEM *item, void
if(options & RRDCONTEXT_OPTION_DEEPSCAN)
rrdcontext_recalculate_context_retention(rc, RRD_FLAG_NONE, false);
- if(after && (!rc->last_time_t || after > rc->last_time_t))
+ if(after && (!rc->last_time_s || after > rc->last_time_s))
return 0;
- if(before && (!rc->first_time_t || before < rc->first_time_t))
+ if(before && (!rc->first_time_s || before < rc->first_time_s))
return 0;
- time_t first_time_t = rc->first_time_t;
- time_t last_time_t = rc->last_time_t;
+ time_t first_time_s = rc->first_time_s;
+ time_t last_time_s = rc->last_time_s;
RRD_FLAGS flags = rrd_flags_get(rc);
BUFFER *wb_instances = NULL;
@@ -1992,7 +1989,7 @@ static inline int rrdcontext_to_json_callback(const DICTIONARY_ITEM *item, void
|| t_parent->chart_labels_filter
|| t_parent->chart_dimensions) {
- wb_instances = buffer_create(4096);
+ wb_instances = buffer_create(4096, &netdata_buffers_statistics.buffers_api);
struct rrdcontext_to_json t_instances = {
.wb = wb_instances,
@@ -2012,8 +2009,8 @@ static inline int rrdcontext_to_json_callback(const DICTIONARY_ITEM *item, void
return 0;
}
- first_time_t = t_instances.combined_first_time_t;
- last_time_t = t_instances.combined_last_time_t;
+ first_time_s = t_instances.combined_first_time_s;
+ last_time_s = t_instances.combined_last_time_s;
flags = t_instances.combined_flags;
}
@@ -2043,8 +2040,8 @@ static inline int rrdcontext_to_json_callback(const DICTIONARY_ITEM *item, void
, string2str(rc->family)
, rrdset_type_name(rc->chart_type)
, rc->priority
- , (long long)first_time_t
- , (flags & RRD_FLAG_COLLECTED) ? (long long)t_parent->now : (long long)last_time_t
+ , (long long)first_time_s
+ , (flags & RRD_FLAG_COLLECTED) ? (long long)t_parent->now : (long long)last_time_s
, (flags & RRD_FLAG_COLLECTED) ? "true" : "false"
);
@@ -2220,7 +2217,7 @@ DICTIONARY *rrdcontext_all_metrics_to_dict(RRDHOST *host, SIMPLE_PATTERN *contex
if(!host || !host->rrdctx)
return NULL;
- DICTIONARY *dict = dictionary_create(DICT_OPTION_SINGLE_THREADED|DICT_OPTION_DONT_OVERWRITE_VALUE);
+ DICTIONARY *dict = dictionary_create_advanced(DICT_OPTION_SINGLE_THREADED|DICT_OPTION_DONT_OVERWRITE_VALUE, &dictionary_stats_category_rrdcontext, 0);
dictionary_register_insert_callback(dict, metric_entry_insert_callback, NULL);
dictionary_register_delete_callback(dict, metric_entry_delete_callback, NULL);
dictionary_register_conflict_callback(dict, metric_entry_conflict_callback, NULL);
@@ -2328,11 +2325,25 @@ void query_target_release(QUERY_TARGET *qt) {
string_freez(qt->query.array[i].chart.name);
qt->query.array[i].chart.name = NULL;
+ // reset the plans
+ for(size_t p = 0; p < qt->query.array[i].plan.used; p++) {
+ internal_fatal(qt->query.array[i].plan.array[p].initialized &&
+ !qt->query.array[i].plan.array[p].finalized,
+ "QUERY: left-over initialized plan");
+
+ qt->query.array[i].plan.array[p].initialized = false;
+ qt->query.array[i].plan.array[p].finalized = false;
+ }
+ qt->query.array[i].plan.used = 0;
+
+ // reset the tiers
for(size_t tier = 0; tier < storage_tiers ;tier++) {
if(qt->query.array[i].tiers[tier].db_metric_handle) {
STORAGE_ENGINE *eng = qt->query.array[i].tiers[tier].eng;
eng->api.metric_release(qt->query.array[i].tiers[tier].db_metric_handle);
qt->query.array[i].tiers[tier].db_metric_handle = NULL;
+ qt->query.array[i].tiers[tier].weight = 0;
+ qt->query.array[i].tiers[tier].eng = NULL;
}
}
}
@@ -2366,37 +2377,44 @@ void query_target_release(QUERY_TARGET *qt) {
qt->contexts.used = 0;
qt->hosts.used = 0;
- qt->db.minimum_latest_update_every = 0;
- qt->db.first_time_t = 0;
- qt->db.last_time_t = 0;
+ qt->db.minimum_latest_update_every_s = 0;
+ qt->db.first_time_s = 0;
+ qt->db.last_time_s = 0;
qt->id[0] = '\0';
qt->used = false;
}
void query_target_free(void) {
- if(thread_query_target.used)
- query_target_release(&thread_query_target);
+ QUERY_TARGET *qt = &thread_query_target;
- freez(thread_query_target.query.array);
- thread_query_target.query.array = NULL;
- thread_query_target.query.size = 0;
+ if(qt->used)
+ query_target_release(qt);
- freez(thread_query_target.metrics.array);
- thread_query_target.metrics.array = NULL;
- thread_query_target.metrics.size = 0;
+ __atomic_sub_fetch(&netdata_buffers_statistics.query_targets_size, qt->query.size * sizeof(QUERY_METRIC), __ATOMIC_RELAXED);
+ freez(qt->query.array);
+ qt->query.array = NULL;
+ qt->query.size = 0;
- freez(thread_query_target.instances.array);
- thread_query_target.instances.array = NULL;
- thread_query_target.instances.size = 0;
+ __atomic_sub_fetch(&netdata_buffers_statistics.query_targets_size, qt->metrics.size * sizeof(RRDMETRIC_ACQUIRED *), __ATOMIC_RELAXED);
+ freez(qt->metrics.array);
+ qt->metrics.array = NULL;
+ qt->metrics.size = 0;
- freez(thread_query_target.contexts.array);
- thread_query_target.contexts.array = NULL;
- thread_query_target.contexts.size = 0;
+ __atomic_sub_fetch(&netdata_buffers_statistics.query_targets_size, qt->instances.size * sizeof(RRDINSTANCE_ACQUIRED *), __ATOMIC_RELAXED);
+ freez(qt->instances.array);
+ qt->instances.array = NULL;
+ qt->instances.size = 0;
- freez(thread_query_target.hosts.array);
- thread_query_target.hosts.array = NULL;
- thread_query_target.hosts.size = 0;
+ __atomic_sub_fetch(&netdata_buffers_statistics.query_targets_size, qt->contexts.size * sizeof(RRDCONTEXT_ACQUIRED *), __ATOMIC_RELAXED);
+ freez(qt->contexts.array);
+ qt->contexts.array = NULL;
+ qt->contexts.size = 0;
+
+ __atomic_sub_fetch(&netdata_buffers_statistics.query_targets_size, qt->hosts.size * sizeof(RRDHOST *), __ATOMIC_RELAXED);
+ freez(qt->hosts.array);
+ qt->hosts.array = NULL;
+ qt->hosts.size = 0;
}
static void query_target_add_metric(QUERY_TARGET_LOCALS *qtl, RRDMETRIC_ACQUIRED *rma, RRDINSTANCE *ri,
@@ -2408,69 +2426,73 @@ static void query_target_add_metric(QUERY_TARGET_LOCALS *qtl, RRDMETRIC_ACQUIRED
return;
if(qt->metrics.used == qt->metrics.size) {
+ size_t old_mem = qt->metrics.size * sizeof(RRDMETRIC_ACQUIRED *);
qt->metrics.size = (qt->metrics.size) ? qt->metrics.size * 2 : 1;
- qt->metrics.array = reallocz(qt->metrics.array, qt->metrics.size * sizeof(RRDMETRIC_ACQUIRED *));
+ size_t new_mem = qt->metrics.size * sizeof(RRDMETRIC_ACQUIRED *);
+ qt->metrics.array = reallocz(qt->metrics.array, new_mem);
+
+ __atomic_add_fetch(&netdata_buffers_statistics.query_targets_size, new_mem - old_mem, __ATOMIC_RELAXED);
}
qt->metrics.array[qt->metrics.used++] = rrdmetric_acquired_dup(rma);
if(!queryable_instance)
return;
- time_t common_first_time_t = 0;
- time_t common_last_time_t = 0;
- time_t common_update_every = 0;
+ time_t common_first_time_s = 0;
+ time_t common_last_time_s = 0;
+ time_t common_update_every_s = 0;
size_t tiers_added = 0;
struct {
STORAGE_ENGINE *eng;
STORAGE_METRIC_HANDLE *db_metric_handle;
- time_t db_first_time_t;
- time_t db_last_time_t;
- time_t db_update_every;
+ time_t db_first_time_s;
+ time_t db_last_time_s;
+ time_t db_update_every_s;
} tier_retention[storage_tiers];
for (size_t tier = 0; tier < storage_tiers; tier++) {
STORAGE_ENGINE *eng = qtl->host->db[tier].eng;
tier_retention[tier].eng = eng;
- tier_retention[tier].db_update_every = (time_t) (qtl->host->db[tier].tier_grouping * ri->update_every);
+ tier_retention[tier].db_update_every_s = (time_t) (qtl->host->db[tier].tier_grouping * ri->update_every_s);
- if(rm->rrddim && rm->rrddim->tiers[tier] && rm->rrddim->tiers[tier]->db_metric_handle)
- tier_retention[tier].db_metric_handle = eng->api.metric_dup(rm->rrddim->tiers[tier]->db_metric_handle);
+ if(rm->rrddim && rm->rrddim->tiers[tier].db_metric_handle)
+ tier_retention[tier].db_metric_handle = eng->api.metric_dup(rm->rrddim->tiers[tier].db_metric_handle);
else
tier_retention[tier].db_metric_handle = eng->api.metric_get(qtl->host->db[tier].instance, &rm->uuid);
if(tier_retention[tier].db_metric_handle) {
- tier_retention[tier].db_first_time_t = tier_retention[tier].eng->api.query_ops.oldest_time(tier_retention[tier].db_metric_handle);
- tier_retention[tier].db_last_time_t = tier_retention[tier].eng->api.query_ops.latest_time(tier_retention[tier].db_metric_handle);
+ tier_retention[tier].db_first_time_s = tier_retention[tier].eng->api.query_ops.oldest_time_s(tier_retention[tier].db_metric_handle);
+ tier_retention[tier].db_last_time_s = tier_retention[tier].eng->api.query_ops.latest_time_s(tier_retention[tier].db_metric_handle);
- if(!common_first_time_t)
- common_first_time_t = tier_retention[tier].db_first_time_t;
- else if(tier_retention[tier].db_first_time_t)
- common_first_time_t = MIN(common_first_time_t, tier_retention[tier].db_first_time_t);
+ if(!common_first_time_s)
+ common_first_time_s = tier_retention[tier].db_first_time_s;
+ else if(tier_retention[tier].db_first_time_s)
+ common_first_time_s = MIN(common_first_time_s, tier_retention[tier].db_first_time_s);
- if(!common_last_time_t)
- common_last_time_t = tier_retention[tier].db_last_time_t;
+ if(!common_last_time_s)
+ common_last_time_s = tier_retention[tier].db_last_time_s;
else
- common_last_time_t = MAX(common_last_time_t, tier_retention[tier].db_last_time_t);
+ common_last_time_s = MAX(common_last_time_s, tier_retention[tier].db_last_time_s);
- if(!common_update_every)
- common_update_every = tier_retention[tier].db_update_every;
- else if(tier_retention[tier].db_update_every)
- common_update_every = MIN(common_update_every, tier_retention[tier].db_update_every);
+ if(!common_update_every_s)
+ common_update_every_s = tier_retention[tier].db_update_every_s;
+ else if(tier_retention[tier].db_update_every_s)
+ common_update_every_s = MIN(common_update_every_s, tier_retention[tier].db_update_every_s);
tiers_added++;
}
else {
- tier_retention[tier].db_first_time_t = 0;
- tier_retention[tier].db_last_time_t = 0;
- tier_retention[tier].db_update_every = 0;
+ tier_retention[tier].db_first_time_s = 0;
+ tier_retention[tier].db_last_time_s = 0;
+ tier_retention[tier].db_update_every_s = 0;
}
}
bool release_retention = true;
bool timeframe_matches =
(tiers_added
- && (common_first_time_t - common_update_every * 2) <= qt->window.before
- && (common_last_time_t + common_update_every * 2) >= qt->window.after
+ && (common_first_time_s - common_update_every_s * 2) <= qt->window.before
+ && (common_last_time_s + common_update_every_s * 2) >= qt->window.after
) ? true : false;
if(timeframe_matches) {
@@ -2515,14 +2537,19 @@ static void query_target_add_metric(QUERY_TARGET_LOCALS *qtl, RRDMETRIC_ACQUIRED
// let's add it to the query metrics
if(ri->rrdset)
- ri->rrdset->last_accessed_time = qtl->start_s;
+ ri->rrdset->last_accessed_time_s = qtl->start_s;
if (qt->query.used == qt->query.size) {
+ size_t old_mem = qt->query.size * sizeof(QUERY_METRIC);
qt->query.size = (qt->query.size) ? qt->query.size * 2 : 1;
- qt->query.array = reallocz(qt->query.array, qt->query.size * sizeof(QUERY_METRIC));
+ size_t new_mem = qt->query.size * sizeof(QUERY_METRIC);
+ qt->query.array = reallocz(qt->query.array, new_mem);
+
+ __atomic_add_fetch(&netdata_buffers_statistics.query_targets_size, new_mem - old_mem, __ATOMIC_RELAXED);
}
QUERY_METRIC *qm = &qt->query.array[qt->query.used++];
+ qm->plan.used = 0;
qm->dimension.options = options;
qm->link.host = qtl->host;
@@ -2536,18 +2563,18 @@ static void query_target_add_metric(QUERY_TARGET_LOCALS *qtl, RRDMETRIC_ACQUIRED
qm->dimension.id = string_dup(rm->id);
qm->dimension.name = string_dup(rm->name);
- if (!qt->db.first_time_t || common_first_time_t < qt->db.first_time_t)
- qt->db.first_time_t = common_first_time_t;
+ if (!qt->db.first_time_s || common_first_time_s < qt->db.first_time_s)
+ qt->db.first_time_s = common_first_time_s;
- if (!qt->db.last_time_t || common_last_time_t > qt->db.last_time_t)
- qt->db.last_time_t = common_last_time_t;
+ if (!qt->db.last_time_s || common_last_time_s > qt->db.last_time_s)
+ qt->db.last_time_s = common_last_time_s;
for (size_t tier = 0; tier < storage_tiers; tier++) {
qm->tiers[tier].eng = tier_retention[tier].eng;
qm->tiers[tier].db_metric_handle = tier_retention[tier].db_metric_handle;
- qm->tiers[tier].db_first_time_t = tier_retention[tier].db_first_time_t;
- qm->tiers[tier].db_last_time_t = tier_retention[tier].db_last_time_t;
- qm->tiers[tier].db_update_every = tier_retention[tier].db_update_every;
+ qm->tiers[tier].db_first_time_s = tier_retention[tier].db_first_time_s;
+ qm->tiers[tier].db_last_time_s = tier_retention[tier].db_last_time_s;
+ qm->tiers[tier].db_update_every_s = tier_retention[tier].db_update_every_s;
}
release_retention = false;
}
@@ -2572,14 +2599,18 @@ static void query_target_add_instance(QUERY_TARGET_LOCALS *qtl, RRDINSTANCE_ACQU
return;
if(qt->instances.used == qt->instances.size) {
+ size_t old_mem = qt->instances.size * sizeof(RRDINSTANCE_ACQUIRED *);
qt->instances.size = (qt->instances.size) ? qt->instances.size * 2 : 1;
- qt->instances.array = reallocz(qt->instances.array, qt->instances.size * sizeof(RRDINSTANCE_ACQUIRED *));
+ size_t new_mem = qt->instances.size * sizeof(RRDINSTANCE_ACQUIRED *);
+ qt->instances.array = reallocz(qt->instances.array, new_mem);
+
+ __atomic_add_fetch(&netdata_buffers_statistics.query_targets_size, new_mem - old_mem, __ATOMIC_RELAXED);
}
qtl->ria = qt->instances.array[qt->instances.used++] = rrdinstance_acquired_dup(ria);
- if(qt->db.minimum_latest_update_every == 0 || ri->update_every < qt->db.minimum_latest_update_every)
- qt->db.minimum_latest_update_every = ri->update_every;
+ if(qt->db.minimum_latest_update_every_s == 0 || ri->update_every_s < qt->db.minimum_latest_update_every_s)
+ qt->db.minimum_latest_update_every_s = ri->update_every_s;
if(queryable_instance) {
if ((qt->instances.chart_label_key_pattern && !rrdlabels_match_simple_pattern_parsed(ri->rrdlabels, qt->instances.chart_label_key_pattern, ':')) ||
@@ -2616,8 +2647,12 @@ static void query_target_add_context(QUERY_TARGET_LOCALS *qtl, RRDCONTEXT_ACQUIR
return;
if(qt->contexts.used == qt->contexts.size) {
+ size_t old_mem = qt->contexts.size * sizeof(RRDCONTEXT_ACQUIRED *);
qt->contexts.size = (qt->contexts.size) ? qt->contexts.size * 2 : 1;
- qt->contexts.array = reallocz(qt->contexts.array, qt->contexts.size * sizeof(RRDCONTEXT_ACQUIRED *));
+ size_t new_mem = qt->contexts.size * sizeof(RRDCONTEXT_ACQUIRED *);
+ qt->contexts.array = reallocz(qt->contexts.array, new_mem);
+
+ __atomic_add_fetch(&netdata_buffers_statistics.query_targets_size, new_mem - old_mem, __ATOMIC_RELAXED);
}
qtl->rca = qt->contexts.array[qt->contexts.used++] = rrdcontext_acquired_dup(rca);
@@ -2656,8 +2691,12 @@ static void query_target_add_host(QUERY_TARGET_LOCALS *qtl, RRDHOST *host) {
QUERY_TARGET *qt = qtl->qt;
if(qt->hosts.used == qt->hosts.size) {
+ size_t old_mem = qt->hosts.size * sizeof(RRDHOST *);
qt->hosts.size = (qt->hosts.size) ? qt->hosts.size * 2 : 1;
- qt->hosts.array = reallocz(qt->hosts.array, qt->hosts.size * sizeof(RRDHOST *));
+ size_t new_mem = qt->hosts.size * sizeof(RRDHOST *);
+ qt->hosts.array = reallocz(qt->hosts.array, new_mem);
+
+ __atomic_add_fetch(&netdata_buffers_statistics.query_targets_size, new_mem - old_mem, __ATOMIC_RELAXED);
}
qtl->host = qt->hosts.array[qt->hosts.used++] = host;
@@ -2770,6 +2809,9 @@ void query_target_generate_name(QUERY_TARGET *qt) {
}
QUERY_TARGET *query_target_create(QUERY_TARGET_REQUEST *qtr) {
+ if(!service_running(ABILITY_DATA_QUERIES))
+ return NULL;
+
QUERY_TARGET *qt = &thread_query_target;
if(qt->used)
@@ -2800,7 +2842,7 @@ QUERY_TARGET *query_target_create(QUERY_TARGET_REQUEST *qtr) {
.charts_labels_filter = qt->request.charts_labels_filter,
};
- qt->db.minimum_latest_update_every = 0; // it will be updated by query_target_add_query()
+ qt->db.minimum_latest_update_every_s = 0; // it will be updated by query_target_add_query()
// prepare all the patterns
qt->hosts.pattern = is_valid_sp(qtl.hosts) ? simple_pattern_create(qtl.hosts, ",|\t\r\n\f\v", SIMPLE_PATTERN_EXACT) : NULL;
@@ -2922,7 +2964,7 @@ static void rrdinstance_load_chart_callback(SQL_CHART_DATA *sc, void *data) {
.family = string_strdupz(sc->family),
.chart_type = sc->chart_type,
.priority = sc->priority,
- .update_every = sc->update_every,
+ .update_every_s = sc->update_every,
.flags = RRD_FLAG_ARCHIVED | RRD_FLAG_UPDATE_REASON_LOAD_SQL, // no need for atomics
};
uuid_copy(tri.uuid, sc->chart_id);
@@ -3012,7 +3054,7 @@ static uint64_t rrdcontext_version_hash_with_callback(
// when the context is being collected,
// rc->hub.last_time_t is already zero
- hash += rc->hub.version + rc->hub.last_time_t - rc->hub.first_time_t;
+ hash += rc->hub.version + rc->hub.last_time_s - rc->hub.first_time_s;
rrdcontext_unlock(rc);
@@ -3057,17 +3099,16 @@ static bool rrdmetric_update_retention(RRDMETRIC *rm) {
time_t min_first_time_t = LONG_MAX, max_last_time_t = 0;
if(rm->rrddim) {
- min_first_time_t = rrddim_first_entry_t(rm->rrddim);
- max_last_time_t = rrddim_last_entry_t(rm->rrddim);
+ min_first_time_t = rrddim_first_entry_s(rm->rrddim);
+ max_last_time_t = rrddim_last_entry_s(rm->rrddim);
}
-#ifdef ENABLE_DBENGINE
- else if (dbengine_enabled) {
+ else {
RRDHOST *rrdhost = rm->ri->rc->rrdhost;
for (size_t tier = 0; tier < storage_tiers; tier++) {
- if(!rrdhost->db[tier].instance) continue;
+ STORAGE_ENGINE *eng = rrdhost->db[tier].eng;
time_t first_time_t, last_time_t;
- if (rrdeng_metric_retention_by_uuid(rrdhost->db[tier].instance, &rm->uuid, &first_time_t, &last_time_t) == 0) {
+ if (eng->api.metric_retention_by_uuid(rrdhost->db[tier].instance, &rm->uuid, &first_time_t, &last_time_t)) {
if (first_time_t < min_first_time_t)
min_first_time_t = first_time_t;
@@ -3076,17 +3117,15 @@ static bool rrdmetric_update_retention(RRDMETRIC *rm) {
}
}
}
- else {
- // cannot get retention
+
+ if((min_first_time_t == LONG_MAX || min_first_time_t == 0) && max_last_time_t == 0)
return false;
- }
-#endif
if(min_first_time_t == LONG_MAX)
min_first_time_t = 0;
if(min_first_time_t > max_last_time_t) {
- internal_error(true, "RRDMETRIC: retention of '%s' is flipped", string2str(rm->id));
+ internal_error(true, "RRDMETRIC: retention of '%s' is flipped, first_time_t = %ld, last_time_t = %ld", string2str(rm->id), min_first_time_t, max_last_time_t);
time_t tmp = min_first_time_t;
min_first_time_t = max_last_time_t;
max_last_time_t = tmp;
@@ -3094,17 +3133,17 @@ static bool rrdmetric_update_retention(RRDMETRIC *rm) {
// check if retention changed
- if (min_first_time_t != rm->first_time_t) {
- rm->first_time_t = min_first_time_t;
+ if (min_first_time_t != rm->first_time_s) {
+ rm->first_time_s = min_first_time_t;
rrd_flag_set_updated(rm, RRD_FLAG_UPDATE_REASON_CHANGED_FIRST_TIME_T);
}
- if (max_last_time_t != rm->last_time_t) {
- rm->last_time_t = max_last_time_t;
+ if (max_last_time_t != rm->last_time_s) {
+ rm->last_time_s = max_last_time_t;
rrd_flag_set_updated(rm, RRD_FLAG_UPDATE_REASON_CHANGED_LAST_TIME_T);
}
- if(unlikely(!rm->first_time_t && !rm->last_time_t))
+ if(unlikely(!rm->first_time_s && !rm->last_time_s))
rrd_flag_set_deleted(rm, RRD_FLAG_UPDATE_REASON_ZERO_RETENTION);
rrd_flag_set(rm, RRD_FLAG_LIVE_RETENTION);
@@ -3123,7 +3162,7 @@ static inline bool rrdmetric_should_be_deleted(RRDMETRIC *rm) {
return false;
rrdmetric_update_retention(rm);
- if(rm->first_time_t || rm->last_time_t)
+ if(rm->first_time_s || rm->last_time_s)
return false;
return true;
@@ -3145,7 +3184,7 @@ static inline bool rrdinstance_should_be_deleted(RRDINSTANCE *ri) {
if(unlikely(dictionary_entries(ri->rrdmetrics) != 0))
return false;
- if(ri->first_time_t || ri->last_time_t)
+ if(ri->first_time_s || ri->last_time_s)
return false;
return true;
@@ -3164,7 +3203,7 @@ static inline bool rrdcontext_should_be_deleted(RRDCONTEXT *rc) {
if(unlikely(dictionary_entries(rc->rrdinstances) != 0))
return false;
- if(unlikely(rc->first_time_t || rc->last_time_t))
+ if(unlikely(rc->first_time_s || rc->last_time_s))
return false;
return true;
@@ -3189,7 +3228,7 @@ static void rrdcontext_garbage_collect_single_host(RRDHOST *host, bool worker_jo
RRDCONTEXT *rc;
dfe_start_reentrant((DICTIONARY *)host->rrdctx, rc) {
- if(unlikely(netdata_exit)) break;
+ if(unlikely(!service_running(SERVICE_CONTEXT))) break;
if(worker_jobs) worker_is_busy(WORKER_JOB_CLEANUP);
@@ -3197,7 +3236,7 @@ static void rrdcontext_garbage_collect_single_host(RRDHOST *host, bool worker_jo
RRDINSTANCE *ri;
dfe_start_reentrant(rc->rrdinstances, ri) {
- if(unlikely(netdata_exit)) break;
+ if(unlikely(!service_running(SERVICE_CONTEXT))) break;
RRDMETRIC *rm;
dfe_start_write(ri->rrdmetrics, rm) {
@@ -3313,7 +3352,7 @@ static void rrdinstance_post_process_updates(RRDINSTANCE *ri, bool force, RRD_FL
if(dictionary_entries(ri->rrdmetrics) > 0) {
RRDMETRIC *rm;
dfe_start_read((DICTIONARY *)ri->rrdmetrics, rm) {
- if(unlikely(netdata_exit)) break;
+ if(unlikely(!service_running(SERVICE_CONTEXT))) break;
RRD_FLAGS reason_to_pass = reason;
if(rrd_flag_check(ri, RRD_FLAG_UPDATE_REASON_UPDATE_RETENTION))
@@ -3329,16 +3368,16 @@ static void rrdinstance_post_process_updates(RRDINSTANCE *ri, bool force, RRD_FL
continue;
}
- if(!currently_collected && rrd_flag_check(rm, RRD_FLAG_COLLECTED) && rm->first_time_t)
+ if(!currently_collected && rrd_flag_check(rm, RRD_FLAG_COLLECTED) && rm->first_time_s)
currently_collected = true;
metrics_active++;
- if (rm->first_time_t && rm->first_time_t < min_first_time_t)
- min_first_time_t = rm->first_time_t;
+ if (rm->first_time_s && rm->first_time_s < min_first_time_t)
+ min_first_time_t = rm->first_time_s;
- if (rm->last_time_t && rm->last_time_t > max_last_time_t)
- max_last_time_t = rm->last_time_t;
+ if (rm->last_time_s && rm->last_time_s > max_last_time_t)
+ max_last_time_t = rm->last_time_s;
}
dfe_done(rm);
}
@@ -3351,13 +3390,13 @@ static void rrdinstance_post_process_updates(RRDINSTANCE *ri, bool force, RRD_FL
if(unlikely(!metrics_active)) {
// no metrics available
- if(ri->first_time_t) {
- ri->first_time_t = 0;
+ if(ri->first_time_s) {
+ ri->first_time_s = 0;
rrd_flag_set_updated(ri, RRD_FLAG_UPDATE_REASON_CHANGED_FIRST_TIME_T);
}
- if(ri->last_time_t) {
- ri->last_time_t = 0;
+ if(ri->last_time_s) {
+ ri->last_time_s = 0;
rrd_flag_set_updated(ri, RRD_FLAG_UPDATE_REASON_CHANGED_LAST_TIME_T);
}
@@ -3370,13 +3409,13 @@ static void rrdinstance_post_process_updates(RRDINSTANCE *ri, bool force, RRD_FL
min_first_time_t = 0;
if (unlikely(min_first_time_t == 0 || max_last_time_t == 0)) {
- if(ri->first_time_t) {
- ri->first_time_t = 0;
+ if(ri->first_time_s) {
+ ri->first_time_s = 0;
rrd_flag_set_updated(ri, RRD_FLAG_UPDATE_REASON_CHANGED_FIRST_TIME_T);
}
- if(ri->last_time_t) {
- ri->last_time_t = 0;
+ if(ri->last_time_s) {
+ ri->last_time_s = 0;
rrd_flag_set_updated(ri, RRD_FLAG_UPDATE_REASON_CHANGED_LAST_TIME_T);
}
@@ -3386,13 +3425,13 @@ static void rrdinstance_post_process_updates(RRDINSTANCE *ri, bool force, RRD_FL
else {
rrd_flag_clear(ri, RRD_FLAG_UPDATE_REASON_ZERO_RETENTION);
- if (unlikely(ri->first_time_t != min_first_time_t)) {
- ri->first_time_t = min_first_time_t;
+ if (unlikely(ri->first_time_s != min_first_time_t)) {
+ ri->first_time_s = min_first_time_t;
rrd_flag_set_updated(ri, RRD_FLAG_UPDATE_REASON_CHANGED_FIRST_TIME_T);
}
- if (unlikely(ri->last_time_t != max_last_time_t)) {
- ri->last_time_t = max_last_time_t;
+ if (unlikely(ri->last_time_s != max_last_time_t)) {
+ ri->last_time_s = max_last_time_t;
rrd_flag_set_updated(ri, RRD_FLAG_UPDATE_REASON_CHANGED_LAST_TIME_T);
}
@@ -3413,6 +3452,8 @@ static void rrdcontext_post_process_updates(RRDCONTEXT *rc, bool force, RRD_FLAG
if(worker_jobs)
worker_is_busy(WORKER_JOB_PP_CONTEXT);
+ size_t min_priority_collected = LONG_MAX;
+ size_t min_priority_not_collected = LONG_MAX;
size_t min_priority = LONG_MAX;
time_t min_first_time_t = LONG_MAX, max_last_time_t = 0;
size_t instances_active = 0, instances_deleted = 0;
@@ -3420,7 +3461,7 @@ static void rrdcontext_post_process_updates(RRDCONTEXT *rc, bool force, RRD_FLAG
if(dictionary_entries(rc->rrdinstances) > 0) {
RRDINSTANCE *ri;
dfe_start_reentrant(rc->rrdinstances, ri) {
- if(unlikely(netdata_exit)) break;
+ if(unlikely(!service_running(SERVICE_CONTEXT))) break;
RRD_FLAGS reason_to_pass = reason;
if(rrd_flag_check(rc, RRD_FLAG_UPDATE_REASON_UPDATE_RETENTION))
@@ -3439,7 +3480,7 @@ static void rrdcontext_post_process_updates(RRDCONTEXT *rc, bool force, RRD_FLAG
continue;
}
- if(unlikely(!currently_collected && rrd_flag_is_collected(ri) && ri->first_time_t))
+ if(unlikely(!currently_collected && rrd_flag_is_collected(ri) && ri->first_time_s))
currently_collected = true;
internal_error(rc->units != ri->units,
@@ -3449,16 +3490,31 @@ static void rrdcontext_post_process_updates(RRDCONTEXT *rc, bool force, RRD_FLAG
instances_active++;
- if (ri->priority >= RRDCONTEXT_MINIMUM_ALLOWED_PRIORITY && ri->priority < min_priority)
- min_priority = ri->priority;
+ if (ri->priority >= RRDCONTEXT_MINIMUM_ALLOWED_PRIORITY) {
+ if(rrd_flag_check(ri, RRD_FLAG_COLLECTED)) {
+ if(ri->priority < min_priority_collected)
+ min_priority_collected = ri->priority;
+ }
+ else {
+ if(ri->priority < min_priority_not_collected)
+ min_priority_not_collected = ri->priority;
+ }
+ }
- if (ri->first_time_t && ri->first_time_t < min_first_time_t)
- min_first_time_t = ri->first_time_t;
+ if (ri->first_time_s && ri->first_time_s < min_first_time_t)
+ min_first_time_t = ri->first_time_s;
- if (ri->last_time_t && ri->last_time_t > max_last_time_t)
- max_last_time_t = ri->last_time_t;
+ if (ri->last_time_s && ri->last_time_s > max_last_time_t)
+ max_last_time_t = ri->last_time_s;
}
dfe_done(ri);
+
+ if(min_priority_collected != LONG_MAX)
+ // use the collected priority
+ min_priority = min_priority_collected;
+ else
+ // use the non-collected priority
+ min_priority = min_priority_not_collected;
}
{
@@ -3485,13 +3541,13 @@ static void rrdcontext_post_process_updates(RRDCONTEXT *rc, bool force, RRD_FLAG
if(unlikely(!instances_active)) {
// we had some instances, but they are gone now...
- if(rc->first_time_t) {
- rc->first_time_t = 0;
+ if(rc->first_time_s) {
+ rc->first_time_s = 0;
rrd_flag_set_updated(rc, RRD_FLAG_UPDATE_REASON_CHANGED_FIRST_TIME_T);
}
- if(rc->last_time_t) {
- rc->last_time_t = 0;
+ if(rc->last_time_s) {
+ rc->last_time_s = 0;
rrd_flag_set_updated(rc, RRD_FLAG_UPDATE_REASON_CHANGED_LAST_TIME_T);
}
@@ -3504,13 +3560,13 @@ static void rrdcontext_post_process_updates(RRDCONTEXT *rc, bool force, RRD_FLAG
min_first_time_t = 0;
if (unlikely(min_first_time_t == 0 && max_last_time_t == 0)) {
- if(rc->first_time_t) {
- rc->first_time_t = 0;
+ if(rc->first_time_s) {
+ rc->first_time_s = 0;
rrd_flag_set_updated(rc, RRD_FLAG_UPDATE_REASON_CHANGED_FIRST_TIME_T);
}
- if(rc->last_time_t) {
- rc->last_time_t = 0;
+ if(rc->last_time_s) {
+ rc->last_time_s = 0;
rrd_flag_set_updated(rc, RRD_FLAG_UPDATE_REASON_CHANGED_LAST_TIME_T);
}
@@ -3519,13 +3575,13 @@ static void rrdcontext_post_process_updates(RRDCONTEXT *rc, bool force, RRD_FLAG
else {
rrd_flag_clear(rc, RRD_FLAG_UPDATE_REASON_ZERO_RETENTION);
- if (unlikely(rc->first_time_t != min_first_time_t)) {
- rc->first_time_t = min_first_time_t;
+ if (unlikely(rc->first_time_s != min_first_time_t)) {
+ rc->first_time_s = min_first_time_t;
rrd_flag_set_updated(rc, RRD_FLAG_UPDATE_REASON_CHANGED_FIRST_TIME_T);
}
- if (rc->last_time_t != max_last_time_t) {
- rc->last_time_t = max_last_time_t;
+ if (rc->last_time_s != max_last_time_t) {
+ rc->last_time_s = max_last_time_t;
rrd_flag_set_updated(rc, RRD_FLAG_UPDATE_REASON_CHANGED_LAST_TIME_T);
}
@@ -3592,7 +3648,7 @@ static void rrdcontext_post_process_queued_contexts(RRDHOST *host) {
RRDCONTEXT *rc;
dfe_start_reentrant((DICTIONARY *)host->rrdctx_post_processing_queue, rc) {
- if(unlikely(netdata_exit)) break;
+ if(unlikely(!service_running(SERVICE_CONTEXT))) break;
rrdcontext_dequeue_from_post_processing(rc);
rrdcontext_post_process_updates(rc, false, RRD_FLAG_NONE, true);
@@ -3621,8 +3677,8 @@ static void rrdcontext_message_send_unsafe(RRDCONTEXT *rc, bool snapshot __maybe
rc->hub.family = string2str(rc->family);
rc->hub.chart_type = rrdset_type_name(rc->chart_type);
rc->hub.priority = rc->priority;
- rc->hub.first_time_t = rc->first_time_t;
- rc->hub.last_time_t = rrd_flag_is_collected(rc) ? 0 : rc->last_time_t;
+ rc->hub.first_time_s = rc->first_time_s;
+ rc->hub.last_time_s = rrd_flag_is_collected(rc) ? 0 : rc->last_time_s;
rc->hub.deleted = rrd_flag_is_deleted(rc) ? true : false;
#ifdef ENABLE_ACLK
@@ -3634,8 +3690,8 @@ static void rrdcontext_message_send_unsafe(RRDCONTEXT *rc, bool snapshot __maybe
.family = rc->hub.family,
.chart_type = rc->hub.chart_type,
.priority = rc->hub.priority,
- .first_entry = rc->hub.first_time_t,
- .last_entry = rc->hub.last_time_t,
+ .first_entry = rc->hub.first_time_s,
+ .last_entry = rc->hub.last_time_s,
.deleted = rc->hub.deleted,
};
@@ -3689,10 +3745,10 @@ static bool check_if_cloud_version_changed_unsafe(RRDCONTEXT *rc, bool sending _
if(unlikely(rc->priority != rc->hub.priority))
priority_changed = true;
- if(unlikely((uint64_t)rc->first_time_t != rc->hub.first_time_t))
+ if(unlikely((uint64_t)rc->first_time_s != rc->hub.first_time_s))
first_time_changed = true;
- if(unlikely((uint64_t)((flags & RRD_FLAG_COLLECTED) ? 0 : rc->last_time_t) != rc->hub.last_time_t))
+ if(unlikely((uint64_t)((flags & RRD_FLAG_COLLECTED) ? 0 : rc->last_time_s) != rc->hub.last_time_s))
last_time_changed = true;
if(unlikely(((flags & RRD_FLAG_DELETED) ? true : false) != rc->hub.deleted))
@@ -3711,8 +3767,8 @@ static bool check_if_cloud_version_changed_unsafe(RRDCONTEXT *rc, bool sending _
string2str(rc->family), family_changed ? " (CHANGED)" : "",
rrdset_type_name(rc->chart_type), chart_type_changed ? " (CHANGED)" : "",
rc->priority, priority_changed ? " (CHANGED)" : "",
- rc->first_time_t, first_time_changed ? " (CHANGED)" : "",
- (flags & RRD_FLAG_COLLECTED) ? 0 : rc->last_time_t, last_time_changed ? " (CHANGED)" : "",
+ rc->first_time_s, first_time_changed ? " (CHANGED)" : "",
+ (flags & RRD_FLAG_COLLECTED) ? 0 : rc->last_time_s, last_time_changed ? " (CHANGED)" : "",
(flags & RRD_FLAG_DELETED) ? "true" : "false", deleted_changed ? " (CHANGED)" : "",
sending ? (now_realtime_usec() - rc->queue.queued_ut) / USEC_PER_MS : 0,
sending ? (rc->queue.scheduled_dispatch_ut - rc->queue.queued_ut) / USEC_PER_MS : 0
@@ -3773,7 +3829,7 @@ static void rrdcontext_dispatch_queued_contexts_to_hub(RRDHOST *host, usec_t now
RRDCONTEXT *rc;
dfe_start_reentrant((DICTIONARY *)host->rrdctx_hub_queue, rc) {
- if(unlikely(netdata_exit)) break;
+ if(unlikely(!service_running(SERVICE_CONTEXT))) break;
if(unlikely(messages_added >= MESSAGES_PER_BUNDLE_TO_SEND_TO_HUB_PER_HOST))
break;
@@ -3840,7 +3896,7 @@ static void rrdcontext_dispatch_queued_contexts_to_hub(RRDHOST *host, usec_t now
dfe_done(rc);
#ifdef ENABLE_ACLK
- if(!netdata_exit && bundle) {
+ if(service_running(SERVICE_CONTEXT) && bundle) {
// we have a bundle to send messages
// update the version hash
@@ -3891,11 +3947,11 @@ void *rrdcontext_main(void *ptr) {
heartbeat_init(&hb);
usec_t step = RRDCONTEXT_WORKER_THREAD_HEARTBEAT_USEC;
- while (!netdata_exit) {
+ while (service_running(SERVICE_CONTEXT)) {
worker_is_idle();
heartbeat_next(&hb, step);
- if(unlikely(netdata_exit)) break;
+ if(unlikely(!service_running(SERVICE_CONTEXT))) break;
usec_t now_ut = now_realtime_usec();
@@ -3911,7 +3967,7 @@ void *rrdcontext_main(void *ptr) {
rrd_rdlock();
RRDHOST *host;
rrdhost_foreach_read(host) {
- if(unlikely(netdata_exit)) break;
+ if(unlikely(!service_running(SERVICE_CONTEXT))) break;
worker_is_busy(WORKER_JOB_HOSTS);
diff --git a/database/rrdcontext.h b/database/rrdcontext.h
index 67e6cf39..eae37036 100644
--- a/database/rrdcontext.h
+++ b/database/rrdcontext.h
@@ -118,16 +118,38 @@ DICTIONARY *rrdcontext_all_metrics_to_dict(RRDHOST *host, SIMPLE_PATTERN *contex
// ----------------------------------------------------------------------------
// public API for queries
+typedef struct query_plan_entry {
+ size_t tier;
+ time_t after;
+ time_t before;
+ time_t expanded_after;
+ time_t expanded_before;
+ struct storage_engine_query_handle handle;
+ STORAGE_POINT (*next_metric)(struct storage_engine_query_handle *handle);
+ int (*is_finished)(struct storage_engine_query_handle *handle);
+ void (*finalize)(struct storage_engine_query_handle *handle);
+ bool initialized;
+ bool finalized;
+} QUERY_PLAN_ENTRY;
+
+#define QUERY_PLANS_MAX (RRD_STORAGE_TIERS * 2)
+
typedef struct query_metric {
struct query_metric_tier {
struct storage_engine *eng;
STORAGE_METRIC_HANDLE *db_metric_handle;
- time_t db_first_time_t; // the oldest timestamp available for this tier
- time_t db_last_time_t; // the latest timestamp available for this tier
- time_t db_update_every; // latest update every for this tier
+ time_t db_first_time_s; // the oldest timestamp available for this tier
+ time_t db_last_time_s; // the latest timestamp available for this tier
+ time_t db_update_every_s; // latest update every for this tier
+ long weight;
} tiers[RRD_STORAGE_TIERS];
struct {
+ size_t used;
+ QUERY_PLAN_ENTRY array[QUERY_PLANS_MAX];
+ } plan;
+
+ struct {
RRDHOST *host;
RRDCONTEXT_ACQUIRED *rca;
RRDINSTANCE_ACQUIRED *ria;
@@ -172,6 +194,7 @@ typedef struct query_target_request {
time_t resampling_time;
size_t tier;
QUERY_SOURCE query_source;
+ STORAGE_PRIORITY priority;
} QUERY_TARGET_REQUEST;
typedef struct query_target {
@@ -198,9 +221,9 @@ typedef struct query_target {
} window;
struct {
- time_t first_time_t; // the combined first_time_t of all metrics in the query, across all tiers
- time_t last_time_t; // the combined last_time_T of all metrics in the query, across all tiers
- time_t minimum_latest_update_every; // the min update every of the metrics in the query
+ time_t first_time_s; // the combined first_time_t of all metrics in the query, across all tiers
+ time_t last_time_s; // the combined last_time_T of all metrics in the query, across all tiers
+ time_t minimum_latest_update_every_s; // the min update every of the metrics in the query
} db;
struct {
diff --git a/database/rrddim.c b/database/rrddim.c
index 2d909a70..b8059b3c 100644
--- a/database/rrddim.c
+++ b/database/rrddim.c
@@ -64,12 +64,15 @@ static void rrddim_insert_callback(const DICTIONARY_ITEM *item __maybe_unused, v
size_t entries = st->entries;
if(!entries) entries = 5;
- rd->db = netdata_mmap(NULL, entries * sizeof(storage_number), MAP_PRIVATE, 1);
+ rd->db = netdata_mmap(NULL, entries * sizeof(storage_number), MAP_PRIVATE, 1, false, NULL);
if(!rd->db) {
info("Failed to use memory mode ram for chart '%s', dimension '%s', falling back to alloc", rrdset_name(st), rrddim_name(rd));
ctr->memory_mode = RRD_MEMORY_MODE_ALLOC;
}
- else rd->memsize = entries * sizeof(storage_number);
+ else {
+ rd->memsize = entries * sizeof(storage_number);
+ __atomic_add_fetch(&rrddim_db_memory_size, rd->memsize, __ATOMIC_RELAXED);
+ }
}
if(ctr->memory_mode == RRD_MEMORY_MODE_ALLOC || ctr->memory_mode == RRD_MEMORY_MODE_NONE) {
@@ -78,42 +81,24 @@ static void rrddim_insert_callback(const DICTIONARY_ITEM *item __maybe_unused, v
rd->db = rrddim_alloc_db(entries);
rd->memsize = entries * sizeof(storage_number);
+ __atomic_add_fetch(&rrddim_db_memory_size, rd->memsize, __ATOMIC_RELAXED);
}
rd->rrd_memory_mode = ctr->memory_mode;
- if (unlikely(rrdcontext_find_dimension_uuid(st, rrddim_id(rd), &(rd->metric_uuid)))) {
+ if (unlikely(rrdcontext_find_dimension_uuid(st, rrddim_id(rd), &(rd->metric_uuid))))
uuid_generate(rd->metric_uuid);
- bool found_in_sql = false; (void)found_in_sql;
-
-// bool found_in_sql = true;
-// if(unlikely(sql_find_dimension_uuid(st, rd, &rd->metric_uuid))) {
-// found_in_sql = false;
-// uuid_generate(rd->metric_uuid);
-// }
-
-#ifdef NETDATA_INTERNAL_CHECKS
- char uuid_str[UUID_STR_LEN];
- uuid_unparse_lower(rd->metric_uuid, uuid_str);
- error_report("Dimension UUID for host %s chart [%s] dimension [%s] not found in context. It is now set to %s (%s)",
- string2str(host->hostname),
- string2str(st->name),
- string2str(rd->name),
- uuid_str, found_in_sql ? "found in sqlite" : "newly generated");
-#endif
- }
// initialize the db tiers
{
size_t initialized = 0;
for(size_t tier = 0; tier < storage_tiers ; tier++) {
STORAGE_ENGINE *eng = host->db[tier].eng;
- rd->tiers[tier] = callocz(1, sizeof(struct rrddim_tier));
- rd->tiers[tier]->tier_grouping = host->db[tier].tier_grouping;
- rd->tiers[tier]->collect_ops = &eng->api.collect_ops;
- rd->tiers[tier]->query_ops = &eng->api.query_ops;
- rd->tiers[tier]->db_metric_handle = eng->api.metric_get_or_create(rd, host->db[tier].instance);
- storage_point_unset(rd->tiers[tier]->virtual_point);
+ rd->tiers[tier].tier_grouping = host->db[tier].tier_grouping;
+ rd->tiers[tier].collect_ops = &eng->api.collect_ops;
+ rd->tiers[tier].query_ops = &eng->api.query_ops;
+ rd->tiers[tier].db_metric_handle = eng->api.metric_get_or_create(rd, host->db[tier].instance);
+ storage_point_unset(rd->tiers[tier].virtual_point);
initialized++;
// internal_error(true, "TIER GROUPING of chart '%s', dimension '%s' for tier %d is set to %d", rd->rrdset->name, rd->name, tier, rd->tiers[tier]->tier_grouping);
@@ -122,7 +107,7 @@ static void rrddim_insert_callback(const DICTIONARY_ITEM *item __maybe_unused, v
if(!initialized)
error("Failed to initialize all db tiers for chart '%s', dimension '%s", rrdset_name(st), rrddim_name(rd));
- if(!rd->tiers[0])
+ if(!rd->tiers[0].db_metric_handle)
error("Failed to initialize the first db tier for chart '%s', dimension '%s", rrdset_name(st), rrddim_name(rd));
}
@@ -130,8 +115,8 @@ static void rrddim_insert_callback(const DICTIONARY_ITEM *item __maybe_unused, v
{
size_t initialized = 0;
for (size_t tier = 0; tier < storage_tiers; tier++) {
- if (rd->tiers[tier]) {
- rd->tiers[tier]->db_collection_handle = rd->tiers[tier]->collect_ops->init(rd->tiers[tier]->db_metric_handle, st->rrdhost->db[tier].tier_grouping * st->update_every, rd->rrdset->storage_metrics_groups[tier]);
+ if (rd->tiers[tier].db_metric_handle) {
+ rd->tiers[tier].db_collection_handle = rd->tiers[tier].collect_ops->init(rd->tiers[tier].db_metric_handle, st->rrdhost->db[tier].tier_grouping * st->update_every, rd->rrdset->storage_metrics_groups[tier]);
initialized++;
}
}
@@ -172,7 +157,7 @@ static void rrddim_insert_callback(const DICTIONARY_ITEM *item __maybe_unused, v
rrdset_flag_set(st, RRDSET_FLAG_SYNC_CLOCK);
rrdset_flag_clear(st, RRDSET_FLAG_UPSTREAM_EXPOSED);
- ml_new_dimension(rd);
+ ml_dimension_new(rd);
ctr->react_action = RRDDIM_REACT_NEW;
@@ -181,6 +166,25 @@ static void rrddim_insert_callback(const DICTIONARY_ITEM *item __maybe_unused, v
}
+bool rrddim_finalize_collection_and_check_retention(RRDDIM *rd) {
+ size_t tiers_available = 0, tiers_said_no_retention = 0;
+
+ for(size_t tier = 0; tier < storage_tiers ;tier++) {
+ if(!rd->tiers[tier].db_collection_handle)
+ continue;
+
+ tiers_available++;
+
+ if(rd->tiers[tier].collect_ops->finalize(rd->tiers[tier].db_collection_handle))
+ tiers_said_no_retention++;
+
+ rd->tiers[tier].db_collection_handle = NULL;
+ }
+
+ // return true if the dimension has retention in the db
+ return (!tiers_said_no_retention || tiers_available > tiers_said_no_retention);
+}
+
static void rrddim_delete_callback(const DICTIONARY_ITEM *item __maybe_unused, void *rrddim, void *rrdset) {
RRDDIM *rd = rrddim;
RRDSET *st = rrdset;
@@ -191,23 +195,11 @@ static void rrddim_delete_callback(const DICTIONARY_ITEM *item __maybe_unused, v
rrdcontext_removed_rrddim(rd);
- ml_delete_dimension(rd);
+ ml_dimension_delete(rd);
debug(D_RRD_CALLS, "rrddim_free() %s.%s", rrdset_name(st), rrddim_name(rd));
- size_t tiers_available = 0, tiers_said_no_retention = 0;
- for(size_t tier = 0; tier < storage_tiers ;tier++) {
- if(rd->tiers[tier] && rd->tiers[tier]->db_collection_handle) {
- tiers_available++;
-
- if(rd->tiers[tier]->collect_ops->finalize(rd->tiers[tier]->db_collection_handle))
- tiers_said_no_retention++;
-
- rd->tiers[tier]->db_collection_handle = NULL;
- }
- }
-
- if (tiers_available == tiers_said_no_retention && tiers_said_no_retention && rd->rrd_memory_mode == RRD_MEMORY_MODE_DBENGINE) {
+ if (!rrddim_finalize_collection_and_check_retention(rd) && rd->rrd_memory_mode == RRD_MEMORY_MODE_DBENGINE) {
/* This metric has no data and no references */
metaqueue_delete_dimension_uuid(&rd->metric_uuid);
}
@@ -224,16 +216,16 @@ static void rrddim_delete_callback(const DICTIONARY_ITEM *item __maybe_unused, v
rrddim_memory_file_free(rd);
for(size_t tier = 0; tier < storage_tiers ;tier++) {
- if(!rd->tiers[tier]) continue;
+ if(!rd->tiers[tier].db_metric_handle) continue;
STORAGE_ENGINE* eng = host->db[tier].eng;
- eng->api.metric_release(rd->tiers[tier]->db_metric_handle);
-
- freez(rd->tiers[tier]);
- rd->tiers[tier] = NULL;
+ eng->api.metric_release(rd->tiers[tier].db_metric_handle);
+ rd->tiers[tier].db_metric_handle = NULL;
}
if(rd->db) {
+ __atomic_sub_fetch(&rrddim_db_memory_size, rd->memsize, __ATOMIC_RELAXED);
+
if(rd->rrd_memory_mode == RRD_MEMORY_MODE_RAM)
netdata_munmap(rd->db, rd->memsize);
else
@@ -259,9 +251,9 @@ static bool rrddim_conflict_callback(const DICTIONARY_ITEM *item __maybe_unused,
rc += rrddim_set_divisor(st, rd, ctr->divisor);
for(size_t tier = 0; tier < storage_tiers ;tier++) {
- if (rd->tiers[tier] && !rd->tiers[tier]->db_collection_handle)
- rd->tiers[tier]->db_collection_handle =
- rd->tiers[tier]->collect_ops->init(rd->tiers[tier]->db_metric_handle, st->rrdhost->db[tier].tier_grouping * st->update_every, rd->rrdset->storage_metrics_groups[tier]);
+ if (!rd->tiers[tier].db_collection_handle)
+ rd->tiers[tier].db_collection_handle =
+ rd->tiers[tier].collect_ops->init(rd->tiers[tier].db_metric_handle, st->rrdhost->db[tier].tier_grouping * st->update_every, rd->rrdset->storage_metrics_groups[tier]);
}
if(rrddim_flag_check(rd, RRDDIM_FLAG_ARCHIVED)) {
@@ -285,7 +277,6 @@ static void rrddim_react_callback(const DICTIONARY_ITEM *item __maybe_unused, vo
if(ctr->react_action & (RRDDIM_REACT_UPDATED | RRDDIM_REACT_NEW)) {
rrddim_flag_set(rd, RRDDIM_FLAG_METADATA_UPDATE);
- rrdset_flag_set(rd->rrdset, RRDSET_FLAG_METADATA_UPDATE);
rrdhost_flag_set(rd->rrdset->rrdhost, RRDHOST_FLAG_METADATA_UPDATE);
}
@@ -300,7 +291,8 @@ static void rrddim_react_callback(const DICTIONARY_ITEM *item __maybe_unused, vo
void rrddim_index_init(RRDSET *st) {
if(!st->rrddim_root_index) {
- st->rrddim_root_index = dictionary_create(DICT_OPTION_DONT_OVERWRITE_VALUE);
+ st->rrddim_root_index = dictionary_create_advanced(DICT_OPTION_DONT_OVERWRITE_VALUE | DICT_OPTION_FIXED_SIZE,
+ &dictionary_stats_category_rrdset_rrddim, sizeof(RRDDIM));
dictionary_register_insert_callback(st->rrddim_root_index, rrddim_insert_callback, NULL);
dictionary_register_conflict_callback(st->rrddim_root_index, rrddim_conflict_callback, NULL);
@@ -420,38 +412,45 @@ inline int rrddim_set_divisor(RRDSET *st, RRDDIM *rd, collected_number divisor)
// ----------------------------------------------------------------------------
+time_t rrddim_last_entry_s_of_tier(RRDDIM *rd, size_t tier) {
+ if(unlikely(tier > storage_tiers || !rd->tiers[tier].db_metric_handle))
+ return 0;
+
+ return rd->tiers[tier].query_ops->latest_time_s(rd->tiers[tier].db_metric_handle);
+}
+
// get the timestamp of the last entry in the round-robin database
-time_t rrddim_last_entry_t(RRDDIM *rd) {
- time_t latest = rd->tiers[0]->query_ops->latest_time(rd->tiers[0]->db_metric_handle);
+time_t rrddim_last_entry_s(RRDDIM *rd) {
+ time_t latest_time_s = rrddim_last_entry_s_of_tier(rd, 0);
for(size_t tier = 1; tier < storage_tiers ;tier++) {
- if(unlikely(!rd->tiers[tier])) continue;
+ if(unlikely(!rd->tiers[tier].db_metric_handle)) continue;
- time_t t = rd->tiers[tier]->query_ops->latest_time(rd->tiers[tier]->db_metric_handle);
- if(t > latest)
- latest = t;
+ time_t t = rrddim_last_entry_s_of_tier(rd, tier);
+ if(t > latest_time_s)
+ latest_time_s = t;
}
- return latest;
+ return latest_time_s;
}
-time_t rrddim_first_entry_t_of_tier(RRDDIM *rd, size_t tier) {
- if(unlikely(tier > storage_tiers || !rd->tiers[tier]))
+time_t rrddim_first_entry_s_of_tier(RRDDIM *rd, size_t tier) {
+ if(unlikely(tier > storage_tiers || !rd->tiers[tier].db_metric_handle))
return 0;
- return rd->tiers[tier]->query_ops->oldest_time(rd->tiers[tier]->db_metric_handle);
+ return rd->tiers[tier].query_ops->oldest_time_s(rd->tiers[tier].db_metric_handle);
}
-time_t rrddim_first_entry_t(RRDDIM *rd) {
- time_t oldest = 0;
+time_t rrddim_first_entry_s(RRDDIM *rd) {
+ time_t oldest_time_s = 0;
for(size_t tier = 0; tier < storage_tiers ;tier++) {
- time_t t = rrddim_first_entry_t_of_tier(rd, tier);
- if(t != 0 && (oldest == 0 || t < oldest))
- oldest = t;
+ time_t t = rrddim_first_entry_s_of_tier(rd, tier);
+ if(t != 0 && (oldest_time_s == 0 || t < oldest_time_s))
+ oldest_time_s = t;
}
- return oldest;
+ return oldest_time_s;
}
RRDDIM *rrddim_add_custom(RRDSET *st
@@ -498,8 +497,8 @@ int rrddim_hide(RRDSET *st, const char *id) {
return 1;
}
if (!rrddim_flag_check(rd, RRDDIM_FLAG_META_HIDDEN)) {
- rrddim_flag_set(rd, RRDDIM_FLAG_META_HIDDEN);
- metaqueue_dimension_update_flags(rd);
+ rrddim_flag_set(rd, RRDDIM_FLAG_META_HIDDEN | RRDDIM_FLAG_METADATA_UPDATE);
+ rrdhost_flag_set(rd->rrdset->rrdhost, RRDHOST_FLAG_METADATA_UPDATE);
}
rrddim_option_set(rd, RRDDIM_OPTION_HIDDEN);
@@ -518,7 +517,8 @@ int rrddim_unhide(RRDSET *st, const char *id) {
}
if (rrddim_flag_check(rd, RRDDIM_FLAG_META_HIDDEN)) {
rrddim_flag_clear(rd, RRDDIM_FLAG_META_HIDDEN);
- metaqueue_dimension_update_flags(rd);
+ rrddim_flag_set(rd, RRDDIM_FLAG_METADATA_UPDATE);
+ rrdhost_flag_set(rd->rrdset->rrdhost, RRDHOST_FLAG_METADATA_UPDATE);
}
rrddim_option_clear(rd, RRDDIM_OPTION_HIDDEN);
@@ -650,6 +650,7 @@ void rrddim_memory_file_free(RRDDIM *rd) {
rrddim_memory_file_update(rd);
struct rrddim_map_save_v019 *rd_on_file = rd->rd_on_file;
+ __atomic_sub_fetch(&rrddim_db_memory_size, rd_on_file->memsize + strlen(rd_on_file->cache_filename), __ATOMIC_RELAXED);
freez(rd_on_file->cache_filename);
netdata_munmap(rd_on_file, rd_on_file->memsize);
@@ -686,10 +687,10 @@ bool rrddim_memory_load_or_create_map_save(RRDSET *st, RRDDIM *rd, RRD_MEMORY_MO
char filename[FILENAME_MAX + 1];
char fullfilename[FILENAME_MAX + 1];
rrdset_strncpyz_name(filename, rrddim_id(rd), FILENAME_MAX);
- snprintfz(fullfilename, FILENAME_MAX, "%s/%s.db", st->cache_dir, filename);
+ snprintfz(fullfilename, FILENAME_MAX, "%s/%s.db", rrdset_cache_dir(st), filename);
- rd_on_file = (struct rrddim_map_save_v019 *)netdata_mmap(fullfilename, size,
- ((memory_mode == RRD_MEMORY_MODE_MAP) ? MAP_SHARED : MAP_PRIVATE), 1);
+ rd_on_file = (struct rrddim_map_save_v019 *)netdata_mmap(
+ fullfilename, size, ((memory_mode == RRD_MEMORY_MODE_MAP) ? MAP_SHARED : MAP_PRIVATE), 1, false, NULL);
if(unlikely(!rd_on_file)) return false;
@@ -747,6 +748,8 @@ bool rrddim_memory_load_or_create_map_save(RRDSET *st, RRDDIM *rd, RRD_MEMORY_MO
rd_on_file->rrd_memory_mode = memory_mode;
rd_on_file->cache_filename = strdupz(fullfilename);
+ __atomic_add_fetch(&rrddim_db_memory_size, rd_on_file->memsize + strlen(rd_on_file->cache_filename), __ATOMIC_RELAXED);
+
rd->db = &rd_on_file->values[0];
rd->rd_on_file = rd_on_file;
rd->memsize = size;
diff --git a/database/rrddimvar.c b/database/rrddimvar.c
index 449ceeb9..da8b939c 100644
--- a/database/rrddimvar.c
+++ b/database/rrddimvar.c
@@ -65,7 +65,7 @@ static inline void rrddimvar_free_variables_unsafe(RRDDIMVAR *rs) {
// HOST VARIABLES FOR THIS DIMENSION
- if(host->rrdvars && host->health_enabled) {
+ if(host->rrdvars && host->health.health_enabled) {
rrdvar_release_and_del(host->rrdvars, rs->rrdvar_host_chart_id_dim_id);
rs->rrdvar_host_chart_id_dim_id = NULL;
@@ -152,7 +152,7 @@ static inline void rrddimvar_update_variables_unsafe(RRDDIMVAR *rs) {
// - $chart-name.id
// - $chart-name.name
- if(host->rrdvars && host->health_enabled) {
+ if(host->rrdvars && host->health.health_enabled) {
rs->rrdvar_host_chart_id_dim_id = rrdvar_add_and_acquire("host", host->rrdvars, key_chart_id_dim_id, rs->type, RRDVAR_FLAG_NONE, rs->value);
rs->rrdvar_host_chart_id_dim_name = rrdvar_add_and_acquire("host", host->rrdvars, key_chart_id_dim_name, rs->type, RRDVAR_FLAG_NONE, rs->value);
rs->rrdvar_host_chart_name_dim_id = rrdvar_add_and_acquire("host", host->rrdvars, key_chart_name_dim_id, rs->type, RRDVAR_FLAG_NONE, rs->value);
@@ -214,7 +214,8 @@ static void rrddimvar_delete_callback(const DICTIONARY_ITEM *item __maybe_unused
void rrddimvar_index_init(RRDSET *st) {
if(!st->rrddimvar_root_index) {
- st->rrddimvar_root_index = dictionary_create(DICT_OPTION_DONT_OVERWRITE_VALUE);
+ st->rrddimvar_root_index = dictionary_create_advanced(DICT_OPTION_DONT_OVERWRITE_VALUE | DICT_OPTION_FIXED_SIZE,
+ &dictionary_stats_category_rrdhealth, sizeof(RRDDIMVAR));
dictionary_register_insert_callback(st->rrddimvar_root_index, rrddimvar_insert_callback, NULL);
dictionary_register_conflict_callback(st->rrddimvar_root_index, rrddimvar_conflict_callback, NULL);
diff --git a/database/rrdfamily.c b/database/rrdfamily.c
index e7d1536c..011cb28b 100644
--- a/database/rrdfamily.c
+++ b/database/rrdfamily.c
@@ -33,7 +33,8 @@ static void rrdfamily_delete_callback(const DICTIONARY_ITEM *item __maybe_unused
void rrdfamily_index_init(RRDHOST *host) {
if(!host->rrdfamily_root_index) {
- host->rrdfamily_root_index = dictionary_create(DICT_OPTION_DONT_OVERWRITE_VALUE);
+ host->rrdfamily_root_index = dictionary_create_advanced(DICT_OPTION_DONT_OVERWRITE_VALUE | DICT_OPTION_FIXED_SIZE,
+ &dictionary_stats_category_rrdhealth, sizeof(RRDFAMILY));
dictionary_register_insert_callback(host->rrdfamily_root_index, rrdfamily_insert_callback, NULL);
dictionary_register_delete_callback(host->rrdfamily_root_index, rrdfamily_delete_callback, host);
diff --git a/database/rrdfunctions.c b/database/rrdfunctions.c
index fb847a35..a8341f87 100644
--- a/database/rrdfunctions.c
+++ b/database/rrdfunctions.c
@@ -424,7 +424,9 @@ static bool rrd_functions_conflict_callback(const DICTIONARY_ITEM *item __maybe_
void rrdfunctions_init(RRDHOST *host) {
if(host->functions) return;
- host->functions = dictionary_create(DICT_OPTION_DONT_OVERWRITE_VALUE);
+ host->functions = dictionary_create_advanced(DICT_OPTION_DONT_OVERWRITE_VALUE | DICT_OPTION_FIXED_SIZE,
+ &dictionary_stats_category_functions, sizeof(struct rrd_collector_function));
+
dictionary_register_insert_callback(host->functions, rrd_functions_insert_callback, host);
dictionary_register_delete_callback(host->functions, rrd_functions_delete_callback, host);
dictionary_register_conflict_callback(host->functions, rrd_functions_conflict_callback, host);
@@ -629,7 +631,7 @@ int rrd_call_function_and_wait(RRDHOST *host, BUFFER *wb, int timeout, const cha
pthread_cond_init(&tmp->cond, NULL);
bool we_should_free = true;
- BUFFER *temp_wb = buffer_create(PLUGINSD_LINE_MAX + 1); // we need it because we may give up on it
+ BUFFER *temp_wb = buffer_create(PLUGINSD_LINE_MAX + 1, &netdata_buffers_statistics.buffers_functions); // we need it because we may give up on it
temp_wb->contenttype = wb->contenttype;
code = rdcf->function(temp_wb, timeout, key, rdcf->collector_data, rrd_call_function_signal_when_ready, tmp);
if (code == HTTP_RESP_OK) {
@@ -668,13 +670,14 @@ int rrd_call_function_and_wait(RRDHOST *host, BUFFER *wb, int timeout, const cha
netdata_mutex_unlock(&tmp->mutex);
}
else {
- buffer_free(temp_wb);
if(!buffer_strlen(wb))
rrd_call_function_error(wb, "Failed to send request to the collector.", code);
}
- if (we_should_free)
+ if (we_should_free) {
rrd_function_call_wait_free(tmp);
+ buffer_free(temp_wb);
+ }
}
return code;
diff --git a/database/rrdhost.c b/database/rrdhost.c
index 5ba13d47..60b14c13 100644
--- a/database/rrdhost.c
+++ b/database/rrdhost.c
@@ -3,8 +3,11 @@
#define NETDATA_RRD_INTERNALS
#include "rrd.h"
+static void rrdhost_streaming_sender_structures_init(RRDHOST *host);
+
bool dbengine_enabled = false; // will become true if and when dbengine is initialized
size_t storage_tiers = 3;
+bool use_direct_io = true;
size_t storage_tiers_grouping_iterations[RRD_STORAGE_TIERS] = { 1, 60, 60, 60, 60 };
RRD_BACKFILL storage_tiers_backfill[RRD_STORAGE_TIERS] = { RRD_BACKFILL_NEW, RRD_BACKFILL_NEW, RRD_BACKFILL_NEW, RRD_BACKFILL_NEW, RRD_BACKFILL_NEW };
@@ -24,18 +27,15 @@ size_t get_tier_grouping(size_t tier) {
}
RRDHOST *localhost = NULL;
-size_t rrd_hosts_available = 0;
netdata_rwlock_t rrd_rwlock = NETDATA_RWLOCK_INITIALIZER;
-time_t rrdset_free_obsolete_time = 3600;
-time_t rrdhost_free_orphan_time = 3600;
+time_t rrdset_free_obsolete_time_s = 3600;
+time_t rrdhost_free_orphan_time_s = 3600;
-bool is_storage_engine_shared(STORAGE_INSTANCE *engine) {
+bool is_storage_engine_shared(STORAGE_INSTANCE *engine __maybe_unused) {
#ifdef ENABLE_DBENGINE
- for(size_t tier = 0; tier < storage_tiers ;tier++) {
- if (engine == (STORAGE_INSTANCE *)multidb_ctx[tier])
- return true;
- }
+ if(!rrdeng_is_legacy(engine))
+ return true;
#endif
return false;
@@ -50,20 +50,22 @@ static DICTIONARY *rrdhost_root_index_hostname = NULL;
static inline void rrdhost_init() {
if(unlikely(!rrdhost_root_index)) {
- rrdhost_root_index = dictionary_create(
- DICT_OPTION_NAME_LINK_DONT_CLONE | DICT_OPTION_VALUE_LINK_DONT_CLONE | DICT_OPTION_DONT_OVERWRITE_VALUE);
+ rrdhost_root_index = dictionary_create_advanced(
+ DICT_OPTION_NAME_LINK_DONT_CLONE | DICT_OPTION_VALUE_LINK_DONT_CLONE | DICT_OPTION_DONT_OVERWRITE_VALUE,
+ &dictionary_stats_category_rrdhost, 0);
}
if(unlikely(!rrdhost_root_index_hostname)) {
- rrdhost_root_index_hostname = dictionary_create(
- DICT_OPTION_NAME_LINK_DONT_CLONE | DICT_OPTION_VALUE_LINK_DONT_CLONE | DICT_OPTION_DONT_OVERWRITE_VALUE);
+ rrdhost_root_index_hostname = dictionary_create_advanced(
+ DICT_OPTION_NAME_LINK_DONT_CLONE | DICT_OPTION_VALUE_LINK_DONT_CLONE | DICT_OPTION_DONT_OVERWRITE_VALUE,
+ &dictionary_stats_category_rrdhost, 0);
}
}
// ----------------------------------------------------------------------------
// RRDHOST index by UUID
-inline long rrdhost_hosts_available(void) {
+inline size_t rrdhost_hosts_available(void) {
return dictionary_entries(rrdhost_root_index);
}
@@ -139,7 +141,7 @@ static inline void rrdhost_init_tags(RRDHOST *host, const char *tags) {
string_freez(old);
}
-static inline void rrdhost_init_hostname(RRDHOST *host, const char *hostname) {
+static inline void rrdhost_init_hostname(RRDHOST *host, const char *hostname, bool add_to_index) {
if(unlikely(hostname && !*hostname)) hostname = NULL;
if(host->hostname && hostname && !strcmp(rrdhost_hostname(host), hostname))
@@ -151,7 +153,8 @@ static inline void rrdhost_init_hostname(RRDHOST *host, const char *hostname) {
host->hostname = string_strdupz(hostname?hostname:"localhost");
string_freez(old);
- rrdhost_index_add_hostname(host);
+ if(add_to_index)
+ rrdhost_index_add_hostname(host);
}
static inline void rrdhost_init_os(RRDHOST *host, const char *os) {
@@ -211,7 +214,7 @@ static void rrdhost_initialize_rrdpush_sender(RRDHOST *host,
if(rrdpush_enabled && rrdpush_destination && *rrdpush_destination && rrdpush_api_key && *rrdpush_api_key) {
rrdhost_flag_set(host, RRDHOST_FLAG_RRDPUSH_SENDER_INITIALIZED);
- sender_init(host);
+ rrdhost_streaming_sender_structures_init(host);
#ifdef ENABLE_HTTPS
host->sender->ssl.conn = NULL;
@@ -230,35 +233,34 @@ static void rrdhost_initialize_rrdpush_sender(RRDHOST *host,
rrdhost_option_clear(host, RRDHOST_OPTION_SENDER_ENABLED);
}
-RRDHOST *rrdhost_create(const char *hostname,
- const char *registry_hostname,
- const char *guid,
- const char *os,
- const char *timezone,
- const char *abbrev_timezone,
- int32_t utc_offset,
- const char *tags,
- const char *program_name,
- const char *program_version,
- int update_every,
- long entries,
- RRD_MEMORY_MODE memory_mode,
- unsigned int health_enabled,
- unsigned int rrdpush_enabled,
- char *rrdpush_destination,
- char *rrdpush_api_key,
- char *rrdpush_send_charts_matching,
- bool rrdpush_enable_replication,
- time_t rrdpush_seconds_to_replicate,
- time_t rrdpush_replication_step,
- struct rrdhost_system_info *system_info,
- int is_localhost,
- bool archived
+static RRDHOST *rrdhost_create(
+ const char *hostname,
+ const char *registry_hostname,
+ const char *guid,
+ const char *os,
+ const char *timezone,
+ const char *abbrev_timezone,
+ int32_t utc_offset,
+ const char *tags,
+ const char *program_name,
+ const char *program_version,
+ int update_every,
+ long entries,
+ RRD_MEMORY_MODE memory_mode,
+ unsigned int health_enabled,
+ unsigned int rrdpush_enabled,
+ char *rrdpush_destination,
+ char *rrdpush_api_key,
+ char *rrdpush_send_charts_matching,
+ bool rrdpush_enable_replication,
+ time_t rrdpush_seconds_to_replicate,
+ time_t rrdpush_replication_step,
+ struct rrdhost_system_info *system_info,
+ int is_localhost,
+ bool archived
) {
debug(D_RRDHOST, "Host '%s': adding with guid '%s'", hostname, guid);
- rrd_check_wrlock();
-
if(memory_mode == RRD_MEMORY_MODE_DBENGINE && !dbengine_enabled) {
error("memory mode 'dbengine' is not enabled, but host '%s' is configured for it. Falling back to 'alloc'", hostname);
memory_mode = RRD_MEMORY_MODE_ALLOC;
@@ -272,16 +274,17 @@ int is_legacy = 1;
int is_in_multihost = (memory_mode == RRD_MEMORY_MODE_DBENGINE && !is_legacy);
RRDHOST *host = callocz(1, sizeof(RRDHOST));
+ __atomic_add_fetch(&netdata_buffers_statistics.rrdhost_allocations_size, sizeof(RRDHOST), __ATOMIC_RELAXED);
strncpyz(host->machine_guid, guid, GUID_LEN + 1);
set_host_properties(host, (update_every > 0)?update_every:1, memory_mode, registry_hostname, os,
tags, timezone, abbrev_timezone, utc_offset, program_name, program_version);
- rrdhost_init_hostname(host, hostname);
+ rrdhost_init_hostname(host, hostname, false);
- host->rrd_history_entries = align_entries_to_pagesize(memory_mode, entries);
- host->health_enabled = ((memory_mode == RRD_MEMORY_MODE_NONE)) ? 0 : health_enabled;
+ host->rrd_history_entries = align_entries_to_pagesize(memory_mode, entries);
+ host->health.health_enabled = ((memory_mode == RRD_MEMORY_MODE_NONE)) ? 0 : health_enabled;
if (likely(!archived)) {
rrdfunctions_init(host);
@@ -312,7 +315,6 @@ int is_legacy = 1;
break;
}
- netdata_rwlock_init(&host->rrdhost_rwlock);
netdata_mutex_init(&host->aclk_state_lock);
netdata_mutex_init(&host->receiver_lock);
@@ -356,27 +358,15 @@ int is_legacy = 1;
if(!host->rrdvars)
host->rrdvars = rrdvariables_create();
- RRDHOST *t = rrdhost_index_add_by_guid(host);
- if(t != host) {
- error("Host '%s': cannot add host with machine guid '%s' to index. It already exists as host '%s' with machine guid '%s'.", rrdhost_hostname(host), host->machine_guid, rrdhost_hostname(t), t->machine_guid);
- rrdhost_free(host, 1);
- return NULL;
- }
-
- if (likely(!uuid_parse(host->machine_guid, host->host_uuid))) {
- if(!archived)
- metaqueue_host_update_info(host->machine_guid);
+ if (likely(!uuid_parse(host->machine_guid, host->host_uuid)))
sql_load_node_id(host);
- }
else
error_report("Host machine GUID %s is not valid", host->machine_guid);
rrdfamily_index_init(host);
rrdcalctemplate_index_init(host);
rrdcalc_rrdhost_index_init(host);
-
- if (health_enabled)
- health_thread_spawn(host);
+ metaqueue_host_update_info(host);
if (host->rrd_memory_mode == RRD_MEMORY_MODE_DBENGINE) {
#ifdef ENABLE_DBENGINE
@@ -385,9 +375,12 @@ int is_legacy = 1;
snprintfz(dbenginepath, FILENAME_MAX, "%s/dbengine", host->cache_dir);
ret = mkdir(dbenginepath, 0775);
+
if (ret != 0 && errno != EEXIST)
error("Host '%s': cannot create directory '%s'", rrdhost_hostname(host), dbenginepath);
- else ret = 0; // succeed
+ else
+ ret = 0; // succeed
+
if (is_legacy) {
// initialize legacy dbengine instance as needed
@@ -396,16 +389,17 @@ int is_legacy = 1;
host->db[0].tier_grouping = get_tier_grouping(0);
ret = rrdeng_init(
- host,
(struct rrdengine_instance **)&host->db[0].instance,
dbenginepath,
- default_rrdeng_page_cache_mb,
default_rrdeng_disk_quota_mb,
0); // may fail here for legacy dbengine initialization
if(ret == 0) {
+ rrdeng_readiness_wait((struct rrdengine_instance *)host->db[0].instance);
+
// assign the rest of the shared storage instances to it
// to allow them collect its metrics too
+
for(size_t tier = 1; tier < storage_tiers ; tier++) {
host->db[tier].mode = RRD_MEMORY_MODE_DBENGINE;
host->db[tier].eng = storage_engine_get(host->db[tier].mode);
@@ -422,15 +416,17 @@ int is_legacy = 1;
host->db[tier].tier_grouping = get_tier_grouping(tier);
}
}
+
if (ret) { // check legacy or multihost initialization success
error(
"Host '%s': cannot initialize host with machine guid '%s'. Failed to initialize DB engine at '%s'.",
rrdhost_hostname(host), host->machine_guid, host->cache_dir);
- rrdhost_free(host, 1);
- host = NULL;
- //rrd_hosts_available++; //TODO: maybe we want this?
- return host;
+ rrd_wrlock();
+ rrdhost_free___while_having_rrd_wrlock(host, true);
+ rrd_unlock();
+
+ return NULL;
}
#else
@@ -455,14 +451,6 @@ int is_legacy = 1;
}
// ------------------------------------------------------------------------
- // link it and add it to the index
-
- if(is_localhost)
- DOUBLE_LINKED_LIST_PREPEND_UNSAFE(localhost, host, prev, next);
- else
- DOUBLE_LINKED_LIST_APPEND_UNSAFE(localhost, host, prev, next);
-
- // ------------------------------------------------------------------------
// init new ML host and update system_info to let upstreams know
// about ML functionality
//
@@ -473,6 +461,30 @@ int is_legacy = 1;
host->system_info->mc_version = enable_metric_correlations ? metric_correlations_version : 0;
}
+ // ------------------------------------------------------------------------
+ // link it and add it to the index
+
+ rrd_wrlock();
+
+ RRDHOST *t = rrdhost_index_add_by_guid(host);
+ if(t != host) {
+ error("Host '%s': cannot add host with machine guid '%s' to index. It already exists as host '%s' with machine guid '%s'.", rrdhost_hostname(host), host->machine_guid, rrdhost_hostname(t), t->machine_guid);
+ rrdhost_free___while_having_rrd_wrlock(host, true);
+ rrd_unlock();
+ return NULL;
+ }
+
+ rrdhost_index_add_hostname(host);
+
+ if(is_localhost)
+ DOUBLE_LINKED_LIST_PREPEND_ITEM_UNSAFE(localhost, host, prev, next);
+ else
+ DOUBLE_LINKED_LIST_APPEND_ITEM_UNSAFE(localhost, host, prev, next);
+
+ rrd_unlock();
+
+ // ------------------------------------------------------------------------
+
info("Host '%s' (at registry as '%s') with guid '%s' initialized"
", os '%s'"
", timezone '%s'"
@@ -487,7 +499,6 @@ int is_legacy = 1;
", health %s"
", cache_dir '%s'"
", varlib_dir '%s'"
- ", health_log '%s'"
", alarms default handler '%s'"
", alarms default recipient '%s'"
, rrdhost_hostname(host)
@@ -504,29 +515,27 @@ int is_legacy = 1;
, rrdhost_has_rrdpush_sender_enabled(host)?"enabled":"disabled"
, host->rrdpush_send_destination?host->rrdpush_send_destination:""
, host->rrdpush_send_api_key?host->rrdpush_send_api_key:""
- , host->health_enabled?"enabled":"disabled"
+ , host->health.health_enabled?"enabled":"disabled"
, host->cache_dir
, host->varlib_dir
- , host->health_log_filename
- , string2str(host->health_default_exec)
- , string2str(host->health_default_recipient)
+ , string2str(host->health.health_default_exec)
+ , string2str(host->health.health_default_recipient)
);
- if(!archived)
- metaqueue_host_update_system_info(host);
- rrd_hosts_available++;
+ if(!archived)
+ rrdhost_flag_set(host,RRDHOST_FLAG_METADATA_INFO | RRDHOST_FLAG_METADATA_UPDATE);
rrdhost_load_rrdcontext_data(host);
- if (!archived)
- ml_new_host(host);
- else
- rrdhost_flag_set(host, RRDHOST_FLAG_ARCHIVED);
-
+ if (!archived) {
+ ml_host_new(host);
+ ml_start_anomaly_detection_threads(host);
+ } else
+ rrdhost_flag_set(host, RRDHOST_FLAG_ARCHIVED | RRDHOST_FLAG_ORPHAN);
return host;
}
-void rrdhost_update(RRDHOST *host
+static void rrdhost_update(RRDHOST *host
, const char *hostname
, const char *registry_hostname
, const char *guid
@@ -553,11 +562,16 @@ void rrdhost_update(RRDHOST *host
{
UNUSED(guid);
- host->health_enabled = (mode == RRD_MEMORY_MODE_NONE) ? 0 : health_enabled;
+ netdata_spinlock_lock(&host->rrdhost_update_lock);
- rrdhost_system_info_free(host->system_info);
- host->system_info = system_info;
- metaqueue_host_update_system_info(host);
+ host->health.health_enabled = (mode == RRD_MEMORY_MODE_NONE) ? 0 : health_enabled;
+
+ {
+ struct rrdhost_system_info *old = host->system_info;
+ host->system_info = system_info;
+ rrdhost_flag_set(host, RRDHOST_FLAG_METADATA_INFO | RRDHOST_FLAG_METADATA_CLAIMID | RRDHOST_FLAG_METADATA_UPDATE);
+ rrdhost_system_info_free(old);
+ }
rrdhost_init_os(host, os);
rrdhost_init_timezone(host, timezone, abbrev_timezone, utc_offset);
@@ -567,7 +581,7 @@ void rrdhost_update(RRDHOST *host
if(strcmp(rrdhost_hostname(host), hostname) != 0) {
info("Host '%s' has been renamed to '%s'. If this is not intentional it may mean multiple hosts are using the same machine_guid.", rrdhost_hostname(host), hostname);
- rrdhost_init_hostname(host, hostname);
+ rrdhost_init_hostname(host, hostname, true);
}
if(strcmp(rrdhost_program_name(host), program_name) != 0) {
@@ -628,14 +642,14 @@ void rrdhost_update(RRDHOST *host
host->rrdpush_seconds_to_replicate = rrdpush_seconds_to_replicate;
host->rrdpush_replication_step = rrdpush_replication_step;
- rrd_hosts_available++;
- ml_new_host(host);
+ ml_host_new(host);
+ ml_start_anomaly_detection_threads(host);
+
rrdhost_load_rrdcontext_data(host);
info("Host %s is not in archived mode anymore", rrdhost_hostname(host));
}
- if (health_enabled)
- health_thread_spawn(host);
+ netdata_spinlock_unlock(&host->rrdhost_update_lock);
}
RRDHOST *rrdhost_find_or_create(
@@ -665,15 +679,18 @@ RRDHOST *rrdhost_find_or_create(
) {
debug(D_RRDHOST, "Searching for host '%s' with guid '%s'", hostname, guid);
- rrd_wrlock();
RRDHOST *host = rrdhost_find_by_guid(guid);
if (unlikely(host && host->rrd_memory_mode != mode && rrdhost_flag_check(host, RRDHOST_FLAG_ARCHIVED))) {
/* If a legacy memory mode instantiates all dbengine state must be discarded to avoid inconsistencies */
error("Archived host '%s' has memory mode '%s', but the wanted one is '%s'. Discarding archived state.",
rrdhost_hostname(host), rrd_memory_mode_name(host->rrd_memory_mode), rrd_memory_mode_name(mode));
- rrdhost_free(host, 1);
+
+ rrd_wrlock();
+ rrdhost_free___while_having_rrd_wrlock(host, true);
host = NULL;
+ rrd_unlock();
}
+
if(!host) {
host = rrdhost_create(
hostname
@@ -703,6 +720,7 @@ RRDHOST *rrdhost_find_or_create(
);
}
else {
+
rrdhost_update(host
, hostname
, registry_hostname
@@ -726,19 +744,13 @@ RRDHOST *rrdhost_find_or_create(
, rrdpush_seconds_to_replicate
, rrdpush_replication_step
, system_info);
- }
- if (host) {
- rrdhost_wrlock(host);
- rrdhost_flag_clear(host, RRDHOST_FLAG_ORPHAN);
- host->senders_disconnected_time = 0;
- rrdhost_unlock(host);
- }
- rrd_unlock();
+ }
return host;
}
-inline int rrdhost_should_be_removed(RRDHOST *host, RRDHOST *protected_host, time_t now) {
+
+inline int rrdhost_should_be_removed(RRDHOST *host, RRDHOST *protected_host, time_t now_s) {
if(host != protected_host
&& host != localhost
&& rrdhost_receiver_replicating_charts(host) == 0
@@ -746,8 +758,8 @@ inline int rrdhost_should_be_removed(RRDHOST *host, RRDHOST *protected_host, tim
&& rrdhost_flag_check(host, RRDHOST_FLAG_ORPHAN)
&& !rrdhost_flag_check(host, RRDHOST_FLAG_ARCHIVED)
&& !host->receiver
- && host->senders_disconnected_time
- && host->senders_disconnected_time + rrdhost_free_orphan_time < now)
+ && host->child_disconnected_time
+ && host->child_disconnected_time + rrdhost_free_orphan_time_s < now_s)
return 1;
return 0;
@@ -756,8 +768,34 @@ inline int rrdhost_should_be_removed(RRDHOST *host, RRDHOST *protected_host, tim
// ----------------------------------------------------------------------------
// RRDHOST global / startup initialization
+#ifdef ENABLE_DBENGINE
+struct dbengine_initialization {
+ netdata_thread_t thread;
+ char path[FILENAME_MAX + 1];
+ int disk_space_mb;
+ size_t tier;
+ int ret;
+};
+
+void *dbengine_tier_init(void *ptr) {
+ struct dbengine_initialization *dbi = ptr;
+ dbi->ret = rrdeng_init(NULL, dbi->path, dbi->disk_space_mb, dbi->tier);
+ return ptr;
+}
+#endif
+
void dbengine_init(char *hostname) {
#ifdef ENABLE_DBENGINE
+ use_direct_io = config_get_boolean(CONFIG_SECTION_DB, "dbengine use direct io", use_direct_io);
+
+ unsigned read_num = (unsigned)config_get_number(CONFIG_SECTION_DB, "dbengine pages per extent", MAX_PAGES_PER_EXTENT);
+ if (read_num > 0 && read_num <= MAX_PAGES_PER_EXTENT)
+ rrdeng_pages_per_extent = read_num;
+ else {
+ error("Invalid dbengine pages per extent %u given. Using %u.", read_num, rrdeng_pages_per_extent);
+ config_set_number(CONFIG_SECTION_DB, "dbengine pages per extent", rrdeng_pages_per_extent);
+ }
+
storage_tiers = config_get_number(CONFIG_SECTION_DB, "storage tiers", storage_tiers);
if(storage_tiers < 1) {
error("At least 1 storage tier is required. Assuming 1.");
@@ -770,6 +808,9 @@ void dbengine_init(char *hostname) {
config_set_number(CONFIG_SECTION_DB, "storage tiers", storage_tiers);
}
+ bool parallel_initialization = (storage_tiers <= (size_t)get_netdata_cpus()) ? true : false;
+ parallel_initialization = config_get_boolean(CONFIG_SECTION_DB, "dbengine parallel initialization", parallel_initialization);
+
default_rrdeng_page_fetch_timeout = (int) config_get_number(CONFIG_SECTION_DB, "dbengine page fetch timeout secs", PAGE_CACHE_FETCH_WAIT_TIMEOUT);
if (default_rrdeng_page_fetch_timeout < 1) {
info("'dbengine page fetch timeout secs' cannot be %d, using 1", default_rrdeng_page_fetch_timeout);
@@ -784,10 +825,7 @@ void dbengine_init(char *hostname) {
config_set_number(CONFIG_SECTION_DB, "dbengine page fetch retries", default_rrdeng_page_fetch_retries);
}
- if(config_get_boolean(CONFIG_SECTION_DB, "dbengine page descriptors in file mapped memory", rrdeng_page_descr_is_mmap()) == CONFIG_BOOLEAN_YES)
- rrdeng_page_descr_use_mmap();
- else
- rrdeng_page_descr_use_malloc();
+ struct dbengine_initialization tiers_init[RRD_STORAGE_TIERS] = {};
size_t created_tiers = 0;
char dbenginepath[FILENAME_MAX + 1];
@@ -808,15 +846,11 @@ void dbengine_init(char *hostname) {
if(tier > 0)
divisor *= 2;
- int page_cache_mb = default_rrdeng_page_cache_mb / divisor;
int disk_space_mb = default_multidb_disk_quota_mb / divisor;
size_t grouping_iterations = storage_tiers_grouping_iterations[tier];
RRD_BACKFILL backfill = storage_tiers_backfill[tier];
if(tier > 0) {
- snprintfz(dbengineconfig, 200, "dbengine tier %zu page cache size MB", tier);
- page_cache_mb = config_get_number(CONFIG_SECTION_DB, dbengineconfig, page_cache_mb);
-
snprintfz(dbengineconfig, 200, "dbengine tier %zu multihost disk space MB", tier);
disk_space_mb = config_get_number(CONFIG_SECTION_DB, dbengineconfig, disk_space_mb);
@@ -850,13 +884,30 @@ void dbengine_init(char *hostname) {
}
internal_error(true, "DBENGINE tier %zu grouping iterations is set to %zu", tier, storage_tiers_grouping_iterations[tier]);
- ret = rrdeng_init(NULL, NULL, dbenginepath, page_cache_mb, disk_space_mb, tier);
- if(ret != 0) {
+
+ tiers_init[tier].disk_space_mb = disk_space_mb;
+ tiers_init[tier].tier = tier;
+ strncpyz(tiers_init[tier].path, dbenginepath, FILENAME_MAX);
+ tiers_init[tier].ret = 0;
+
+ if(parallel_initialization)
+ netdata_thread_create(&tiers_init[tier].thread, "DBENGINE_INIT", NETDATA_THREAD_OPTION_JOINABLE,
+ dbengine_tier_init, &tiers_init[tier]);
+ else
+ dbengine_tier_init(&tiers_init[tier]);
+ }
+
+ for(size_t tier = 0; tier < storage_tiers ;tier++) {
+ void *ptr;
+
+ if(parallel_initialization)
+ netdata_thread_join(tiers_init[tier].thread, &ptr);
+
+ if(tiers_init[tier].ret != 0) {
error("DBENGINE on '%s': Failed to initialize multi-host database tier %zu on path '%s'",
- hostname, tier, dbenginepath);
- break;
+ hostname, tiers_init[tier].tier, tiers_init[tier].path);
}
- else
+ else if(created_tiers == tier)
created_tiers++;
}
@@ -868,6 +919,9 @@ void dbengine_init(char *hostname) {
else if(!created_tiers)
fatal("DBENGINE on '%s', failed to initialize databases at '%s'.", hostname, netdata_configured_cache_dir);
+ for(size_t tier = 0; tier < storage_tiers ;tier++)
+ rrdeng_readiness_wait(multidb_ctx[tier]);
+
dbengine_enabled = true;
#else
storage_tiers = config_get_number(CONFIG_SECTION_DB, "storage tiers", 1);
@@ -880,7 +934,7 @@ void dbengine_init(char *hostname) {
#endif
}
-int rrd_init(char *hostname, struct rrdhost_system_info *system_info) {
+int rrd_init(char *hostname, struct rrdhost_system_info *system_info, bool unittest) {
rrdhost_init();
if (unlikely(sql_init_database(DB_CHECK_NONE, system_info ? 0 : 1))) {
@@ -893,7 +947,7 @@ int rrd_init(char *hostname, struct rrdhost_system_info *system_info) {
error_report("Failed to initialize context metadata database");
}
- if (unlikely(strcmp(hostname, "unittest") == 0)) {
+ if (unlikely(unittest)) {
dbengine_enabled = true;
}
else {
@@ -901,11 +955,11 @@ int rrd_init(char *hostname, struct rrdhost_system_info *system_info) {
rrdpush_init();
if (default_rrd_memory_mode == RRD_MEMORY_MODE_DBENGINE || rrdpush_receiver_needs_dbengine()) {
- info("Initializing dbengine...");
+ info("DBENGINE: Initializing ...");
dbengine_init(hostname);
}
else {
- info("Not initializing dbengine...");
+ info("DBENGINE: Not initializing ...");
storage_tiers = 1;
}
@@ -923,42 +977,41 @@ int rrd_init(char *hostname, struct rrdhost_system_info *system_info) {
}
}
- metadata_sync_init();
+ if(!unittest)
+ metadata_sync_init();
+
debug(D_RRDHOST, "Initializing localhost with hostname '%s'", hostname);
- rrd_wrlock();
localhost = rrdhost_create(
- hostname
- , registry_get_this_machine_hostname()
+ hostname
+ , registry_get_this_machine_hostname()
, registry_get_this_machine_guid()
, os_type
- , netdata_configured_timezone
- , netdata_configured_abbrev_timezone
- , netdata_configured_utc_offset
- , ""
- , program_name
- , program_version
- , default_rrd_update_every
- , default_rrd_history_entries
- , default_rrd_memory_mode
- , default_health_enabled
- , default_rrdpush_enabled
- , default_rrdpush_destination
- , default_rrdpush_api_key
- , default_rrdpush_send_charts_matching
- , default_rrdpush_enable_replication
- , default_rrdpush_seconds_to_replicate
- , default_rrdpush_replication_step
- , system_info
- , 1
- , 0
+ , netdata_configured_timezone
+ , netdata_configured_abbrev_timezone
+ , netdata_configured_utc_offset
+ , ""
+ , program_name
+ , program_version
+ , default_rrd_update_every
+ , default_rrd_history_entries
+ , default_rrd_memory_mode
+ , default_health_enabled
+ , default_rrdpush_enabled
+ , default_rrdpush_destination
+ , default_rrdpush_api_key
+ , default_rrdpush_send_charts_matching
+ , default_rrdpush_enable_replication
+ , default_rrdpush_seconds_to_replicate
+ , default_rrdpush_replication_step
+ , system_info
+ , 1
+ , 0
);
+
if (unlikely(!localhost)) {
- rrd_unlock();
return 1;
}
- rrd_unlock();
-
if (likely(system_info)) {
migrate_localhost(&localhost->host_uuid);
sql_aclk_sync_init();
@@ -968,46 +1021,12 @@ int rrd_init(char *hostname, struct rrdhost_system_info *system_info) {
}
// ----------------------------------------------------------------------------
-// RRDHOST - lock validations
-// there are only used when NETDATA_INTERNAL_CHECKS is set
-
-void __rrdhost_check_rdlock(RRDHOST *host, const char *file, const char *function, const unsigned long line) {
- debug(D_RRDHOST, "Checking read lock on host '%s'", rrdhost_hostname(host));
-
- int ret = netdata_rwlock_trywrlock(&host->rrdhost_rwlock);
- if(ret == 0)
- fatal("RRDHOST '%s' should be read-locked, but it is not, at function %s() at line %lu of file '%s'", rrdhost_hostname(host), function, line, file);
-}
-
-void __rrdhost_check_wrlock(RRDHOST *host, const char *file, const char *function, const unsigned long line) {
- debug(D_RRDHOST, "Checking write lock on host '%s'", rrdhost_hostname(host));
-
- int ret = netdata_rwlock_tryrdlock(&host->rrdhost_rwlock);
- if(ret == 0)
- fatal("RRDHOST '%s' should be write-locked, but it is not, at function %s() at line %lu of file '%s'", rrdhost_hostname(host), function, line, file);
-}
-
-void __rrd_check_rdlock(const char *file, const char *function, const unsigned long line) {
- debug(D_RRDHOST, "Checking read lock on all RRDs");
-
- int ret = netdata_rwlock_trywrlock(&rrd_rwlock);
- if(ret == 0)
- fatal("RRDs should be read-locked, but it are not, at function %s() at line %lu of file '%s'", function, line, file);
-}
-
-void __rrd_check_wrlock(const char *file, const char *function, const unsigned long line) {
- debug(D_RRDHOST, "Checking write lock on all RRDs");
-
- int ret = netdata_rwlock_tryrdlock(&rrd_rwlock);
- if(ret == 0)
- fatal("RRDs should be write-locked, but it are not, at function %s() at line %lu of file '%s'", function, line, file);
-}
-
-// ----------------------------------------------------------------------------
// RRDHOST - free
void rrdhost_system_info_free(struct rrdhost_system_info *system_info) {
if(likely(system_info)) {
+ __atomic_sub_fetch(&netdata_buffers_statistics.rrdhost_allocations_size, sizeof(struct rrdhost_system_info), __ATOMIC_RELAXED);
+
freez(system_info->cloud_provider_type);
freez(system_info->cloud_instance_type);
freez(system_info->cloud_instance_region);
@@ -1042,63 +1061,80 @@ void rrdhost_system_info_free(struct rrdhost_system_info *system_info) {
}
}
-void destroy_receiver_state(struct receiver_state *rpt);
+static void rrdhost_streaming_sender_structures_init(RRDHOST *host)
+{
+ if (host->sender)
+ return;
+
+ host->sender = callocz(1, sizeof(*host->sender));
+ __atomic_add_fetch(&netdata_buffers_statistics.rrdhost_senders, sizeof(*host->sender), __ATOMIC_RELAXED);
+
+ host->sender->host = host;
+ host->sender->buffer = cbuffer_new(CBUFFER_INITIAL_SIZE, 1024 * 1024, &netdata_buffers_statistics.cbuffers_streaming);
+ host->sender->capabilities = STREAM_OUR_CAPABILITIES;
-void stop_streaming_sender(RRDHOST *host)
+ host->sender->rrdpush_sender_pipe[PIPE_READ] = -1;
+ host->sender->rrdpush_sender_pipe[PIPE_WRITE] = -1;
+ host->sender->rrdpush_sender_socket = -1;
+
+#ifdef ENABLE_COMPRESSION
+ if(default_compression_enabled) {
+ host->sender->flags |= SENDER_FLAG_COMPRESSION;
+ host->sender->compressor = create_compressor();
+ }
+ else
+ host->sender->flags &= ~SENDER_FLAG_COMPRESSION;
+#endif
+
+ netdata_mutex_init(&host->sender->mutex);
+ replication_init_sender(host->sender);
+}
+
+static void rrdhost_streaming_sender_structures_free(RRDHOST *host)
{
rrdhost_option_clear(host, RRDHOST_OPTION_SENDER_ENABLED);
if (unlikely(!host->sender))
return;
- rrdpush_sender_thread_stop(host); // stop a possibly running thread
+ rrdpush_sender_thread_stop(host, "HOST CLEANUP", true); // stop a possibly running thread
cbuffer_free(host->sender->buffer);
#ifdef ENABLE_COMPRESSION
if (host->sender->compressor)
host->sender->compressor->destroy(&host->sender->compressor);
#endif
replication_cleanup_sender(host->sender);
+
+ __atomic_sub_fetch(&netdata_buffers_statistics.rrdhost_senders, sizeof(*host->sender), __ATOMIC_RELAXED);
+
freez(host->sender);
host->sender = NULL;
rrdhost_flag_clear(host, RRDHOST_FLAG_RRDPUSH_SENDER_INITIALIZED);
}
-void stop_streaming_receiver(RRDHOST *host)
-{
- netdata_mutex_lock(&host->receiver_lock);
- if (host->receiver) {
- if (!host->receiver->exited)
- netdata_thread_cancel(host->receiver->thread);
- netdata_mutex_unlock(&host->receiver_lock);
- struct receiver_state *rpt = host->receiver;
- while (host->receiver && !rpt->exited)
- sleep_usec(50 * USEC_PER_MS);
- // If the receiver detached from the host then its thread will destroy the state
- if (host->receiver == rpt)
- destroy_receiver_state(host->receiver);
- } else
- netdata_mutex_unlock(&host->receiver_lock);
-}
-
-void rrdhost_free(RRDHOST *host, bool force) {
+void rrdhost_free___while_having_rrd_wrlock(RRDHOST *host, bool force) {
if(!host) return;
- if (netdata_exit || force)
- info("Freeing all memory for host '%s'...", rrdhost_hostname(host));
+ if (netdata_exit || force) {
+ info("RRD: 'host:%s' freeing memory...", rrdhost_hostname(host));
- rrd_check_wrlock(); // make sure the RRDs are write locked
+ // ------------------------------------------------------------------------
+ // first remove it from the indexes, so that it will not be discoverable
- rrdhost_wrlock(host);
- ml_delete_host(host);
- rrdhost_unlock(host);
+ rrdhost_index_del_hostname(host);
+ rrdhost_index_del_by_guid(host);
+
+ if (host->prev)
+ DOUBLE_LINKED_LIST_REMOVE_ITEM_UNSAFE(localhost, host, prev, next);
+ }
// ------------------------------------------------------------------------
// clean up streaming
- stop_streaming_sender(host);
+ rrdhost_streaming_sender_structures_free(host);
if (netdata_exit || force)
- stop_streaming_receiver(host);
+ stop_streaming_receiver(host, "HOST CLEANUP");
// ------------------------------------------------------------------------
@@ -1106,9 +1142,6 @@ void rrdhost_free(RRDHOST *host, bool force) {
rrdcalc_delete_all(host);
-
- rrdhost_wrlock(host); // lock this RRDHOST
-
// ------------------------------------------------------------------------
// release its children resources
@@ -1126,6 +1159,10 @@ void rrdhost_free(RRDHOST *host, bool force) {
rrdcalc_rrdhost_index_destroy(host);
rrdcalctemplate_index_destroy(host);
+ // cleanup ML resources
+ ml_stop_anomaly_detection_threads(host);
+ ml_host_delete(host);
+
freez(host->exporting_flags);
health_alarm_log_free(host);
@@ -1140,9 +1177,8 @@ void rrdhost_free(RRDHOST *host, bool force) {
#endif
if (!netdata_exit && !force) {
- info("Setting archive mode for host '%s'...", rrdhost_hostname(host));
- rrdhost_flag_set(host, RRDHOST_FLAG_ARCHIVED);
- rrdhost_unlock(host);
+ info("RRD: 'host:%s' is now in archive mode...", rrdhost_hostname(host));
+ rrdhost_flag_set(host, RRDHOST_FLAG_ARCHIVED | RRDHOST_FLAG_ORPHAN);
return;
}
@@ -1162,17 +1198,6 @@ void rrdhost_free(RRDHOST *host, bool force) {
#endif
// ------------------------------------------------------------------------
- // remove it from the indexes
-
- rrdhost_index_del_hostname(host);
- rrdhost_index_del_by_guid(host);
-
- // ------------------------------------------------------------------------
- // unlink it from the host
-
- DOUBLE_LINKED_LIST_REMOVE_UNSAFE(localhost, host, prev, next);
-
- // ------------------------------------------------------------------------
// free it
pthread_mutex_destroy(&host->aclk_state_lock);
@@ -1191,14 +1216,11 @@ void rrdhost_free(RRDHOST *host, bool force) {
freez(host->rrdpush_send_api_key);
freez(host->rrdpush_send_destination);
rrdpush_destinations_free(host);
- string_freez(host->health_default_exec);
- string_freez(host->health_default_recipient);
- freez(host->health_log_filename);
+ string_freez(host->health.health_default_exec);
+ string_freez(host->health.health_default_recipient);
string_freez(host->registry_hostname);
simple_pattern_free(host->rrdpush_send_charts_matching);
- rrdhost_unlock(host);
netdata_rwlock_destroy(&host->health_log.alarm_log_rwlock);
- netdata_rwlock_destroy(&host->rrdhost_rwlock);
freez(host->node_id);
rrdfamily_index_destroy(host);
@@ -1208,12 +1230,12 @@ void rrdhost_free(RRDHOST *host, bool force) {
rrdhost_destroy_rrdcontexts(host);
string_freez(host->hostname);
+ __atomic_sub_fetch(&netdata_buffers_statistics.rrdhost_allocations_size, sizeof(RRDHOST), __ATOMIC_RELAXED);
freez(host);
#ifdef ENABLE_ACLK
if (wc)
wc->is_orphan = 0;
#endif
- rrd_hosts_available--;
}
void rrdhost_free_all(void) {
@@ -1221,21 +1243,30 @@ void rrdhost_free_all(void) {
/* Make sure child-hosts are released before the localhost. */
while(localhost && localhost->next)
- rrdhost_free(localhost->next, 1);
+ rrdhost_free___while_having_rrd_wrlock(localhost->next, true);
if(localhost)
- rrdhost_free(localhost, 1);
+ rrdhost_free___while_having_rrd_wrlock(localhost, true);
rrd_unlock();
}
+void rrd_finalize_collection_for_all_hosts(void) {
+ RRDHOST *host;
+ rrd_wrlock();
+ rrdhost_foreach_read(host) {
+ rrdhost_finalize_collection(host);
+ }
+ rrd_unlock();
+}
+
// ----------------------------------------------------------------------------
// RRDHOST - save host files
void rrdhost_save_charts(RRDHOST *host) {
if(!host) return;
- info("Saving/Closing database of host '%s'...", rrdhost_hostname(host));
+ info("RRD: 'host:%s' saving / closing database...", rrdhost_hostname(host));
RRDSET *st;
@@ -1312,8 +1343,7 @@ static void rrdhost_load_auto_labels(void) {
health_add_host_labels();
- rrdlabels_add(
- labels, "_is_parent", (localhost->senders_count > 0) ? "true" : "false", RRDLABEL_SRC_AUTO);
+ rrdlabels_add(labels, "_is_parent", (localhost->connected_children_count > 0) ? "true" : "false", RRDLABEL_SRC_AUTO);
if (localhost->rrdpush_send_destination)
rrdlabels_add(labels, "_streams_to", localhost->rrdpush_send_destination, RRDLABEL_SRC_AUTO);
@@ -1391,13 +1421,18 @@ void reload_host_labels(void) {
rrdhost_load_kubernetes_labels();
rrdhost_load_auto_labels();
- rrdlabels_remove_all_unmarked(localhost->rrdlabels);
- metaqueue_store_host_labels(localhost->machine_guid);
-
- health_label_log_save(localhost);
+ rrdhost_flag_set(localhost,RRDHOST_FLAG_METADATA_LABELS | RRDHOST_FLAG_METADATA_UPDATE);
rrdpush_send_host_labels(localhost);
- health_reload();
+}
+
+void rrdhost_finalize_collection(RRDHOST *host) {
+ info("RRD: 'host:%s' stopping data collection...", rrdhost_hostname(host));
+
+ RRDSET *st;
+ rrdset_foreach_write(st, host)
+ rrdset_finalize_collection(st, true);
+ rrdset_foreach_done(st);
}
// ----------------------------------------------------------------------------
@@ -1406,16 +1441,18 @@ void reload_host_labels(void) {
void rrdhost_delete_charts(RRDHOST *host) {
if(!host) return;
- info("Deleting database of host '%s'...", rrdhost_hostname(host));
+ info("RRD: 'host:%s' deleting disk files...", rrdhost_hostname(host));
RRDSET *st;
- // we get a write lock
- // to ensure only one thread is saving the database
- rrdset_foreach_write(st, host) {
- rrdset_delete_files(st);
+ if(host->rrd_memory_mode == RRD_MEMORY_MODE_SAVE || host->rrd_memory_mode == RRD_MEMORY_MODE_MAP) {
+ // we get a write lock
+ // to ensure only one thread is saving the database
+ rrdset_foreach_write(st, host){
+ rrdset_delete_files(st);
+ }
+ rrdset_foreach_done(st);
}
- rrdset_foreach_done(st);
recursively_delete_dir(host->cache_dir, "left over host");
}
@@ -1426,7 +1463,7 @@ void rrdhost_delete_charts(RRDHOST *host) {
void rrdhost_cleanup_charts(RRDHOST *host) {
if(!host) return;
- info("Cleaning up database of host '%s'...", rrdhost_hostname(host));
+ info("RRD: 'host:%s' cleaning up disk files...", rrdhost_hostname(host));
RRDSET *st;
uint32_t rrdhost_delete_obsolete_charts = rrdhost_option_check(host, RRDHOST_OPTION_DELETE_OBSOLETE_CHARTS);
@@ -1453,7 +1490,7 @@ void rrdhost_cleanup_charts(RRDHOST *host) {
// RRDHOST - save all hosts to disk
void rrdhost_save_all(void) {
- info("Saving database [%zu hosts(s)]...", rrd_hosts_available);
+ info("RRD: saving databases [%zu hosts(s)]...", rrdhost_hosts_available());
rrd_rdlock();
@@ -1468,7 +1505,7 @@ void rrdhost_save_all(void) {
// RRDHOST - save or delete all hosts from disk
void rrdhost_cleanup_all(void) {
- info("Cleaning up database [%zu hosts(s)]...", rrd_hosts_available);
+ info("RRD: cleaning up database [%zu hosts(s)]...", rrdhost_hosts_available());
rrd_rdlock();
@@ -1622,19 +1659,3 @@ int rrdhost_set_system_info_variable(struct rrdhost_system_info *system_info, ch
return res;
}
-
-// Added for gap-filling, if this proves to be a bottleneck in large-scale systems then we will need to cache
-// the last entry times as the metric updates, but let's see if it is a problem first.
-time_t rrdhost_last_entry_t(RRDHOST *h) {
- RRDSET *st;
- time_t result = 0;
-
- rrdset_foreach_read(st, h) {
- time_t st_last = rrdset_last_entry_t(st);
-
- if (st_last > result)
- result = st_last;
- }
- rrdset_foreach_done(st);
- return result;
-}
diff --git a/database/rrdlabels.c b/database/rrdlabels.c
index 743499ab..4a9a6dae 100644
--- a/database/rrdlabels.c
+++ b/database/rrdlabels.c
@@ -533,7 +533,9 @@ static bool rrdlabel_conflict_callback(const DICTIONARY_ITEM *item __maybe_unuse
}
DICTIONARY *rrdlabels_create(void) {
- DICTIONARY *dict = dictionary_create(DICT_OPTION_DONT_OVERWRITE_VALUE);
+ DICTIONARY *dict = dictionary_create_advanced(DICT_OPTION_DONT_OVERWRITE_VALUE | DICT_OPTION_FIXED_SIZE,
+ &dictionary_stats_category_rrdlabels, sizeof(RRDLABEL));
+
dictionary_register_insert_callback(dict, rrdlabel_insert_callback, dict);
dictionary_register_delete_callback(dict, rrdlabel_delete_callback, dict);
dictionary_register_conflict_callback(dict, rrdlabel_conflict_callback, dict);
@@ -964,7 +966,8 @@ void rrdset_update_rrdlabels(RRDSET *st, DICTIONARY *new_rrdlabels) {
if (new_rrdlabels)
rrdlabels_migrate_to_these(st->rrdlabels, new_rrdlabels);
- metaqueue_chart_labels(st);
+ rrdset_flag_set(st, RRDSET_FLAG_METADATA_UPDATE);
+ rrdhost_flag_set(st->rrdhost, RRDHOST_FLAG_METADATA_UPDATE);
}
diff --git a/database/rrdset.c b/database/rrdset.c
index 6eb3c710..57f962cd 100644
--- a/database/rrdset.c
+++ b/database/rrdset.c
@@ -28,6 +28,8 @@ static inline void rrdset_index_del_name(RRDHOST *host, RRDSET *st) {
}
static inline RRDSET *rrdset_index_find_name(RRDHOST *host, const char *name) {
+ if (unlikely(!host->rrdset_root_index_name))
+ return NULL;
return dictionary_get(host->rrdset_root_index_name, name);
}
@@ -126,15 +128,15 @@ static void rrdset_insert_callback(const DICTIONARY_ITEM *item __maybe_unused, v
st->module_name = rrd_string_strdupz(ctr->module);
st->priority = ctr->priority;
- st->cache_dir = rrdset_cache_dir(host, chart_full_id);
st->entries = (ctr->memory_mode != RRD_MEMORY_MODE_DBENGINE) ? align_entries_to_pagesize(ctr->memory_mode, ctr->history_entries) : 5;
st->update_every = ctr->update_every;
st->rrd_memory_mode = ctr->memory_mode;
st->chart_type = ctr->chart_type;
- st->gap_when_lost_iterations_above = (int) (gap_when_lost_iterations_above + 2);
st->rrdhost = host;
+ netdata_spinlock_init(&st->data_collection_lock);
+
st->flags = RRDSET_FLAG_SYNC_CLOCK
| RRDSET_FLAG_INDEXED_ID
| RRDSET_FLAG_RECEIVER_REPLICATION_FINISHED
@@ -165,7 +167,7 @@ static void rrdset_insert_callback(const DICTIONARY_ITEM *item __maybe_unused, v
// chart variables - we need this for data collection to work (collector given chart variables) - not only health
rrdsetvar_index_init(st);
- if (host->health_enabled) {
+ if (host->health.health_enabled) {
st->rrdfamily = rrdfamily_add_and_acquire(host, rrdset_family(st));
st->rrdvars = rrdvariables_create();
rrddimvar_index_init(st);
@@ -178,6 +180,31 @@ static void rrdset_insert_callback(const DICTIONARY_ITEM *item __maybe_unused, v
st->red = NAN;
ctr->react_action = RRDSET_REACT_NEW;
+
+ ml_chart_new(st);
+}
+
+void rrdset_finalize_collection(RRDSET *st, bool dimensions_too) {
+ RRDHOST *host = st->rrdhost;
+
+ rrdset_flag_set(st, RRDSET_FLAG_COLLECTION_FINISHED);
+
+ if(dimensions_too) {
+ RRDDIM *rd;
+ rrddim_foreach_read(rd, st)
+ rrddim_finalize_collection_and_check_retention(rd);
+ rrddim_foreach_done(rd);
+ }
+
+ for(size_t tier = 0; tier < storage_tiers ; tier++) {
+ STORAGE_ENGINE *eng = st->rrdhost->db[tier].eng;
+ if(!eng) continue;
+
+ if(st->storage_metrics_groups[tier]) {
+ eng->api.collect_ops.metrics_group_release(host->db[tier].instance, st->storage_metrics_groups[tier]);
+ st->storage_metrics_groups[tier] = NULL;
+ }
+ }
}
// the destructor - the dictionary is write locked while this runs
@@ -187,15 +214,7 @@ static void rrdset_delete_callback(const DICTIONARY_ITEM *item __maybe_unused, v
rrdset_flag_clear(st, RRDSET_FLAG_INDEXED_ID);
- // cleanup storage engines
- {
- for(size_t tier = 0; tier < storage_tiers ; tier++) {
- STORAGE_ENGINE *eng = st->rrdhost->db[tier].eng;
- if(!eng) continue;
-
- eng->api.collect_ops.metrics_group_release(host->db[tier].instance, st->storage_metrics_groups[tier]);
- }
- }
+ rrdset_finalize_collection(st, false);
// remove it from the name index
rrdset_index_del_name(host, st);
@@ -232,6 +251,9 @@ static void rrdset_delete_callback(const DICTIONARY_ITEM *item __maybe_unused, v
// 7. destroy the chart labels
rrdlabels_destroy(st->rrdlabels); // destroy the labels, after letting the contexts know
+ // 8. destroy the ml handle
+ ml_chart_delete(st);
+
rrdset_memory_file_free(st); // remove files of db mode save and map
// ------------------------------------------------------------------------
@@ -282,7 +304,7 @@ static bool rrdset_conflict_callback(const DICTIONARY_ITEM *item __maybe_unused,
}
if (unlikely(st->update_every != ctr->update_every)) {
- rrdset_set_update_every(st, ctr->update_every);
+ rrdset_set_update_every_s(st, ctr->update_every);
ctr->react_action |= RRDSET_REACT_UPDATED;
}
@@ -356,33 +378,17 @@ static void rrdset_react_callback(const DICTIONARY_ITEM *item __maybe_unused, vo
RRDSET *st = rrdset;
RRDHOST *host = st->rrdhost;
- st->last_accessed_time = now_realtime_sec();
+ st->last_accessed_time_s = now_realtime_sec();
- if(host->health_enabled && (ctr->react_action & (RRDSET_REACT_NEW | RRDSET_REACT_CHART_ACTIVATED))) {
+ if(host->health.health_enabled && (ctr->react_action & (RRDSET_REACT_NEW | RRDSET_REACT_CHART_ACTIVATED))) {
rrdset_flag_set(st, RRDSET_FLAG_PENDING_HEALTH_INITIALIZATION);
rrdhost_flag_set(st->rrdhost, RRDHOST_FLAG_PENDING_HEALTH_INITIALIZATION);
}
if(ctr->react_action & (RRDSET_REACT_NEW | RRDSET_REACT_PLUGIN_UPDATED | RRDSET_REACT_MODULE_UPDATED)) {
if (ctr->react_action & RRDSET_REACT_NEW) {
- if(unlikely(rrdcontext_find_chart_uuid(st, &st->chart_uuid))) {
+ if(unlikely(rrdcontext_find_chart_uuid(st, &st->chart_uuid)))
uuid_generate(st->chart_uuid);
- bool found_in_sql = false; (void)found_in_sql;
-
-// bool found_in_sql = true;
-// if(unlikely(sql_find_chart_uuid(host, st, &st->chart_uuid))) {
-// uuid_generate(st->chart_uuid);
-// found_in_sql = false;
-// }
-
-#ifdef NETDATA_INTERNAL_CHECKS
- char uuid_str[UUID_STR_LEN];
- uuid_unparse_lower(st->chart_uuid, uuid_str);
- error_report("Chart UUID for host %s chart [%s] not found in context. It is now set to %s (%s)",
- string2str(host->hostname),
- string2str(st->name), uuid_str, found_in_sql ? "found in sqlite" : "newly generated");
-#endif
- }
}
rrdset_flag_set(st, RRDSET_FLAG_METADATA_UPDATE);
rrdhost_flag_set(st->rrdhost, RRDHOST_FLAG_METADATA_UPDATE);
@@ -393,7 +399,8 @@ static void rrdset_react_callback(const DICTIONARY_ITEM *item __maybe_unused, vo
void rrdset_index_init(RRDHOST *host) {
if(!host->rrdset_root_index) {
- host->rrdset_root_index = dictionary_create(DICT_OPTION_DONT_OVERWRITE_VALUE);
+ host->rrdset_root_index = dictionary_create_advanced(DICT_OPTION_DONT_OVERWRITE_VALUE | DICT_OPTION_FIXED_SIZE,
+ &dictionary_stats_category_rrdset_rrddim, sizeof(RRDSET));
dictionary_register_insert_callback(host->rrdset_root_index, rrdset_insert_callback, NULL);
dictionary_register_conflict_callback(host->rrdset_root_index, rrdset_conflict_callback, NULL);
@@ -402,8 +409,9 @@ void rrdset_index_init(RRDHOST *host) {
}
if(!host->rrdset_root_index_name) {
- host->rrdset_root_index_name = dictionary_create(
- DICT_OPTION_NAME_LINK_DONT_CLONE | DICT_OPTION_VALUE_LINK_DONT_CLONE | DICT_OPTION_DONT_OVERWRITE_VALUE);
+ host->rrdset_root_index_name = dictionary_create_advanced(
+ DICT_OPTION_NAME_LINK_DONT_CLONE | DICT_OPTION_VALUE_LINK_DONT_CLONE | DICT_OPTION_DONT_OVERWRITE_VALUE,
+ &dictionary_stats_category_rrdset_rrddim, 0);
dictionary_register_insert_callback(host->rrdset_root_index_name, rrdset_name_insert_callback, host);
dictionary_register_delete_callback(host->rrdset_root_index_name, rrdset_name_delete_callback, host);
@@ -431,6 +439,8 @@ static inline void rrdset_index_del(RRDHOST *host, RRDSET *st) {
static RRDSET *rrdset_index_find(RRDHOST *host, const char *id) {
// TODO - the name index should have an acquired dictionary item, not just a pointer to RRDSET
+ if (unlikely(!host->rrdset_root_index))
+ return NULL;
return dictionary_get(host->rrdset_root_index, id);
}
@@ -442,7 +452,7 @@ inline RRDSET *rrdset_find(RRDHOST *host, const char *id) {
RRDSET *st = rrdset_index_find(host, id);
if(st)
- st->last_accessed_time = now_realtime_sec();
+ st->last_accessed_time_s = now_realtime_sec();
return(st);
}
@@ -521,51 +531,106 @@ int rrdset_reset_name(RRDSET *st, const char *name) {
}
// get the timestamp of the last entry in the round-robin database
-time_t rrdset_last_entry_t(RRDSET *st) {
+time_t rrdset_last_entry_s(RRDSET *st) {
RRDDIM *rd;
- time_t last_entry_t = 0;
+ time_t last_entry_s = 0;
rrddim_foreach_read(rd, st) {
- time_t t = rrddim_last_entry_t(rd);
- if(t > last_entry_t) last_entry_t = t;
+ time_t t = rrddim_last_entry_s(rd);
+ if(t > last_entry_s) last_entry_s = t;
}
rrddim_foreach_done(rd);
- return last_entry_t;
+ return last_entry_s;
+}
+
+time_t rrdset_last_entry_s_of_tier(RRDSET *st, size_t tier) {
+ RRDDIM *rd;
+ time_t last_entry_s = 0;
+
+ rrddim_foreach_read(rd, st) {
+ time_t t = rrddim_last_entry_s_of_tier(rd, tier);
+ if(t > last_entry_s) last_entry_s = t;
+ }
+ rrddim_foreach_done(rd);
+
+ return last_entry_s;
}
// get the timestamp of first entry in the round-robin database
-time_t rrdset_first_entry_t(RRDSET *st) {
+time_t rrdset_first_entry_s(RRDSET *st) {
RRDDIM *rd;
- time_t first_entry_t = LONG_MAX;
+ time_t first_entry_s = LONG_MAX;
rrddim_foreach_read(rd, st) {
- time_t t = rrddim_first_entry_t(rd);
- if(t < first_entry_t)
- first_entry_t = t;
+ time_t t = rrddim_first_entry_s(rd);
+ if(t < first_entry_s)
+ first_entry_s = t;
}
rrddim_foreach_done(rd);
- if (unlikely(LONG_MAX == first_entry_t)) return 0;
- return first_entry_t;
+ if (unlikely(LONG_MAX == first_entry_s)) return 0;
+ return first_entry_s;
}
-time_t rrdset_first_entry_t_of_tier(RRDSET *st, size_t tier) {
+time_t rrdset_first_entry_s_of_tier(RRDSET *st, size_t tier) {
if(unlikely(tier > storage_tiers))
return 0;
RRDDIM *rd;
- time_t first_entry_t = LONG_MAX;
+ time_t first_entry_s = LONG_MAX;
rrddim_foreach_read(rd, st) {
- time_t t = rrddim_first_entry_t_of_tier(rd, tier);
- if(t && t < first_entry_t)
- first_entry_t = t;
+ time_t t = rrddim_first_entry_s_of_tier(rd, tier);
+ if(t && t < first_entry_s)
+ first_entry_s = t;
}
rrddim_foreach_done(rd);
- if (unlikely(LONG_MAX == first_entry_t)) return 0;
- return first_entry_t;
+ if (unlikely(LONG_MAX == first_entry_s)) return 0;
+ return first_entry_s;
+}
+
+void rrdset_get_retention_of_tier_for_collected_chart(RRDSET *st, time_t *first_time_s, time_t *last_time_s, time_t now_s, size_t tier) {
+ if(!now_s)
+ now_s = now_realtime_sec();
+
+ time_t db_first_entry_s = rrdset_first_entry_s_of_tier(st, tier);
+ time_t db_last_entry_s = st->last_updated.tv_sec; // we assume this is a collected RRDSET
+
+ if(unlikely(!db_last_entry_s)) {
+ db_last_entry_s = rrdset_last_entry_s_of_tier(st, tier);
+
+ if (unlikely(!db_last_entry_s)) {
+ // we assume this is a collected RRDSET
+ db_first_entry_s = 0;
+ db_last_entry_s = 0;
+ }
+ }
+
+ if(unlikely(db_last_entry_s > now_s)) {
+ internal_error(db_last_entry_s > now_s + 1,
+ "RRDSET: 'host:%s/chart:%s' latest db time %ld is in the future, adjusting it to now %ld",
+ rrdhost_hostname(st->rrdhost), rrdset_id(st),
+ db_last_entry_s, now_s);
+ db_last_entry_s = now_s;
+ }
+
+ if(unlikely(db_first_entry_s && db_last_entry_s && db_first_entry_s >= db_last_entry_s)) {
+ internal_error(db_first_entry_s > db_last_entry_s,
+ "RRDSET: 'host:%s/chart:%s' oldest db time %ld is bigger than latest db time %ld, adjusting it to (latest time %ld - update every %ld)",
+ rrdhost_hostname(st->rrdhost), rrdset_id(st),
+ db_first_entry_s, db_last_entry_s,
+ db_last_entry_s, (time_t)st->update_every);
+ db_first_entry_s = db_last_entry_s - st->update_every;
+ }
+
+ if(unlikely(!db_first_entry_s && db_last_entry_s))
+ // this can be the case on the first data collection of a chart
+ db_first_entry_s = db_last_entry_s - st->update_every;
+
+ *first_time_s = db_first_entry_s;
+ *last_time_s = db_last_entry_s;
}
inline void rrdset_is_obsolete(RRDSET *st) {
@@ -578,7 +643,7 @@ inline void rrdset_is_obsolete(RRDSET *st) {
rrdset_flag_set(st, RRDSET_FLAG_OBSOLETE);
rrdhost_flag_set(st->rrdhost, RRDHOST_FLAG_PENDING_OBSOLETE_CHARTS);
- st->last_accessed_time = now_realtime_sec();
+ st->last_accessed_time_s = now_realtime_sec();
rrdset_flag_clear(st, RRDSET_FLAG_UPSTREAM_EXPOSED);
@@ -592,7 +657,7 @@ inline void rrdset_is_obsolete(RRDSET *st) {
inline void rrdset_isnot_obsolete(RRDSET *st) {
if(unlikely((rrdset_flag_check(st, RRDSET_FLAG_OBSOLETE)))) {
rrdset_flag_clear(st, RRDSET_FLAG_OBSOLETE);
- st->last_accessed_time = now_realtime_sec();
+ st->last_accessed_time_s = now_realtime_sec();
rrdset_flag_clear(st, RRDSET_FLAG_UPSTREAM_EXPOSED);
@@ -673,8 +738,8 @@ void rrdset_reset(RRDSET *st) {
if(!rrddim_flag_check(rd, RRDDIM_FLAG_ARCHIVED)) {
for(size_t tier = 0; tier < storage_tiers ;tier++) {
- if(rd->tiers[tier])
- rd->tiers[tier]->collect_ops->flush(rd->tiers[tier]->db_collection_handle);
+ if(rd->tiers[tier].db_collection_handle)
+ rd->tiers[tier].collect_ops->flush(rd->tiers[tier].db_collection_handle);
}
}
}
@@ -768,7 +833,8 @@ void rrdset_delete_files(RRDSET *st) {
}
rrddim_foreach_done(rd);
- recursively_delete_dir(st->cache_dir, "left-over chart");
+ if(st->cache_dir)
+ recursively_delete_dir(st->cache_dir, "left-over chart");
}
void rrdset_delete_obsolete_dimensions(RRDSET *st) {
@@ -809,7 +875,7 @@ RRDSET *rrdset_create_custom(
, long history_entries
) {
if (host != localhost)
- host->senders_last_chart_command = now_realtime_sec();
+ host->child_last_chart_command = now_realtime_sec();
if(!type || !type[0])
fatal("Cannot create rrd stats without a type: id '%s', name '%s', family '%s', context '%s', title '%s', units '%s', plugin '%s', module '%s'."
@@ -920,15 +986,8 @@ void rrdset_timed_next(RRDSET *st, struct timeval now, usec_t duration_since_las
);
#endif
- st->last_collected_time.tv_sec = now.tv_sec - st->update_every;
- st->last_collected_time.tv_usec = now.tv_usec;
- last_collected_time_align(st);
+ duration_since_last_update = 0;
- st->last_updated.tv_sec = now.tv_sec - st->update_every;
- st->last_updated.tv_usec = now.tv_usec;
- last_updated_time_align(st);
-
- duration_since_last_update = st->update_every * USEC_PER_SEC;
#ifdef NETDATA_INTERNAL_CHECKS
if(!discard_reason) discard_reason = "COLLECTION TIME IN FUTURE";
#endif
@@ -941,6 +1000,7 @@ void rrdset_timed_next(RRDSET *st, struct timeval now, usec_t duration_since_las
#endif
duration_since_last_update = (usec_t)since_last_usec;
+
#ifdef NETDATA_INTERNAL_CHECKS
if(!discard_reason) discard_reason = "COLLECTION TIME TOO FAR IN THE PAST";
#endif
@@ -949,16 +1009,16 @@ void rrdset_timed_next(RRDSET *st, struct timeval now, usec_t duration_since_las
#ifdef NETDATA_INTERNAL_CHECKS
if(since_last_usec > 0 && (susec_t) duration_since_last_update < since_last_usec) {
static __thread susec_t min_delta = USEC_PER_SEC * 3600, permanent_min_delta = 0;
- static __thread time_t last_t = 0;
+ static __thread time_t last_time_s = 0;
// the first time initialize it so that it will make the check later
- if(last_t == 0) last_t = now.tv_sec + 60;
+ if(last_time_s == 0) last_time_s = now.tv_sec + 60;
susec_t delta = since_last_usec - (susec_t) duration_since_last_update;
if(delta < min_delta) min_delta = delta;
- if(now.tv_sec >= last_t + 60) {
- last_t = now.tv_sec;
+ if(now.tv_sec >= last_time_s + 60) {
+ last_time_s = now.tv_sec;
if(min_delta > permanent_min_delta) {
info("MINIMUM MICROSECONDS DELTA of thread %d increased from %lld to %lld (+%lld)", gettid(), permanent_min_delta, min_delta, min_delta - permanent_min_delta);
@@ -1029,7 +1089,7 @@ static inline usec_t rrdset_update_last_collected_time(RRDSET *st) {
return last_collect_ut;
}
-static inline usec_t rrdset_init_last_updated_time(RRDSET *st) {
+static inline void rrdset_init_last_updated_time(RRDSET *st) {
// copy the last collected time to last updated time
st->last_updated.tv_sec = st->last_collected_time.tv_sec;
st->last_updated.tv_usec = st->last_collected_time.tv_usec;
@@ -1038,31 +1098,27 @@ static inline usec_t rrdset_init_last_updated_time(RRDSET *st) {
st->last_updated.tv_sec -= st->update_every;
last_updated_time_align(st);
-
- usec_t last_updated_ut = st->last_updated.tv_sec * USEC_PER_SEC + st->last_updated.tv_usec;
-
- rrdset_debug(st, "initialized last updated time to %0.3" NETDATA_DOUBLE_MODIFIER, (NETDATA_DOUBLE)last_updated_ut / USEC_PER_SEC);
-
- return last_updated_ut;
}
static __thread size_t rrdset_done_statistics_points_stored_per_tier[RRD_STORAGE_TIERS];
-static inline time_t tier_next_point_time(RRDDIM *rd, struct rrddim_tier *t, time_t now) {
+static inline time_t tier_next_point_time_s(RRDDIM *rd, struct rrddim_tier *t, time_t now_s) {
time_t loop = (time_t)rd->update_every * (time_t)t->tier_grouping;
- return now + loop - ((now + loop) % loop);
+ return now_s + loop - ((now_s + loop) % loop);
}
void store_metric_at_tier(RRDDIM *rd, size_t tier, struct rrddim_tier *t, STORAGE_POINT sp, usec_t now_ut __maybe_unused) {
- if (unlikely(!t->next_point_time))
- t->next_point_time = tier_next_point_time(rd, t, sp.end_time);
+ if (unlikely(!t->next_point_end_time_s))
+ t->next_point_end_time_s = tier_next_point_time_s(rd, t, sp.end_time_s);
+
+ if(unlikely(sp.start_time_s >= t->next_point_end_time_s)) {
+ // flush the virtual point, it is done
- if(unlikely(sp.start_time > t->next_point_time)) {
if (likely(!storage_point_is_unset(t->virtual_point))) {
t->collect_ops->store_metric(
t->db_collection_handle,
- t->next_point_time * USEC_PER_SEC,
+ t->next_point_end_time_s * USEC_PER_SEC,
t->virtual_point.sum,
t->virtual_point.min,
t->virtual_point.max,
@@ -1073,7 +1129,7 @@ void store_metric_at_tier(RRDDIM *rd, size_t tier, struct rrddim_tier *t, STORAG
else {
t->collect_ops->store_metric(
t->db_collection_handle,
- t->next_point_time * USEC_PER_SEC,
+ t->next_point_end_time_s * USEC_PER_SEC,
NAN,
NAN,
NAN,
@@ -1083,18 +1139,18 @@ void store_metric_at_tier(RRDDIM *rd, size_t tier, struct rrddim_tier *t, STORAG
rrdset_done_statistics_points_stored_per_tier[tier]++;
t->virtual_point.count = 0; // make the point unset
- t->next_point_time = tier_next_point_time(rd, t, sp.end_time);
+ t->next_point_end_time_s = tier_next_point_time_s(rd, t, sp.end_time_s);
}
// merge the dates into our virtual point
- if (unlikely(sp.start_time < t->virtual_point.start_time))
- t->virtual_point.start_time = sp.start_time;
+ if (unlikely(sp.start_time_s < t->virtual_point.start_time_s))
+ t->virtual_point.start_time_s = sp.start_time_s;
- if (likely(sp.end_time > t->virtual_point.end_time))
- t->virtual_point.end_time = sp.end_time;
+ if (likely(sp.end_time_s > t->virtual_point.end_time_s))
+ t->virtual_point.end_time_s = sp.end_time_s;
// merge the values into our virtual point
- if (likely(!storage_point_is_empty(sp))) {
+ if (likely(!storage_point_is_gap(sp))) {
// we aggregate only non NULLs into higher tiers
if (likely(!storage_point_is_unset(t->virtual_point))) {
@@ -1143,14 +1199,14 @@ void rrddim_store_metric(RRDDIM *rd, usec_t point_end_time_ut, NETDATA_DOUBLE n,
#endif // NETDATA_LOG_COLLECTION_ERRORS
// store the metric on tier 0
- rd->tiers[0]->collect_ops->store_metric(rd->tiers[0]->db_collection_handle, point_end_time_ut, n, 0, 0, 1, 0, flags);
+ rd->tiers[0].collect_ops->store_metric(rd->tiers[0].db_collection_handle, point_end_time_ut, n, 0, 0, 1, 0, flags);
rrdset_done_statistics_points_stored_per_tier[0]++;
- time_t now = (time_t)(point_end_time_ut / USEC_PER_SEC);
+ time_t now_s = (time_t)(point_end_time_ut / USEC_PER_SEC);
STORAGE_POINT sp = {
- .start_time = now - rd->update_every,
- .end_time = now,
+ .start_time_s = now_s - rd->update_every,
+ .end_time_s = now_s,
.min = n,
.max = n,
.sum = n,
@@ -1160,14 +1216,14 @@ void rrddim_store_metric(RRDDIM *rd, usec_t point_end_time_ut, NETDATA_DOUBLE n,
};
for(size_t tier = 1; tier < storage_tiers ;tier++) {
- if(unlikely(!rd->tiers[tier])) continue;
+ if(unlikely(!rd->tiers[tier].db_metric_handle)) continue;
- struct rrddim_tier *t = rd->tiers[tier];
+ struct rrddim_tier *t = &rd->tiers[tier];
if(!rrddim_option_check(rd, RRDDIM_OPTION_BACKFILLED_HIGH_TIERS)) {
// we have not collected this tier before
// let's fill any gap that may exist
- rrdr_fill_tier_gap_from_smaller_tiers(rd, tier, now);
+ rrdr_fill_tier_gap_from_smaller_tiers(rd, tier, now_s);
rrddim_option_set(rd, RRDDIM_OPTION_BACKFILLED_HIGH_TIERS);
}
@@ -1188,12 +1244,16 @@ struct rda_item {
static __thread struct rda_item *thread_rda = NULL;
static __thread size_t thread_rda_entries = 0;
-struct rda_item *rrdset_thread_rda(size_t *dimensions) {
+struct rda_item *rrdset_thread_rda_get(size_t *dimensions) {
if(unlikely(!thread_rda || (*dimensions) > thread_rda_entries)) {
+ size_t old_mem = thread_rda_entries * sizeof(struct rda_item);
freez(thread_rda);
- thread_rda = mallocz((*dimensions) * sizeof(struct rda_item));
thread_rda_entries = *dimensions;
+ size_t new_mem = thread_rda_entries * sizeof(struct rda_item);
+ thread_rda = mallocz(new_mem);
+
+ __atomic_add_fetch(&netdata_buffers_statistics.rrdset_done_rda_size, new_mem - old_mem, __ATOMIC_RELAXED);
}
*dimensions = thread_rda_entries;
@@ -1201,6 +1261,8 @@ struct rda_item *rrdset_thread_rda(size_t *dimensions) {
}
void rrdset_thread_rda_free(void) {
+ __atomic_sub_fetch(&netdata_buffers_statistics.rrdset_done_rda_size, thread_rda_entries * sizeof(struct rda_item), __ATOMIC_RELAXED);
+
freez(thread_rda);
thread_rda = NULL;
thread_rda_entries = 0;
@@ -1253,6 +1315,8 @@ static inline size_t rrdset_done_interpolate(
last_ut = next_store_ut;
+ ml_chart_update_begin(st);
+
struct rda_item *rda;
size_t dim_id;
for(dim_id = 0, rda = rda_base ; dim_id < rda_slots ; ++dim_id, ++rda) {
@@ -1332,17 +1396,20 @@ static inline size_t rrdset_done_interpolate(
break;
}
+ time_t current_time_s = (time_t) (next_store_ut / USEC_PER_SEC);
+
if(unlikely(!store_this_entry)) {
- (void) ml_is_anomalous(rd, 0, false);
+ (void) ml_is_anomalous(rd, current_time_s, 0, false);
+
rrddim_store_metric(rd, next_store_ut, NAN, SN_FLAG_NONE);
rrdcontext_collected_rrddim(rd);
continue;
}
- if(likely(rd->updated && rd->collections_counter > 1 && iterations < st->gap_when_lost_iterations_above)) {
+ if(likely(rd->updated && rd->collections_counter > 1 && iterations < gap_when_lost_iterations_above)) {
uint32_t dim_storage_flags = storage_flags;
- if (ml_is_anomalous(rd, new_value, true)) {
+ if (ml_is_anomalous(rd, current_time_s, new_value, true)) {
// clear anomaly bit: 0 -> is anomalous, 1 -> not anomalous
dim_storage_flags &= ~((storage_number)SN_FLAG_NOT_ANOMALOUS);
}
@@ -1352,7 +1419,7 @@ static inline size_t rrdset_done_interpolate(
rd->last_stored_value = new_value;
}
else {
- (void) ml_is_anomalous(rd, 0, false);
+ (void) ml_is_anomalous(rd, current_time_s, 0, false);
rrdset_debug(st, "%s: STORE[%ld] = NON EXISTING ", rrddim_name(rd), current_entry);
@@ -1364,6 +1431,8 @@ static inline size_t rrdset_done_interpolate(
stored_entries++;
}
+ ml_chart_update_end(st);
+
// reset the storage flags for the next point, if any;
storage_flags = SN_DEFAULT_FLAGS;
@@ -1389,36 +1458,6 @@ static inline size_t rrdset_done_interpolate(
return stored_entries;
}
-static inline void rrdset_done_fill_the_gap(RRDSET *st) {
- usec_t update_every_ut = st->update_every * USEC_PER_SEC;
- usec_t now_collect_ut = st->last_collected_time.tv_sec * USEC_PER_SEC + st->last_collected_time.tv_usec;
-
- long c = 0, entries = st->entries;
- RRDDIM *rd;
- rrddim_foreach_read(rd, st) {
- usec_t next_store_ut = (st->last_updated.tv_sec + st->update_every) * USEC_PER_SEC;
- long current_entry = st->current_entry;
-
- for(c = 0; c < entries && next_store_ut <= now_collect_ut ; next_store_ut += update_every_ut, c++) {
- rd->db[current_entry] = pack_storage_number(NAN, SN_FLAG_NONE);
- current_entry = ((current_entry + 1) >= entries) ? 0 : current_entry + 1;
-
- rrdset_debug(st, "%s: STORE[%ld] = NON EXISTING (FILLED THE GAP)", rrddim_name(rd), current_entry);
- }
- }
- rrddim_foreach_done(rd);
-
- if(c > 0) {
- c--;
- st->last_updated.tv_sec += c * st->update_every;
-
- st->current_entry += c;
- st->counter += c;
- if(st->current_entry >= st->entries)
- st->current_entry -= st->entries;
- }
-}
-
void rrdset_done(RRDSET *st) {
struct timeval now;
@@ -1427,10 +1466,12 @@ void rrdset_done(RRDSET *st) {
}
void rrdset_timed_done(RRDSET *st, struct timeval now, bool pending_rrdset_next) {
- if(unlikely(netdata_exit)) return;
+ if(unlikely(!service_running(SERVICE_COLLECTORS))) return;
+
+ netdata_spinlock_lock(&st->data_collection_lock);
if (pending_rrdset_next)
- rrdset_next(st);
+ rrdset_timed_next(st, now, 0ULL);
debug(D_RRD_CALLS, "rrdset_done() for chart '%s'", rrdset_name(st));
@@ -1447,9 +1488,13 @@ void rrdset_timed_done(RRDSET *st, struct timeval now, bool pending_rrdset_next)
next_store_ut = 0, // the timestamp in microseconds, of the next entry to store in the db
update_every_ut = st->update_every * USEC_PER_SEC; // st->update_every in microseconds
+ RRDSET_FLAGS rrdset_flags = rrdset_flag_check(st, ~0);
+ if(unlikely(rrdset_flags & RRDSET_FLAG_COLLECTION_FINISHED))
+ return;
+
netdata_thread_disable_cancelability();
- if(unlikely(rrdset_flag_check(st, RRDSET_FLAG_OBSOLETE))) {
+ if (unlikely(rrdset_flags & RRDSET_FLAG_OBSOLETE)) {
error("Chart '%s' has the OBSOLETE flag set, but it is collected.", rrdset_id(st));
rrdset_isnot_obsolete(st);
}
@@ -1519,29 +1564,6 @@ void rrdset_timed_done(RRDSET *st, struct timeval now, bool pending_rrdset_next)
first_entry = 1;
}
-#ifdef ENABLE_DBENGINE
- // check if we will re-write the entire page
- if(unlikely(st->rrd_memory_mode == RRD_MEMORY_MODE_DBENGINE &&
- dt_usec(&st->last_collected_time, &st->last_updated) > (RRDENG_BLOCK_SIZE / sizeof(storage_number)) * update_every_ut)) {
- info(
- "'%s': too old data (last updated at %" PRId64 ".%" PRId64 ", last collected at %" PRId64 ".%" PRId64 "). "
- "Resetting it. Will not store the next entry.",
- rrdset_id(st),
- (int64_t)st->last_updated.tv_sec,
- (int64_t)st->last_updated.tv_usec,
- (int64_t)st->last_collected_time.tv_sec,
- (int64_t)st->last_collected_time.tv_usec);
- rrdset_reset(st);
- rrdset_init_last_updated_time(st);
-
- st->usec_since_last_update = update_every_ut;
-
- // the first entry should not be stored
- store_this_entry = 0;
- first_entry = 1;
- }
-#endif
-
// these are the 3 variables that will help us in interpolation
// last_stored_ut = the last time we added a value to the storage
// now_collect_ut = the time the current value has been collected
@@ -1551,23 +1573,13 @@ void rrdset_timed_done(RRDSET *st, struct timeval now, bool pending_rrdset_next)
next_store_ut = (st->last_updated.tv_sec + st->update_every) * USEC_PER_SEC;
if(unlikely(!st->counter_done)) {
- // if we have not collected metrics this session (st->counter_done == 0)
- // and we have collected metrics for this chart in the past (st->counter != 0)
- // fill the gap (the chart has been just loaded from disk)
- if(unlikely(st->counter) && st->rrd_memory_mode != RRD_MEMORY_MODE_DBENGINE) {
- // TODO this should be inside the storage engine
- rrdset_done_fill_the_gap(st);
- last_stored_ut = st->last_updated.tv_sec * USEC_PER_SEC + st->last_updated.tv_usec;
- next_store_ut = (st->last_updated.tv_sec + st->update_every) * USEC_PER_SEC;
- }
- if (st->rrd_memory_mode == RRD_MEMORY_MODE_DBENGINE) {
- // set a fake last_updated to jump to current time
- rrdset_init_last_updated_time(st);
- last_stored_ut = st->last_updated.tv_sec * USEC_PER_SEC + st->last_updated.tv_usec;
- next_store_ut = (st->last_updated.tv_sec + st->update_every) * USEC_PER_SEC;
- }
+ // set a fake last_updated to jump to current time
+ rrdset_init_last_updated_time(st);
+
+ last_stored_ut = st->last_updated.tv_sec * USEC_PER_SEC + st->last_updated.tv_usec;
+ next_store_ut = (st->last_updated.tv_sec + st->update_every) * USEC_PER_SEC;
- if(unlikely(rrdset_flag_check(st, RRDSET_FLAG_STORE_FIRST))) {
+ if(unlikely(rrdset_flags & RRDSET_FLAG_STORE_FIRST)) {
store_this_entry = 1;
last_collect_ut = next_store_ut - update_every_ut;
@@ -1589,7 +1601,7 @@ after_first_database_work:
uint32_t has_reset_value = 0;
size_t rda_slots = dictionary_entries(st->rrddim_root_index);
- struct rda_item *rda_base = rrdset_thread_rda(&rda_slots);
+ struct rda_item *rda_base = rrdset_thread_rda_get(&rda_slots);
size_t dim_id;
size_t dimensions = 0;
@@ -1915,6 +1927,8 @@ after_second_database_work:
);
}
+ netdata_spinlock_unlock(&st->data_collection_lock);
+
// ALL DONE ABOUT THE DATA UPDATE
// --------------------------------------------------------------------
@@ -1946,29 +1960,29 @@ after_second_database_work:
store_metric_collection_completed();
}
-time_t rrdset_set_update_every(RRDSET *st, time_t update_every) {
+time_t rrdset_set_update_every_s(RRDSET *st, time_t update_every_s) {
internal_error(true, "RRDSET '%s' switching update every from %d to %d",
- rrdset_id(st), (int)st->update_every, (int)update_every);
+ rrdset_id(st), (int)st->update_every, (int)update_every_s);
- time_t prev_update_every = st->update_every;
- st->update_every = update_every;
+ time_t prev_update_every_s = st->update_every;
+ st->update_every = update_every_s;
// switch update every to the storage engine
RRDDIM *rd;
rrddim_foreach_read(rd, st) {
for (size_t tier = 0; tier < storage_tiers; tier++) {
- if (rd->tiers[tier] && rd->tiers[tier]->db_collection_handle)
- rd->tiers[tier]->collect_ops->change_collection_frequency(rd->tiers[tier]->db_collection_handle, (int)(st->rrdhost->db[tier].tier_grouping * st->update_every));
+ if (rd->tiers[tier].db_collection_handle)
+ rd->tiers[tier].collect_ops->change_collection_frequency(rd->tiers[tier].db_collection_handle, (int)(st->rrdhost->db[tier].tier_grouping * st->update_every));
}
- assert(rd->update_every == prev_update_every &&
+ assert(rd->update_every == prev_update_every_s &&
"chart's update every differs from the update every of its dimensions");
rd->update_every = st->update_every;
}
rrddim_foreach_done(rd);
- return prev_update_every;
+ return prev_update_every_s;
}
// ----------------------------------------------------------------------------
@@ -2016,8 +2030,8 @@ struct rrdset_map_save_v019 {
size_t counter; // NEEDS TO BE UPDATED - maintained on load
size_t counter_done; // ignored
union { //
- time_t last_accessed_time; // ignored
- time_t last_entry_t; // ignored
+ time_t last_accessed_time_s; // ignored
+ time_t last_entry_s; // ignored
}; //
time_t upstream_resync_time; // ignored
void *plugin_name; // ignored
@@ -2064,6 +2078,13 @@ const char *rrdset_cache_filename(RRDSET *st) {
return st_on_file->cache_filename;
}
+const char *rrdset_cache_dir(RRDSET *st) {
+ if(!st->cache_dir)
+ st->cache_dir = rrdhost_cache_dir_for_rrdset_alloc(st->rrdhost, rrdset_id(st));
+
+ return st->cache_dir;
+}
+
void rrdset_memory_file_free(RRDSET *st) {
if(!st->st_on_file) return;
@@ -2071,6 +2092,7 @@ void rrdset_memory_file_free(RRDSET *st) {
rrdset_memory_file_update(st);
struct rrdset_map_save_v019 *st_on_file = st->st_on_file;
+ __atomic_sub_fetch(&rrddim_db_memory_size, st_on_file->memsize, __ATOMIC_RELAXED);
netdata_munmap(st_on_file, st_on_file->memsize);
// remove the pointers from the RRDDIM
@@ -2093,17 +2115,15 @@ bool rrdset_memory_load_or_create_map_save(RRDSET *st, RRD_MEMORY_MODE memory_mo
return false;
char fullfilename[FILENAME_MAX + 1];
- snprintfz(fullfilename, FILENAME_MAX, "%s/main.db", st->cache_dir);
+ snprintfz(fullfilename, FILENAME_MAX, "%s/main.db", rrdset_cache_dir(st));
unsigned long size = sizeof(struct rrdset_map_save_v019);
struct rrdset_map_save_v019 *st_on_file = (struct rrdset_map_save_v019 *)netdata_mmap(
- fullfilename, size,
- ((memory_mode == RRD_MEMORY_MODE_MAP) ? MAP_SHARED : MAP_PRIVATE),
- 0);
+ fullfilename, size, ((memory_mode == RRD_MEMORY_MODE_MAP) ? MAP_SHARED : MAP_PRIVATE), 0, false, NULL);
if(!st_on_file) return false;
- time_t now = now_realtime_sec();
+ time_t now_s = now_realtime_sec();
st_on_file->magic[sizeof(RRDSET_MAGIC_V019)] = '\0';
if(strcmp(st_on_file->magic, RRDSET_MAGIC_V019) != 0) {
@@ -2122,13 +2142,13 @@ bool rrdset_memory_load_or_create_map_save(RRDSET *st, RRD_MEMORY_MODE memory_mo
error("File '%s' does not have the desired granularity. Clearing it.", fullfilename);
memset(st_on_file, 0, size);
}
- else if((now - st_on_file->last_updated.tv_sec) > st->update_every * st->entries) {
+ else if((now_s - st_on_file->last_updated.tv_sec) > st->update_every * st->entries) {
info("File '%s' is too old. Clearing it.", fullfilename);
memset(st_on_file, 0, size);
}
- else if(st_on_file->last_updated.tv_sec > now + st->update_every) {
- error("File '%s' refers to the future by %zd secs. Resetting it to now.", fullfilename, (ssize_t)(st_on_file->last_updated.tv_sec - now));
- st_on_file->last_updated.tv_sec = now;
+ else if(st_on_file->last_updated.tv_sec > now_s + st->update_every) {
+ error("File '%s' refers to the future by %zd secs. Resetting it to now.", fullfilename, (ssize_t)(st_on_file->last_updated.tv_sec - now_s));
+ st_on_file->last_updated.tv_sec = now_s;
}
if(st_on_file->current_entry >= st_on_file->entries)
@@ -2169,5 +2189,6 @@ bool rrdset_memory_load_or_create_map_save(RRDSET *st, RRD_MEMORY_MODE memory_mo
// copy the useful values back to st_on_file
rrdset_memory_file_update(st);
+ __atomic_add_fetch(&rrddim_db_memory_size, st_on_file->memsize, __ATOMIC_RELAXED);
return true;
}
diff --git a/database/rrdsetvar.c b/database/rrdsetvar.c
index 22cf8a1f..15377ddb 100644
--- a/database/rrdsetvar.c
+++ b/database/rrdsetvar.c
@@ -43,7 +43,7 @@ static inline void rrdsetvar_free_rrdvars_unsafe(RRDSET *st, RRDSETVAR *rs) {
// ------------------------------------------------------------------------
// HOST
- if(host->rrdvars && host->health_enabled) {
+ if(host->rrdvars && host->health.health_enabled) {
rrdvar_release_and_del(host->rrdvars, rs->rrdvar_host_chart_id);
rs->rrdvar_host_chart_id = NULL;
@@ -93,7 +93,7 @@ static inline void rrdsetvar_update_rrdvars_unsafe(RRDSET *st, RRDSETVAR *rs) {
// ------------------------------------------------------------------------
// HOST
- if(host->rrdvars && host->health_enabled) {
+ if(host->rrdvars && host->health.health_enabled) {
rs->rrdvar_host_chart_id = rrdvar_add_and_acquire("host", host->rrdvars, key_chart_id, rs->type, options, rs->value);
rs->rrdvar_host_chart_name = rrdvar_add_and_acquire("host", host->rrdvars, key_chart_name, rs->type, options, rs->value);
}
@@ -189,7 +189,8 @@ static void rrdsetvar_delete_callback(const DICTIONARY_ITEM *item __maybe_unused
void rrdsetvar_index_init(RRDSET *st) {
if(!st->rrdsetvar_root_index) {
- st->rrdsetvar_root_index = dictionary_create(DICT_OPTION_DONT_OVERWRITE_VALUE);
+ st->rrdsetvar_root_index = dictionary_create_advanced(DICT_OPTION_DONT_OVERWRITE_VALUE | DICT_OPTION_FIXED_SIZE,
+ &dictionary_stats_category_rrdhealth, sizeof(RRDSETVAR));
dictionary_register_insert_callback(st->rrdsetvar_root_index, rrdsetvar_insert_callback, NULL);
dictionary_register_conflict_callback(st->rrdsetvar_root_index, rrdsetvar_conflict_callback, NULL);
diff --git a/database/rrdvar.c b/database/rrdvar.c
index 28be4f6a..72decbd4 100644
--- a/database/rrdvar.c
+++ b/database/rrdvar.c
@@ -84,7 +84,8 @@ static void rrdvar_delete_callback(const DICTIONARY_ITEM *item __maybe_unused, v
}
DICTIONARY *rrdvariables_create(void) {
- DICTIONARY *dict = dictionary_create(DICT_OPTION_DONT_OVERWRITE_VALUE);
+ DICTIONARY *dict = dictionary_create_advanced(DICT_OPTION_DONT_OVERWRITE_VALUE | DICT_OPTION_FIXED_SIZE,
+ &dictionary_stats_category_rrdhealth, sizeof(RRDVAR));
dictionary_register_insert_callback(dict, rrdvar_insert_callback, NULL);
dictionary_register_delete_callback(dict, rrdvar_delete_callback, NULL);
diff --git a/database/sqlite/sqlite3.c b/database/sqlite/sqlite3.c
index 296de3e7..d5fb13d0 100644
--- a/database/sqlite/sqlite3.c
+++ b/database/sqlite/sqlite3.c
@@ -1,6 +1,6 @@
/******************************************************************************
** This file is an amalgamation of many separate C source files from SQLite
-** version 3.38.5. By combining all the individual C code files into this
+** version 3.40.1. By combining all the individual C code files into this
** single large file, the entire code can be compiled as a single translation
** unit. This allows many compilers to do optimizations that would not be
** possible if the files were compiled separately. Performance improvements
@@ -27,7 +27,6 @@
#define SQLITE_ENABLE_UPDATE_DELETE_LIMIT 1
#define SQLITE_OMIT_LOAD_EXTENSION 1
#define SQLITE_ENABLE_DBSTAT_VTAB 1
-
/************** Begin file sqliteInt.h ***************************************/
/*
** 2001 September 15
@@ -458,9 +457,9 @@ extern "C" {
** [sqlite3_libversion_number()], [sqlite3_sourceid()],
** [sqlite_version()] and [sqlite_source_id()].
*/
-#define SQLITE_VERSION "3.38.5"
-#define SQLITE_VERSION_NUMBER 3038005
-#define SQLITE_SOURCE_ID "2022-05-06 15:25:27 78d9c993d404cdfaa7fdd2973fa1052e3da9f66215cff9c5540ebe55c407d9fe"
+#define SQLITE_VERSION "3.40.1"
+#define SQLITE_VERSION_NUMBER 3040001
+#define SQLITE_SOURCE_ID "2022-12-28 14:03:47 df5c253c0b3dd24916e4ec7cf77d3db5294cc9fd45ae7b9c5e82ad8197f38a24"
/*
** CAPI3REF: Run-Time Library Version Numbers
@@ -982,13 +981,17 @@ SQLITE_API int sqlite3_exec(
**
** SQLite uses one of these integer values as the second
** argument to calls it makes to the xLock() and xUnlock() methods
-** of an [sqlite3_io_methods] object.
+** of an [sqlite3_io_methods] object. These values are ordered from
+** lest restrictive to most restrictive.
+**
+** The argument to xLock() is always SHARED or higher. The argument to
+** xUnlock is either SHARED or NONE.
*/
-#define SQLITE_LOCK_NONE 0
-#define SQLITE_LOCK_SHARED 1
-#define SQLITE_LOCK_RESERVED 2
-#define SQLITE_LOCK_PENDING 3
-#define SQLITE_LOCK_EXCLUSIVE 4
+#define SQLITE_LOCK_NONE 0 /* xUnlock() only */
+#define SQLITE_LOCK_SHARED 1 /* xLock() or xUnlock() */
+#define SQLITE_LOCK_RESERVED 2 /* xLock() only */
+#define SQLITE_LOCK_PENDING 3 /* xLock() only */
+#define SQLITE_LOCK_EXCLUSIVE 4 /* xLock() only */
/*
** CAPI3REF: Synchronization Type Flags
@@ -1066,7 +1069,14 @@ struct sqlite3_file {
** <li> [SQLITE_LOCK_PENDING], or
** <li> [SQLITE_LOCK_EXCLUSIVE].
** </ul>
-** xLock() increases the lock. xUnlock() decreases the lock.
+** xLock() upgrades the database file lock. In other words, xLock() moves the
+** database file lock in the direction NONE toward EXCLUSIVE. The argument to
+** xLock() is always on of SHARED, RESERVED, PENDING, or EXCLUSIVE, never
+** SQLITE_LOCK_NONE. If the database file lock is already at or above the
+** requested lock, then the call to xLock() is a no-op.
+** xUnlock() downgrades the database file lock to either SHARED or NONE.
+* If the lock is already at or below the requested lock state, then the call
+** to xUnlock() is a no-op.
** The xCheckReservedLock() method checks whether any database connection,
** either in this process or in some other process, is holding a RESERVED,
** PENDING, or EXCLUSIVE lock on the file. It returns true
@@ -1171,9 +1181,8 @@ struct sqlite3_io_methods {
** opcode causes the xFileControl method to write the current state of
** the lock (one of [SQLITE_LOCK_NONE], [SQLITE_LOCK_SHARED],
** [SQLITE_LOCK_RESERVED], [SQLITE_LOCK_PENDING], or [SQLITE_LOCK_EXCLUSIVE])
-** into an integer that the pArg argument points to. This capability
-** is used during testing and is only available when the SQLITE_TEST
-** compile-time option is used.
+** into an integer that the pArg argument points to.
+** This capability is only available if SQLite is compiled with [SQLITE_DEBUG].
**
** <li>[[SQLITE_FCNTL_SIZE_HINT]]
** The [SQLITE_FCNTL_SIZE_HINT] opcode is used by SQLite to give the VFS
@@ -1494,6 +1503,12 @@ struct sqlite3_io_methods {
**
** <li>[[SQLITE_FCNTL_CKSM_FILE]]
** Used by the cksmvfs VFS module only.
+**
+** <li>[[SQLITE_FCNTL_RESET_CACHE]]
+** If there is currently no transaction open on the database, and the
+** database is not a temp db, then this file-control purges the contents
+** of the in-memory page cache. If there is an open transaction, or if
+** the db is a temp-db, it is a no-op, not an error.
** </ul>
*/
#define SQLITE_FCNTL_LOCKSTATE 1
@@ -1536,6 +1551,7 @@ struct sqlite3_io_methods {
#define SQLITE_FCNTL_CKPT_START 39
#define SQLITE_FCNTL_EXTERNAL_READER 40
#define SQLITE_FCNTL_CKSM_FILE 41
+#define SQLITE_FCNTL_RESET_CACHE 42
/* deprecated names */
#define SQLITE_GET_LOCKPROXYFILE SQLITE_FCNTL_GET_LOCKPROXYFILE
@@ -1566,6 +1582,26 @@ typedef struct sqlite3_mutex sqlite3_mutex;
typedef struct sqlite3_api_routines sqlite3_api_routines;
/*
+** CAPI3REF: File Name
+**
+** Type [sqlite3_filename] is used by SQLite to pass filenames to the
+** xOpen method of a [VFS]. It may be cast to (const char*) and treated
+** as a normal, nul-terminated, UTF-8 buffer containing the filename, but
+** may also be passed to special APIs such as:
+**
+** <ul>
+** <li> sqlite3_filename_database()
+** <li> sqlite3_filename_journal()
+** <li> sqlite3_filename_wal()
+** <li> sqlite3_uri_parameter()
+** <li> sqlite3_uri_boolean()
+** <li> sqlite3_uri_int64()
+** <li> sqlite3_uri_key()
+** </ul>
+*/
+typedef const char *sqlite3_filename;
+
+/*
** CAPI3REF: OS Interface Object
**
** An instance of the sqlite3_vfs object defines the interface between
@@ -1743,7 +1779,7 @@ struct sqlite3_vfs {
sqlite3_vfs *pNext; /* Next registered VFS */
const char *zName; /* Name of this virtual file system */
void *pAppData; /* Pointer to application-specific data */
- int (*xOpen)(sqlite3_vfs*, const char *zName, sqlite3_file*,
+ int (*xOpen)(sqlite3_vfs*, sqlite3_filename zName, sqlite3_file*,
int flags, int *pOutFlags);
int (*xDelete)(sqlite3_vfs*, const char *zName, int syncDir);
int (*xAccess)(sqlite3_vfs*, const char *zName, int flags, int *pResOut);
@@ -2621,6 +2657,7 @@ struct sqlite3_mem_methods {
** <ul>
** <li> The [PRAGMA writable_schema=ON] statement.
** <li> The [PRAGMA journal_mode=OFF] statement.
+** <li> The [PRAGMA schema_version=N] statement.
** <li> Writes to the [sqlite_dbpage] virtual table.
** <li> Direct writes to [shadow tables].
** </ul>
@@ -3736,6 +3773,9 @@ SQLITE_API void sqlite3_progress_handler(sqlite3*, int, int(*)(void*), void*);
** <dd>The database is opened [shared cache] enabled, overriding
** the default shared cache setting provided by
** [sqlite3_enable_shared_cache()].)^
+** The [use of shared cache mode is discouraged] and hence shared cache
+** capabilities may be omitted from many builds of SQLite. In such cases,
+** this option is a no-op.
**
** ^(<dt>[SQLITE_OPEN_PRIVATECACHE]</dt>
** <dd>The database is opened [shared cache] disabled, overriding
@@ -3751,7 +3791,7 @@ SQLITE_API void sqlite3_progress_handler(sqlite3*, int, int(*)(void*), void*);
** to return an extended result code.</dd>
**
** [[OPEN_NOFOLLOW]] ^(<dt>[SQLITE_OPEN_NOFOLLOW]</dt>
-** <dd>The database filename is not allowed to be a symbolic link</dd>
+** <dd>The database filename is not allowed to contain a symbolic link</dd>
** </dl>)^
**
** If the 3rd parameter to sqlite3_open_v2() is not one of the
@@ -4010,10 +4050,10 @@ SQLITE_API int sqlite3_open_v2(
**
** See the [URI filename] documentation for additional information.
*/
-SQLITE_API const char *sqlite3_uri_parameter(const char *zFilename, const char *zParam);
-SQLITE_API int sqlite3_uri_boolean(const char *zFile, const char *zParam, int bDefault);
-SQLITE_API sqlite3_int64 sqlite3_uri_int64(const char*, const char*, sqlite3_int64);
-SQLITE_API const char *sqlite3_uri_key(const char *zFilename, int N);
+SQLITE_API const char *sqlite3_uri_parameter(sqlite3_filename z, const char *zParam);
+SQLITE_API int sqlite3_uri_boolean(sqlite3_filename z, const char *zParam, int bDefault);
+SQLITE_API sqlite3_int64 sqlite3_uri_int64(sqlite3_filename, const char*, sqlite3_int64);
+SQLITE_API const char *sqlite3_uri_key(sqlite3_filename z, int N);
/*
** CAPI3REF: Translate filenames
@@ -4042,9 +4082,9 @@ SQLITE_API const char *sqlite3_uri_key(const char *zFilename, int N);
** return value from [sqlite3_db_filename()], then the result is
** undefined and is likely a memory access violation.
*/
-SQLITE_API const char *sqlite3_filename_database(const char*);
-SQLITE_API const char *sqlite3_filename_journal(const char*);
-SQLITE_API const char *sqlite3_filename_wal(const char*);
+SQLITE_API const char *sqlite3_filename_database(sqlite3_filename);
+SQLITE_API const char *sqlite3_filename_journal(sqlite3_filename);
+SQLITE_API const char *sqlite3_filename_wal(sqlite3_filename);
/*
** CAPI3REF: Database File Corresponding To A Journal
@@ -4110,14 +4150,14 @@ SQLITE_API sqlite3_file *sqlite3_database_file_object(const char*);
** then the corresponding [sqlite3_module.xClose() method should also be
** invoked prior to calling sqlite3_free_filename(Y).
*/
-SQLITE_API char *sqlite3_create_filename(
+SQLITE_API sqlite3_filename sqlite3_create_filename(
const char *zDatabase,
const char *zJournal,
const char *zWal,
int nParam,
const char **azParam
);
-SQLITE_API void sqlite3_free_filename(char*);
+SQLITE_API void sqlite3_free_filename(sqlite3_filename);
/*
** CAPI3REF: Error Codes And Messages
@@ -5820,6 +5860,16 @@ SQLITE_API SQLITE_DEPRECATED int sqlite3_memory_alarm(void(*)(void*,sqlite3_int6
** then the conversion is performed. Otherwise no conversion occurs.
** The [SQLITE_INTEGER | datatype] after conversion is returned.)^
**
+** ^(The sqlite3_value_encoding(X) interface returns one of [SQLITE_UTF8],
+** [SQLITE_UTF16BE], or [SQLITE_UTF16LE] according to the current encoding
+** of the value X, assuming that X has type TEXT.)^ If sqlite3_value_type(X)
+** returns something other than SQLITE_TEXT, then the return value from
+** sqlite3_value_encoding(X) is meaningless. ^Calls to
+** sqlite3_value_text(X), sqlite3_value_text16(X), sqlite3_value_text16be(X),
+** sqlite3_value_text16le(X), sqlite3_value_bytes(X), or
+** sqlite3_value_bytes16(X) might change the encoding of the value X and
+** thus change the return from subsequent calls to sqlite3_value_encoding(X).
+**
** ^Within the [xUpdate] method of a [virtual table], the
** sqlite3_value_nochange(X) interface returns true if and only if
** the column corresponding to X is unchanged by the UPDATE operation
@@ -5884,6 +5934,7 @@ SQLITE_API int sqlite3_value_type(sqlite3_value*);
SQLITE_API int sqlite3_value_numeric_type(sqlite3_value*);
SQLITE_API int sqlite3_value_nochange(sqlite3_value*);
SQLITE_API int sqlite3_value_frombind(sqlite3_value*);
+SQLITE_API int sqlite3_value_encoding(sqlite3_value*);
/*
** CAPI3REF: Finding The Subtype Of SQL Values
@@ -5905,7 +5956,8 @@ SQLITE_API unsigned int sqlite3_value_subtype(sqlite3_value*);
** object D and returns a pointer to that copy. ^The [sqlite3_value] returned
** is a [protected sqlite3_value] object even if the input is not.
** ^The sqlite3_value_dup(V) interface returns NULL if V is NULL or if a
-** memory allocation fails.
+** memory allocation fails. ^If V is a [pointer value], then the result
+** of sqlite3_value_dup(V) is a NULL value.
**
** ^The sqlite3_value_free(V) interface frees an [sqlite3_value] object
** previously obtained from [sqlite3_value_dup()]. ^If V is a NULL pointer
@@ -5936,7 +5988,7 @@ SQLITE_API void sqlite3_value_free(sqlite3_value*);
**
** ^The sqlite3_aggregate_context(C,N) routine returns a NULL pointer
** when first called if N is less than or equal to zero or if a memory
-** allocate error occurs.
+** allocation error occurs.
**
** ^(The amount of space allocated by sqlite3_aggregate_context(C,N) is
** determined by the N parameter on first successful call. Changing the
@@ -6141,9 +6193,10 @@ typedef void (*sqlite3_destructor_type)(void*);
** of [SQLITE_UTF8], [SQLITE_UTF16], [SQLITE_UTF16BE], or [SQLITE_UTF16LE].
** ^SQLite takes the text result from the application from
** the 2nd parameter of the sqlite3_result_text* interfaces.
-** ^If the 3rd parameter to the sqlite3_result_text* interfaces
-** is negative, then SQLite takes result text from the 2nd parameter
-** through the first zero character.
+** ^If the 3rd parameter to any of the sqlite3_result_text* interfaces
+** other than sqlite3_result_text64() is negative, then SQLite computes
+** the string length itself by searching the 2nd parameter for the first
+** zero character.
** ^If the 3rd parameter to the sqlite3_result_text* interfaces
** is non-negative, then as many bytes (not characters) of the text
** pointed to by the 2nd parameter are taken as the application-defined
@@ -6588,6 +6641,28 @@ SQLITE_API int sqlite3_get_autocommit(sqlite3*);
SQLITE_API sqlite3 *sqlite3_db_handle(sqlite3_stmt*);
/*
+** CAPI3REF: Return The Schema Name For A Database Connection
+** METHOD: sqlite3
+**
+** ^The sqlite3_db_name(D,N) interface returns a pointer to the schema name
+** for the N-th database on database connection D, or a NULL pointer of N is
+** out of range. An N value of 0 means the main database file. An N of 1 is
+** the "temp" schema. Larger values of N correspond to various ATTACH-ed
+** databases.
+**
+** Space to hold the string that is returned by sqlite3_db_name() is managed
+** by SQLite itself. The string might be deallocated by any operation that
+** changes the schema, including [ATTACH] or [DETACH] or calls to
+** [sqlite3_serialize()] or [sqlite3_deserialize()], even operations that
+** occur on a different thread. Applications that need to
+** remember the string long-term should make their own copy. Applications that
+** are accessing the same database connection simultaneously on multiple
+** threads should mutex-protect calls to this API and should make their own
+** private copy of the result prior to releasing the mutex.
+*/
+SQLITE_API const char *sqlite3_db_name(sqlite3 *db, int N);
+
+/*
** CAPI3REF: Return The Filename For A Database Connection
** METHOD: sqlite3
**
@@ -6617,7 +6692,7 @@ SQLITE_API sqlite3 *sqlite3_db_handle(sqlite3_stmt*);
** <li> [sqlite3_filename_wal()]
** </ul>
*/
-SQLITE_API const char *sqlite3_db_filename(sqlite3 *db, const char *zDbName);
+SQLITE_API sqlite3_filename sqlite3_db_filename(sqlite3 *db, const char *zDbName);
/*
** CAPI3REF: Determine if a database is read-only
@@ -6754,7 +6829,7 @@ SQLITE_API void *sqlite3_rollback_hook(sqlite3*, void(*)(void *), void*);
** function C that is invoked prior to each autovacuum of the database
** file. ^The callback is passed a copy of the generic data pointer (P),
** the schema-name of the attached database that is being autovacuumed,
-** the the size of the database file in pages, the number of free pages,
+** the size of the database file in pages, the number of free pages,
** and the number of bytes per page, respectively. The callback should
** return the number of free pages that should be removed by the
** autovacuum. ^If the callback returns zero, then no autovacuum happens.
@@ -6875,6 +6950,11 @@ SQLITE_API void *sqlite3_update_hook(
** to the same database. Sharing is enabled if the argument is true
** and disabled if the argument is false.)^
**
+** This interface is omitted if SQLite is compiled with
+** [-DSQLITE_OMIT_SHARED_CACHE]. The [-DSQLITE_OMIT_SHARED_CACHE]
+** compile-time option is recommended because the
+** [use of shared cache mode is discouraged].
+**
** ^Cache sharing is enabled and disabled for an entire process.
** This is a change as of SQLite [version 3.5.0] ([dateof:3.5.0]).
** In prior versions of SQLite,
@@ -6973,7 +7053,7 @@ SQLITE_API int sqlite3_db_release_memory(sqlite3*);
** ^The soft heap limit may not be greater than the hard heap limit.
** ^If the hard heap limit is enabled and if sqlite3_soft_heap_limit(N)
** is invoked with a value of N that is greater than the hard heap limit,
-** the the soft heap limit is set to the value of the hard heap limit.
+** the soft heap limit is set to the value of the hard heap limit.
** ^The soft heap limit is automatically enabled whenever the hard heap
** limit is enabled. ^When sqlite3_hard_heap_limit64(N) is invoked and
** the soft heap limit is outside the range of 1..N, then the soft heap
@@ -9268,7 +9348,7 @@ typedef struct sqlite3_backup sqlite3_backup;
** if the application incorrectly accesses the destination [database connection]
** and so no error code is reported, but the operations may malfunction
** nevertheless. Use of the destination database connection while a
-** backup is in progress might also also cause a mutex deadlock.
+** backup is in progress might also cause a mutex deadlock.
**
** If running in [shared cache mode], the application must
** guarantee that the shared cache used by the destination database
@@ -9696,7 +9776,7 @@ SQLITE_API int sqlite3_wal_checkpoint_v2(
*/
#define SQLITE_CHECKPOINT_PASSIVE 0 /* Do as much as possible w/o blocking */
#define SQLITE_CHECKPOINT_FULL 1 /* Wait for writers, then checkpoint */
-#define SQLITE_CHECKPOINT_RESTART 2 /* Like FULL but wait for for readers */
+#define SQLITE_CHECKPOINT_RESTART 2 /* Like FULL but wait for readers */
#define SQLITE_CHECKPOINT_TRUNCATE 3 /* Like RESTART but also truncate WAL */
/*
@@ -9866,8 +9946,8 @@ SQLITE_API SQLITE_EXPERIMENTAL const char *sqlite3_vtab_collation(sqlite3_index_
** of a [virtual table] implementation. The result of calling this
** interface from outside of xBestIndex() is undefined and probably harmful.
**
-** ^The sqlite3_vtab_distinct() interface returns an integer that is
-** either 0, 1, or 2. The integer returned by sqlite3_vtab_distinct()
+** ^The sqlite3_vtab_distinct() interface returns an integer between 0 and
+** 3. The integer returned by sqlite3_vtab_distinct()
** gives the virtual table additional information about how the query
** planner wants the output to be ordered. As long as the virtual table
** can meet the ordering requirements of the query planner, it may set
@@ -9899,6 +9979,13 @@ SQLITE_API SQLITE_EXPERIMENTAL const char *sqlite3_vtab_collation(sqlite3_index_
** that have the same value for all columns identified by "aOrderBy".
** ^However omitting the extra rows is optional.
** This mode is used for a DISTINCT query.
+** <li value="3"><p>
+** ^(If the sqlite3_vtab_distinct() interface returns 3, that means
+** that the query planner needs only distinct rows but it does need the
+** rows to be sorted.)^ ^The virtual table implementation is free to omit
+** rows that are identical in all aOrderBy columns, if it wants to, but
+** it is not required to omit any rows. This mode is used for queries
+** that have both DISTINCT and ORDER BY clauses.
** </ol>
**
** ^For the purposes of comparing virtual table output values to see if the
@@ -13121,11 +13208,16 @@ struct fts5_api {
/************** Continuing where we left off in sqliteInt.h ******************/
/*
+** Reuse the STATIC_LRU for mutex access to sqlite3_temp_directory.
+*/
+#define SQLITE_MUTEX_STATIC_TEMPDIR SQLITE_MUTEX_STATIC_VFS1
+
+/*
** Include the configuration header output by 'configure' if we're using the
** autoconf-based build
*/
#if defined(_HAVE_SQLITE_CONFIG_H) && !defined(SQLITECONFIG_H)
-#include "config.h"
+#include "sqlite_cfg.h"
#define SQLITECONFIG_H 1
#endif
@@ -14363,8 +14455,19 @@ typedef INT16_TYPE LogEst;
/*
** Round up a number to the next larger multiple of 8. This is used
** to force 8-byte alignment on 64-bit architectures.
+**
+** ROUND8() always does the rounding, for any argument.
+**
+** ROUND8P() assumes that the argument is already an integer number of
+** pointers in size, and so it is a no-op on systems where the pointer
+** size is 8.
*/
#define ROUND8(x) (((x)+7)&~7)
+#if SQLITE_PTRSIZE==8
+# define ROUND8P(x) (x)
+#else
+# define ROUND8P(x) (((x)+7)&~7)
+#endif
/*
** Round down to the nearest multiple of 8
@@ -14427,22 +14530,23 @@ typedef INT16_TYPE LogEst;
#endif
/*
-** SELECTTRACE_ENABLED will be either 1 or 0 depending on whether or not
-** the Select query generator tracing logic is turned on.
+** TREETRACE_ENABLED will be either 1 or 0 depending on whether or not
+** the Abstract Syntax Tree tracing logic is turned on.
*/
#if !defined(SQLITE_AMALGAMATION)
-SQLITE_PRIVATE u32 sqlite3SelectTrace;
+SQLITE_PRIVATE u32 sqlite3TreeTrace;
#endif
#if defined(SQLITE_DEBUG) \
- && (defined(SQLITE_TEST) || defined(SQLITE_ENABLE_SELECTTRACE))
-# define SELECTTRACE_ENABLED 1
+ && (defined(SQLITE_TEST) || defined(SQLITE_ENABLE_SELECTTRACE) \
+ || defined(SQLITE_ENABLE_TREETRACE))
+# define TREETRACE_ENABLED 1
# define SELECTTRACE(K,P,S,X) \
- if(sqlite3SelectTrace&(K)) \
+ if(sqlite3TreeTrace&(K)) \
sqlite3DebugPrintf("%u/%d/%p: ",(S)->selId,(P)->addrExplain,(S)),\
sqlite3DebugPrintf X
#else
# define SELECTTRACE(K,P,S,X)
-# define SELECTTRACE_ENABLED 0
+# define TREETRACE_ENABLED 0
#endif
/*
@@ -14527,7 +14631,7 @@ struct BusyHandler {
** pointer will work here as long as it is distinct from SQLITE_STATIC
** and SQLITE_TRANSIENT.
*/
-#define SQLITE_DYNAMIC ((sqlite3_destructor_type)sqlite3OomFault)
+#define SQLITE_DYNAMIC ((sqlite3_destructor_type)sqlite3OomClear)
/*
** When SQLITE_OMIT_WSD is defined, it means that the target platform does
@@ -14596,6 +14700,7 @@ typedef struct FuncDef FuncDef;
typedef struct FuncDefHash FuncDefHash;
typedef struct IdList IdList;
typedef struct Index Index;
+typedef struct IndexedExpr IndexedExpr;
typedef struct IndexSample IndexSample;
typedef struct KeyClass KeyClass;
typedef struct KeyInfo KeyInfo;
@@ -14603,6 +14708,7 @@ typedef struct Lookaside Lookaside;
typedef struct LookasideSlot LookasideSlot;
typedef struct Module Module;
typedef struct NameContext NameContext;
+typedef struct OnOrUsing OnOrUsing;
typedef struct Parse Parse;
typedef struct ParseCleanup ParseCleanup;
typedef struct PreUpdate PreUpdate;
@@ -14660,6 +14766,7 @@ typedef struct With With;
#define MASKBIT32(n) (((unsigned int)1)<<(n))
#define SMASKBIT32(n) ((n)<=31?((unsigned int)1)<<(n):0)
#define ALLBITS ((Bitmask)-1)
+#define TOPBIT (((Bitmask)1)<<(BMS-1))
/* A VList object records a mapping between parameters/variables/wildcards
** in the SQL statement (such as $abc, @pqr, or :xyz) and the integer
@@ -14674,6 +14781,331 @@ typedef int VList;
** "BusyHandler" typedefs. vdbe.h also requires a few of the opaque
** pointer types (i.e. FuncDef) defined above.
*/
+/************** Include os.h in the middle of sqliteInt.h ********************/
+/************** Begin file os.h **********************************************/
+/*
+** 2001 September 16
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+******************************************************************************
+**
+** This header file (together with is companion C source-code file
+** "os.c") attempt to abstract the underlying operating system so that
+** the SQLite library will work on both POSIX and windows systems.
+**
+** This header file is #include-ed by sqliteInt.h and thus ends up
+** being included by every source file.
+*/
+#ifndef _SQLITE_OS_H_
+#define _SQLITE_OS_H_
+
+/*
+** Attempt to automatically detect the operating system and setup the
+** necessary pre-processor macros for it.
+*/
+/************** Include os_setup.h in the middle of os.h *********************/
+/************** Begin file os_setup.h ****************************************/
+/*
+** 2013 November 25
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+******************************************************************************
+**
+** This file contains pre-processor directives related to operating system
+** detection and/or setup.
+*/
+#ifndef SQLITE_OS_SETUP_H
+#define SQLITE_OS_SETUP_H
+
+/*
+** Figure out if we are dealing with Unix, Windows, or some other operating
+** system.
+**
+** After the following block of preprocess macros, all of
+**
+** SQLITE_OS_KV
+** SQLITE_OS_OTHER
+** SQLITE_OS_UNIX
+** SQLITE_OS_WIN
+**
+** will defined to either 1 or 0. One of them will be 1. The others will be 0.
+** If none of the macros are initially defined, then select either
+** SQLITE_OS_UNIX or SQLITE_OS_WIN depending on the target platform.
+**
+** If SQLITE_OS_OTHER=1 is specified at compile-time, then the application
+** must provide its own VFS implementation together with sqlite3_os_init()
+** and sqlite3_os_end() routines.
+*/
+#if !defined(SQLITE_OS_KV) && !defined(SQLITE_OS_OTHER) && \
+ !defined(SQLITE_OS_UNIX) && !defined(SQLITE_OS_WIN)
+# if defined(_WIN32) || defined(WIN32) || defined(__CYGWIN__) || \
+ defined(__MINGW32__) || defined(__BORLANDC__)
+# define SQLITE_OS_WIN 1
+# define SQLITE_OS_UNIX 0
+# else
+# define SQLITE_OS_WIN 0
+# define SQLITE_OS_UNIX 1
+# endif
+#endif
+#if SQLITE_OS_OTHER+1>1
+# undef SQLITE_OS_KV
+# define SQLITE_OS_KV 0
+# undef SQLITE_OS_UNIX
+# define SQLITE_OS_UNIX 0
+# undef SQLITE_OS_WIN
+# define SQLITE_OS_WIN 0
+#endif
+#if SQLITE_OS_KV+1>1
+# undef SQLITE_OS_OTHER
+# define SQLITE_OS_OTHER 0
+# undef SQLITE_OS_UNIX
+# define SQLITE_OS_UNIX 0
+# undef SQLITE_OS_WIN
+# define SQLITE_OS_WIN 0
+# define SQLITE_OMIT_LOAD_EXTENSION 1
+# define SQLITE_OMIT_WAL 1
+# define SQLITE_OMIT_DEPRECATED 1
+# undef SQLITE_TEMP_STORE
+# define SQLITE_TEMP_STORE 3 /* Always use memory for temporary storage */
+# define SQLITE_DQS 0
+# define SQLITE_OMIT_SHARED_CACHE 1
+# define SQLITE_OMIT_AUTOINIT 1
+#endif
+#if SQLITE_OS_UNIX+1>1
+# undef SQLITE_OS_KV
+# define SQLITE_OS_KV 0
+# undef SQLITE_OS_OTHER
+# define SQLITE_OS_OTHER 0
+# undef SQLITE_OS_WIN
+# define SQLITE_OS_WIN 0
+#endif
+#if SQLITE_OS_WIN+1>1
+# undef SQLITE_OS_KV
+# define SQLITE_OS_KV 0
+# undef SQLITE_OS_OTHER
+# define SQLITE_OS_OTHER 0
+# undef SQLITE_OS_UNIX
+# define SQLITE_OS_UNIX 0
+#endif
+
+
+#endif /* SQLITE_OS_SETUP_H */
+
+/************** End of os_setup.h ********************************************/
+/************** Continuing where we left off in os.h *************************/
+
+/* If the SET_FULLSYNC macro is not defined above, then make it
+** a no-op
+*/
+#ifndef SET_FULLSYNC
+# define SET_FULLSYNC(x,y)
+#endif
+
+/* Maximum pathname length. Note: FILENAME_MAX defined by stdio.h
+*/
+#ifndef SQLITE_MAX_PATHLEN
+# define SQLITE_MAX_PATHLEN FILENAME_MAX
+#endif
+
+/* Maximum number of symlinks that will be resolved while trying to
+** expand a filename in xFullPathname() in the VFS.
+*/
+#ifndef SQLITE_MAX_SYMLINK
+# define SQLITE_MAX_SYMLINK 200
+#endif
+
+/*
+** The default size of a disk sector
+*/
+#ifndef SQLITE_DEFAULT_SECTOR_SIZE
+# define SQLITE_DEFAULT_SECTOR_SIZE 4096
+#endif
+
+/*
+** Temporary files are named starting with this prefix followed by 16 random
+** alphanumeric characters, and no file extension. They are stored in the
+** OS's standard temporary file directory, and are deleted prior to exit.
+** If sqlite is being embedded in another program, you may wish to change the
+** prefix to reflect your program's name, so that if your program exits
+** prematurely, old temporary files can be easily identified. This can be done
+** using -DSQLITE_TEMP_FILE_PREFIX=myprefix_ on the compiler command line.
+**
+** 2006-10-31: The default prefix used to be "sqlite_". But then
+** Mcafee started using SQLite in their anti-virus product and it
+** started putting files with the "sqlite" name in the c:/temp folder.
+** This annoyed many windows users. Those users would then do a
+** Google search for "sqlite", find the telephone numbers of the
+** developers and call to wake them up at night and complain.
+** For this reason, the default name prefix is changed to be "sqlite"
+** spelled backwards. So the temp files are still identified, but
+** anybody smart enough to figure out the code is also likely smart
+** enough to know that calling the developer will not help get rid
+** of the file.
+*/
+#ifndef SQLITE_TEMP_FILE_PREFIX
+# define SQLITE_TEMP_FILE_PREFIX "etilqs_"
+#endif
+
+/*
+** The following values may be passed as the second argument to
+** sqlite3OsLock(). The various locks exhibit the following semantics:
+**
+** SHARED: Any number of processes may hold a SHARED lock simultaneously.
+** RESERVED: A single process may hold a RESERVED lock on a file at
+** any time. Other processes may hold and obtain new SHARED locks.
+** PENDING: A single process may hold a PENDING lock on a file at
+** any one time. Existing SHARED locks may persist, but no new
+** SHARED locks may be obtained by other processes.
+** EXCLUSIVE: An EXCLUSIVE lock precludes all other locks.
+**
+** PENDING_LOCK may not be passed directly to sqlite3OsLock(). Instead, a
+** process that requests an EXCLUSIVE lock may actually obtain a PENDING
+** lock. This can be upgraded to an EXCLUSIVE lock by a subsequent call to
+** sqlite3OsLock().
+*/
+#define NO_LOCK 0
+#define SHARED_LOCK 1
+#define RESERVED_LOCK 2
+#define PENDING_LOCK 3
+#define EXCLUSIVE_LOCK 4
+
+/*
+** File Locking Notes: (Mostly about windows but also some info for Unix)
+**
+** We cannot use LockFileEx() or UnlockFileEx() on Win95/98/ME because
+** those functions are not available. So we use only LockFile() and
+** UnlockFile().
+**
+** LockFile() prevents not just writing but also reading by other processes.
+** A SHARED_LOCK is obtained by locking a single randomly-chosen
+** byte out of a specific range of bytes. The lock byte is obtained at
+** random so two separate readers can probably access the file at the
+** same time, unless they are unlucky and choose the same lock byte.
+** An EXCLUSIVE_LOCK is obtained by locking all bytes in the range.
+** There can only be one writer. A RESERVED_LOCK is obtained by locking
+** a single byte of the file that is designated as the reserved lock byte.
+** A PENDING_LOCK is obtained by locking a designated byte different from
+** the RESERVED_LOCK byte.
+**
+** On WinNT/2K/XP systems, LockFileEx() and UnlockFileEx() are available,
+** which means we can use reader/writer locks. When reader/writer locks
+** are used, the lock is placed on the same range of bytes that is used
+** for probabilistic locking in Win95/98/ME. Hence, the locking scheme
+** will support two or more Win95 readers or two or more WinNT readers.
+** But a single Win95 reader will lock out all WinNT readers and a single
+** WinNT reader will lock out all other Win95 readers.
+**
+** The following #defines specify the range of bytes used for locking.
+** SHARED_SIZE is the number of bytes available in the pool from which
+** a random byte is selected for a shared lock. The pool of bytes for
+** shared locks begins at SHARED_FIRST.
+**
+** The same locking strategy and
+** byte ranges are used for Unix. This leaves open the possibility of having
+** clients on win95, winNT, and unix all talking to the same shared file
+** and all locking correctly. To do so would require that samba (or whatever
+** tool is being used for file sharing) implements locks correctly between
+** windows and unix. I'm guessing that isn't likely to happen, but by
+** using the same locking range we are at least open to the possibility.
+**
+** Locking in windows is manditory. For this reason, we cannot store
+** actual data in the bytes used for locking. The pager never allocates
+** the pages involved in locking therefore. SHARED_SIZE is selected so
+** that all locks will fit on a single page even at the minimum page size.
+** PENDING_BYTE defines the beginning of the locks. By default PENDING_BYTE
+** is set high so that we don't have to allocate an unused page except
+** for very large databases. But one should test the page skipping logic
+** by setting PENDING_BYTE low and running the entire regression suite.
+**
+** Changing the value of PENDING_BYTE results in a subtly incompatible
+** file format. Depending on how it is changed, you might not notice
+** the incompatibility right away, even running a full regression test.
+** The default location of PENDING_BYTE is the first byte past the
+** 1GB boundary.
+**
+*/
+#ifdef SQLITE_OMIT_WSD
+# define PENDING_BYTE (0x40000000)
+#else
+# define PENDING_BYTE sqlite3PendingByte
+#endif
+#define RESERVED_BYTE (PENDING_BYTE+1)
+#define SHARED_FIRST (PENDING_BYTE+2)
+#define SHARED_SIZE 510
+
+/*
+** Wrapper around OS specific sqlite3_os_init() function.
+*/
+SQLITE_PRIVATE int sqlite3OsInit(void);
+
+/*
+** Functions for accessing sqlite3_file methods
+*/
+SQLITE_PRIVATE void sqlite3OsClose(sqlite3_file*);
+SQLITE_PRIVATE int sqlite3OsRead(sqlite3_file*, void*, int amt, i64 offset);
+SQLITE_PRIVATE int sqlite3OsWrite(sqlite3_file*, const void*, int amt, i64 offset);
+SQLITE_PRIVATE int sqlite3OsTruncate(sqlite3_file*, i64 size);
+SQLITE_PRIVATE int sqlite3OsSync(sqlite3_file*, int);
+SQLITE_PRIVATE int sqlite3OsFileSize(sqlite3_file*, i64 *pSize);
+SQLITE_PRIVATE int sqlite3OsLock(sqlite3_file*, int);
+SQLITE_PRIVATE int sqlite3OsUnlock(sqlite3_file*, int);
+SQLITE_PRIVATE int sqlite3OsCheckReservedLock(sqlite3_file *id, int *pResOut);
+SQLITE_PRIVATE int sqlite3OsFileControl(sqlite3_file*,int,void*);
+SQLITE_PRIVATE void sqlite3OsFileControlHint(sqlite3_file*,int,void*);
+#define SQLITE_FCNTL_DB_UNCHANGED 0xca093fa0
+SQLITE_PRIVATE int sqlite3OsSectorSize(sqlite3_file *id);
+SQLITE_PRIVATE int sqlite3OsDeviceCharacteristics(sqlite3_file *id);
+#ifndef SQLITE_OMIT_WAL
+SQLITE_PRIVATE int sqlite3OsShmMap(sqlite3_file *,int,int,int,void volatile **);
+SQLITE_PRIVATE int sqlite3OsShmLock(sqlite3_file *id, int, int, int);
+SQLITE_PRIVATE void sqlite3OsShmBarrier(sqlite3_file *id);
+SQLITE_PRIVATE int sqlite3OsShmUnmap(sqlite3_file *id, int);
+#endif /* SQLITE_OMIT_WAL */
+SQLITE_PRIVATE int sqlite3OsFetch(sqlite3_file *id, i64, int, void **);
+SQLITE_PRIVATE int sqlite3OsUnfetch(sqlite3_file *, i64, void *);
+
+
+/*
+** Functions for accessing sqlite3_vfs methods
+*/
+SQLITE_PRIVATE int sqlite3OsOpen(sqlite3_vfs *, const char *, sqlite3_file*, int, int *);
+SQLITE_PRIVATE int sqlite3OsDelete(sqlite3_vfs *, const char *, int);
+SQLITE_PRIVATE int sqlite3OsAccess(sqlite3_vfs *, const char *, int, int *pResOut);
+SQLITE_PRIVATE int sqlite3OsFullPathname(sqlite3_vfs *, const char *, int, char *);
+#ifndef SQLITE_OMIT_LOAD_EXTENSION
+SQLITE_PRIVATE void *sqlite3OsDlOpen(sqlite3_vfs *, const char *);
+SQLITE_PRIVATE void sqlite3OsDlError(sqlite3_vfs *, int, char *);
+SQLITE_PRIVATE void (*sqlite3OsDlSym(sqlite3_vfs *, void *, const char *))(void);
+SQLITE_PRIVATE void sqlite3OsDlClose(sqlite3_vfs *, void *);
+#endif /* SQLITE_OMIT_LOAD_EXTENSION */
+SQLITE_PRIVATE int sqlite3OsRandomness(sqlite3_vfs *, int, char *);
+SQLITE_PRIVATE int sqlite3OsSleep(sqlite3_vfs *, int);
+SQLITE_PRIVATE int sqlite3OsGetLastError(sqlite3_vfs*);
+SQLITE_PRIVATE int sqlite3OsCurrentTimeInt64(sqlite3_vfs *, sqlite3_int64*);
+
+/*
+** Convenience functions for opening and closing files using
+** sqlite3_malloc() to obtain space for the file-handle structure.
+*/
+SQLITE_PRIVATE int sqlite3OsOpenMalloc(sqlite3_vfs *, const char *, sqlite3_file **, int,int*);
+SQLITE_PRIVATE void sqlite3OsCloseFree(sqlite3_file *);
+
+#endif /* _SQLITE_OS_H_ */
+
+/************** End of os.h **************************************************/
+/************** Continuing where we left off in sqliteInt.h ******************/
/************** Include pager.h in the middle of sqliteInt.h *****************/
/************** Begin file pager.h *******************************************/
/*
@@ -14721,14 +15153,15 @@ typedef struct Pager Pager;
typedef struct PgHdr DbPage;
/*
-** Page number PAGER_MJ_PGNO is never used in an SQLite database (it is
+** Page number PAGER_SJ_PGNO is never used in an SQLite database (it is
** reserved for working around a windows/posix incompatibility). It is
** used in the journal to signify that the remainder of the journal file
** is devoted to storing a super-journal name - there are no more pages to
** roll back. See comments for function writeSuperJournal() in pager.c
** for details.
*/
-#define PAGER_MJ_PGNO(x) ((Pgno)((PENDING_BYTE/((x)->pageSize))+1))
+#define PAGER_SJ_PGNO_COMPUTED(x) ((Pgno)((PENDING_BYTE/((x)->pageSize))+1))
+#define PAGER_SJ_PGNO(x) ((x)->lckPgno)
/*
** Allowed values for the flags parameter to sqlite3PagerOpen().
@@ -15293,6 +15726,8 @@ SQLITE_PRIVATE int sqlite3BtreeCheckpoint(Btree*, int, int *, int *);
SQLITE_PRIVATE int sqlite3BtreeTransferRow(BtCursor*, BtCursor*, i64);
+SQLITE_PRIVATE void sqlite3BtreeClearCache(Btree*);
+
/*
** If we are not using shared cache, then there is no need to
** use mutexes to access the BtShared structures. So make the
@@ -15405,7 +15840,6 @@ struct VdbeOp {
#ifdef SQLITE_ENABLE_CURSOR_HINTS
Expr *pExpr; /* Used when p4type is P4_EXPR */
#endif
- int (*xAdvance)(BtCursor *, int);
} p4;
#ifdef SQLITE_ENABLE_EXPLAIN_COMMENTS
char *zComment; /* Comment to improve readability */
@@ -15456,21 +15890,19 @@ typedef struct VdbeOpList VdbeOpList;
#define P4_COLLSEQ (-2) /* P4 is a pointer to a CollSeq structure */
#define P4_INT32 (-3) /* P4 is a 32-bit signed integer */
#define P4_SUBPROGRAM (-4) /* P4 is a pointer to a SubProgram structure */
-#define P4_ADVANCE (-5) /* P4 is a pointer to BtreeNext() or BtreePrev() */
-#define P4_TABLE (-6) /* P4 is a pointer to a Table structure */
+#define P4_TABLE (-5) /* P4 is a pointer to a Table structure */
/* Above do not own any resources. Must free those below */
-#define P4_FREE_IF_LE (-7)
-#define P4_DYNAMIC (-7) /* Pointer to memory from sqliteMalloc() */
-#define P4_FUNCDEF (-8) /* P4 is a pointer to a FuncDef structure */
-#define P4_KEYINFO (-9) /* P4 is a pointer to a KeyInfo structure */
-#define P4_EXPR (-10) /* P4 is a pointer to an Expr tree */
-#define P4_MEM (-11) /* P4 is a pointer to a Mem* structure */
-#define P4_VTAB (-12) /* P4 is a pointer to an sqlite3_vtab structure */
-#define P4_REAL (-13) /* P4 is a 64-bit floating point value */
-#define P4_INT64 (-14) /* P4 is a 64-bit signed integer */
-#define P4_INTARRAY (-15) /* P4 is a vector of 32-bit integers */
-#define P4_FUNCCTX (-16) /* P4 is a pointer to an sqlite3_context object */
-#define P4_DYNBLOB (-17) /* Pointer to memory from sqliteMalloc() */
+#define P4_FREE_IF_LE (-6)
+#define P4_DYNAMIC (-6) /* Pointer to memory from sqliteMalloc() */
+#define P4_FUNCDEF (-7) /* P4 is a pointer to a FuncDef structure */
+#define P4_KEYINFO (-8) /* P4 is a pointer to a KeyInfo structure */
+#define P4_EXPR (-9) /* P4 is a pointer to an Expr tree */
+#define P4_MEM (-10) /* P4 is a pointer to a Mem* structure */
+#define P4_VTAB (-11) /* P4 is a pointer to an sqlite3_vtab structure */
+#define P4_REAL (-12) /* P4 is a 64-bit floating point value */
+#define P4_INT64 (-13) /* P4 is a 64-bit signed integer */
+#define P4_INTARRAY (-14) /* P4 is a vector of 32-bit integers */
+#define P4_FUNCCTX (-15) /* P4 is a pointer to an sqlite3_context object */
/* Error message codes for OP_Halt */
#define P5_ConstraintNotNull 1
@@ -15515,53 +15947,53 @@ typedef struct VdbeOpList VdbeOpList;
#define OP_Savepoint 0
#define OP_AutoCommit 1
#define OP_Transaction 2
-#define OP_SorterNext 3 /* jump */
-#define OP_Prev 4 /* jump */
-#define OP_Next 5 /* jump */
-#define OP_Checkpoint 6
-#define OP_JournalMode 7
-#define OP_Vacuum 8
-#define OP_VFilter 9 /* jump, synopsis: iplan=r[P3] zplan='P4' */
-#define OP_VUpdate 10 /* synopsis: data=r[P3@P2] */
-#define OP_Goto 11 /* jump */
-#define OP_Gosub 12 /* jump */
-#define OP_InitCoroutine 13 /* jump */
-#define OP_Yield 14 /* jump */
-#define OP_MustBeInt 15 /* jump */
-#define OP_Jump 16 /* jump */
-#define OP_Once 17 /* jump */
-#define OP_If 18 /* jump */
+#define OP_Checkpoint 3
+#define OP_JournalMode 4
+#define OP_Vacuum 5
+#define OP_VFilter 6 /* jump, synopsis: iplan=r[P3] zplan='P4' */
+#define OP_VUpdate 7 /* synopsis: data=r[P3@P2] */
+#define OP_Init 8 /* jump, synopsis: Start at P2 */
+#define OP_Goto 9 /* jump */
+#define OP_Gosub 10 /* jump */
+#define OP_InitCoroutine 11 /* jump */
+#define OP_Yield 12 /* jump */
+#define OP_MustBeInt 13 /* jump */
+#define OP_Jump 14 /* jump */
+#define OP_Once 15 /* jump */
+#define OP_If 16 /* jump */
+#define OP_IfNot 17 /* jump */
+#define OP_IsType 18 /* jump, synopsis: if typeof(P1.P3) in P5 goto P2 */
#define OP_Not 19 /* same as TK_NOT, synopsis: r[P2]= !r[P1] */
-#define OP_IfNot 20 /* jump */
-#define OP_IsNullOrType 21 /* jump, synopsis: if typeof(r[P1]) IN (P3,5) goto P2 */
-#define OP_IfNullRow 22 /* jump, synopsis: if P1.nullRow then r[P3]=NULL, goto P2 */
-#define OP_SeekLT 23 /* jump, synopsis: key=r[P3@P4] */
-#define OP_SeekLE 24 /* jump, synopsis: key=r[P3@P4] */
-#define OP_SeekGE 25 /* jump, synopsis: key=r[P3@P4] */
-#define OP_SeekGT 26 /* jump, synopsis: key=r[P3@P4] */
-#define OP_IfNotOpen 27 /* jump, synopsis: if( !csr[P1] ) goto P2 */
-#define OP_IfNoHope 28 /* jump, synopsis: key=r[P3@P4] */
-#define OP_NoConflict 29 /* jump, synopsis: key=r[P3@P4] */
-#define OP_NotFound 30 /* jump, synopsis: key=r[P3@P4] */
-#define OP_Found 31 /* jump, synopsis: key=r[P3@P4] */
-#define OP_SeekRowid 32 /* jump, synopsis: intkey=r[P3] */
-#define OP_NotExists 33 /* jump, synopsis: intkey=r[P3] */
-#define OP_Last 34 /* jump */
-#define OP_IfSmaller 35 /* jump */
-#define OP_SorterSort 36 /* jump */
-#define OP_Sort 37 /* jump */
-#define OP_Rewind 38 /* jump */
-#define OP_IdxLE 39 /* jump, synopsis: key=r[P3@P4] */
-#define OP_IdxGT 40 /* jump, synopsis: key=r[P3@P4] */
-#define OP_IdxLT 41 /* jump, synopsis: key=r[P3@P4] */
-#define OP_IdxGE 42 /* jump, synopsis: key=r[P3@P4] */
+#define OP_IfNullRow 20 /* jump, synopsis: if P1.nullRow then r[P3]=NULL, goto P2 */
+#define OP_SeekLT 21 /* jump, synopsis: key=r[P3@P4] */
+#define OP_SeekLE 22 /* jump, synopsis: key=r[P3@P4] */
+#define OP_SeekGE 23 /* jump, synopsis: key=r[P3@P4] */
+#define OP_SeekGT 24 /* jump, synopsis: key=r[P3@P4] */
+#define OP_IfNotOpen 25 /* jump, synopsis: if( !csr[P1] ) goto P2 */
+#define OP_IfNoHope 26 /* jump, synopsis: key=r[P3@P4] */
+#define OP_NoConflict 27 /* jump, synopsis: key=r[P3@P4] */
+#define OP_NotFound 28 /* jump, synopsis: key=r[P3@P4] */
+#define OP_Found 29 /* jump, synopsis: key=r[P3@P4] */
+#define OP_SeekRowid 30 /* jump, synopsis: intkey=r[P3] */
+#define OP_NotExists 31 /* jump, synopsis: intkey=r[P3] */
+#define OP_Last 32 /* jump */
+#define OP_IfSmaller 33 /* jump */
+#define OP_SorterSort 34 /* jump */
+#define OP_Sort 35 /* jump */
+#define OP_Rewind 36 /* jump */
+#define OP_SorterNext 37 /* jump */
+#define OP_Prev 38 /* jump */
+#define OP_Next 39 /* jump */
+#define OP_IdxLE 40 /* jump, synopsis: key=r[P3@P4] */
+#define OP_IdxGT 41 /* jump, synopsis: key=r[P3@P4] */
+#define OP_IdxLT 42 /* jump, synopsis: key=r[P3@P4] */
#define OP_Or 43 /* same as TK_OR, synopsis: r[P3]=(r[P1] || r[P2]) */
#define OP_And 44 /* same as TK_AND, synopsis: r[P3]=(r[P1] && r[P2]) */
-#define OP_RowSetRead 45 /* jump, synopsis: r[P3]=rowset(P1) */
-#define OP_RowSetTest 46 /* jump, synopsis: if r[P3] in rowset(P1) goto P2 */
-#define OP_Program 47 /* jump */
-#define OP_FkIfZero 48 /* jump, synopsis: if fkctr[P1]==0 goto P2 */
-#define OP_IfPos 49 /* jump, synopsis: if r[P1]>0 then r[P1]-=P3, goto P2 */
+#define OP_IdxGE 45 /* jump, synopsis: key=r[P3@P4] */
+#define OP_RowSetRead 46 /* jump, synopsis: r[P3]=rowset(P1) */
+#define OP_RowSetTest 47 /* jump, synopsis: if r[P3] in rowset(P1) goto P2 */
+#define OP_Program 48 /* jump */
+#define OP_FkIfZero 49 /* jump, synopsis: if fkctr[P1]==0 goto P2 */
#define OP_IsNull 50 /* jump, same as TK_ISNULL, synopsis: if r[P1]==NULL goto P2 */
#define OP_NotNull 51 /* jump, same as TK_NOTNULL, synopsis: if r[P1]!=NULL goto P2 */
#define OP_Ne 52 /* jump, same as TK_NE, synopsis: IF r[P3]!=r[P1] */
@@ -15571,12 +16003,12 @@ typedef struct VdbeOpList VdbeOpList;
#define OP_Lt 56 /* jump, same as TK_LT, synopsis: IF r[P3]<r[P1] */
#define OP_Ge 57 /* jump, same as TK_GE, synopsis: IF r[P3]>=r[P1] */
#define OP_ElseEq 58 /* jump, same as TK_ESCAPE */
-#define OP_IfNotZero 59 /* jump, synopsis: if r[P1]!=0 then r[P1]--, goto P2 */
-#define OP_DecrJumpZero 60 /* jump, synopsis: if (--r[P1])==0 goto P2 */
-#define OP_IncrVacuum 61 /* jump */
-#define OP_VNext 62 /* jump */
-#define OP_Filter 63 /* jump, synopsis: if key(P3@P4) not in filter(P1) goto P2 */
-#define OP_Init 64 /* jump, synopsis: Start at P2 */
+#define OP_IfPos 59 /* jump, synopsis: if r[P1]>0 then r[P1]-=P3, goto P2 */
+#define OP_IfNotZero 60 /* jump, synopsis: if r[P1]!=0 then r[P1]--, goto P2 */
+#define OP_DecrJumpZero 61 /* jump, synopsis: if (--r[P1])==0 goto P2 */
+#define OP_IncrVacuum 62 /* jump */
+#define OP_VNext 63 /* jump */
+#define OP_Filter 64 /* jump, synopsis: if key(P3@P4) not in filter(P1) goto P2 */
#define OP_PureFunc 65 /* synopsis: r[P3]=func(r[P2@NP]) */
#define OP_Function 66 /* synopsis: r[P3]=func(r[P2@NP]) */
#define OP_Return 67
@@ -15586,34 +16018,34 @@ typedef struct VdbeOpList VdbeOpList;
#define OP_Integer 71 /* synopsis: r[P2]=P1 */
#define OP_Int64 72 /* synopsis: r[P2]=P4 */
#define OP_String 73 /* synopsis: r[P2]='P4' (len=P1) */
-#define OP_Null 74 /* synopsis: r[P2..P3]=NULL */
-#define OP_SoftNull 75 /* synopsis: r[P1]=NULL */
-#define OP_Blob 76 /* synopsis: r[P2]=P4 (len=P1) */
-#define OP_Variable 77 /* synopsis: r[P2]=parameter(P1,P4) */
-#define OP_Move 78 /* synopsis: r[P2@P3]=r[P1@P3] */
-#define OP_Copy 79 /* synopsis: r[P2@P3+1]=r[P1@P3+1] */
-#define OP_SCopy 80 /* synopsis: r[P2]=r[P1] */
-#define OP_IntCopy 81 /* synopsis: r[P2]=r[P1] */
-#define OP_FkCheck 82
-#define OP_ResultRow 83 /* synopsis: output=r[P1@P2] */
-#define OP_CollSeq 84
-#define OP_AddImm 85 /* synopsis: r[P1]=r[P1]+P2 */
-#define OP_RealAffinity 86
-#define OP_Cast 87 /* synopsis: affinity(r[P1]) */
-#define OP_Permutation 88
-#define OP_Compare 89 /* synopsis: r[P1@P3] <-> r[P2@P3] */
-#define OP_IsTrue 90 /* synopsis: r[P2] = coalesce(r[P1]==TRUE,P3) ^ P4 */
-#define OP_ZeroOrNull 91 /* synopsis: r[P2] = 0 OR NULL */
-#define OP_Offset 92 /* synopsis: r[P3] = sqlite_offset(P1) */
-#define OP_Column 93 /* synopsis: r[P3]=PX */
-#define OP_TypeCheck 94 /* synopsis: typecheck(r[P1@P2]) */
-#define OP_Affinity 95 /* synopsis: affinity(r[P1@P2]) */
-#define OP_MakeRecord 96 /* synopsis: r[P3]=mkrec(r[P1@P2]) */
-#define OP_Count 97 /* synopsis: r[P2]=count() */
-#define OP_ReadCookie 98
-#define OP_SetCookie 99
-#define OP_ReopenIdx 100 /* synopsis: root=P2 iDb=P3 */
-#define OP_OpenRead 101 /* synopsis: root=P2 iDb=P3 */
+#define OP_BeginSubrtn 74 /* synopsis: r[P2]=NULL */
+#define OP_Null 75 /* synopsis: r[P2..P3]=NULL */
+#define OP_SoftNull 76 /* synopsis: r[P1]=NULL */
+#define OP_Blob 77 /* synopsis: r[P2]=P4 (len=P1) */
+#define OP_Variable 78 /* synopsis: r[P2]=parameter(P1,P4) */
+#define OP_Move 79 /* synopsis: r[P2@P3]=r[P1@P3] */
+#define OP_Copy 80 /* synopsis: r[P2@P3+1]=r[P1@P3+1] */
+#define OP_SCopy 81 /* synopsis: r[P2]=r[P1] */
+#define OP_IntCopy 82 /* synopsis: r[P2]=r[P1] */
+#define OP_FkCheck 83
+#define OP_ResultRow 84 /* synopsis: output=r[P1@P2] */
+#define OP_CollSeq 85
+#define OP_AddImm 86 /* synopsis: r[P1]=r[P1]+P2 */
+#define OP_RealAffinity 87
+#define OP_Cast 88 /* synopsis: affinity(r[P1]) */
+#define OP_Permutation 89
+#define OP_Compare 90 /* synopsis: r[P1@P3] <-> r[P2@P3] */
+#define OP_IsTrue 91 /* synopsis: r[P2] = coalesce(r[P1]==TRUE,P3) ^ P4 */
+#define OP_ZeroOrNull 92 /* synopsis: r[P2] = 0 OR NULL */
+#define OP_Offset 93 /* synopsis: r[P3] = sqlite_offset(P1) */
+#define OP_Column 94 /* synopsis: r[P3]=PX cursor P1 column P2 */
+#define OP_TypeCheck 95 /* synopsis: typecheck(r[P1@P2]) */
+#define OP_Affinity 96 /* synopsis: affinity(r[P1@P2]) */
+#define OP_MakeRecord 97 /* synopsis: r[P3]=mkrec(r[P1@P2]) */
+#define OP_Count 98 /* synopsis: r[P2]=count() */
+#define OP_ReadCookie 99
+#define OP_SetCookie 100
+#define OP_ReopenIdx 101 /* synopsis: root=P2 iDb=P3 */
#define OP_BitAnd 102 /* same as TK_BITAND, synopsis: r[P3]=r[P1]&r[P2] */
#define OP_BitOr 103 /* same as TK_BITOR, synopsis: r[P3]=r[P1]|r[P2] */
#define OP_ShiftLeft 104 /* same as TK_LSHIFT, synopsis: r[P3]=r[P2]<<r[P1] */
@@ -15624,79 +16056,81 @@ typedef struct VdbeOpList VdbeOpList;
#define OP_Divide 109 /* same as TK_SLASH, synopsis: r[P3]=r[P2]/r[P1] */
#define OP_Remainder 110 /* same as TK_REM, synopsis: r[P3]=r[P2]%r[P1] */
#define OP_Concat 111 /* same as TK_CONCAT, synopsis: r[P3]=r[P2]+r[P1] */
-#define OP_OpenWrite 112 /* synopsis: root=P2 iDb=P3 */
-#define OP_OpenDup 113
+#define OP_OpenRead 112 /* synopsis: root=P2 iDb=P3 */
+#define OP_OpenWrite 113 /* synopsis: root=P2 iDb=P3 */
#define OP_BitNot 114 /* same as TK_BITNOT, synopsis: r[P2]= ~r[P1] */
-#define OP_OpenAutoindex 115 /* synopsis: nColumn=P2 */
-#define OP_OpenEphemeral 116 /* synopsis: nColumn=P2 */
+#define OP_OpenDup 115
+#define OP_OpenAutoindex 116 /* synopsis: nColumn=P2 */
#define OP_String8 117 /* same as TK_STRING, synopsis: r[P2]='P4' */
-#define OP_SorterOpen 118
-#define OP_SequenceTest 119 /* synopsis: if( cursor[P1].ctr++ ) pc = P2 */
-#define OP_OpenPseudo 120 /* synopsis: P3 columns in r[P2] */
-#define OP_Close 121
-#define OP_ColumnsUsed 122
-#define OP_SeekScan 123 /* synopsis: Scan-ahead up to P1 rows */
-#define OP_SeekHit 124 /* synopsis: set P2<=seekHit<=P3 */
-#define OP_Sequence 125 /* synopsis: r[P2]=cursor[P1].ctr++ */
-#define OP_NewRowid 126 /* synopsis: r[P2]=rowid */
-#define OP_Insert 127 /* synopsis: intkey=r[P3] data=r[P2] */
-#define OP_RowCell 128
-#define OP_Delete 129
-#define OP_ResetCount 130
-#define OP_SorterCompare 131 /* synopsis: if key(P1)!=trim(r[P3],P4) goto P2 */
-#define OP_SorterData 132 /* synopsis: r[P2]=data */
-#define OP_RowData 133 /* synopsis: r[P2]=data */
-#define OP_Rowid 134 /* synopsis: r[P2]=rowid */
-#define OP_NullRow 135
-#define OP_SeekEnd 136
-#define OP_IdxInsert 137 /* synopsis: key=r[P2] */
-#define OP_SorterInsert 138 /* synopsis: key=r[P2] */
-#define OP_IdxDelete 139 /* synopsis: key=r[P2@P3] */
-#define OP_DeferredSeek 140 /* synopsis: Move P3 to P1.rowid if needed */
-#define OP_IdxRowid 141 /* synopsis: r[P2]=rowid */
-#define OP_FinishSeek 142
-#define OP_Destroy 143
-#define OP_Clear 144
-#define OP_ResetSorter 145
-#define OP_CreateBtree 146 /* synopsis: r[P2]=root iDb=P1 flags=P3 */
-#define OP_SqlExec 147
-#define OP_ParseSchema 148
-#define OP_LoadAnalysis 149
-#define OP_DropTable 150
-#define OP_DropIndex 151
-#define OP_DropTrigger 152
+#define OP_OpenEphemeral 118 /* synopsis: nColumn=P2 */
+#define OP_SorterOpen 119
+#define OP_SequenceTest 120 /* synopsis: if( cursor[P1].ctr++ ) pc = P2 */
+#define OP_OpenPseudo 121 /* synopsis: P3 columns in r[P2] */
+#define OP_Close 122
+#define OP_ColumnsUsed 123
+#define OP_SeekScan 124 /* synopsis: Scan-ahead up to P1 rows */
+#define OP_SeekHit 125 /* synopsis: set P2<=seekHit<=P3 */
+#define OP_Sequence 126 /* synopsis: r[P2]=cursor[P1].ctr++ */
+#define OP_NewRowid 127 /* synopsis: r[P2]=rowid */
+#define OP_Insert 128 /* synopsis: intkey=r[P3] data=r[P2] */
+#define OP_RowCell 129
+#define OP_Delete 130
+#define OP_ResetCount 131
+#define OP_SorterCompare 132 /* synopsis: if key(P1)!=trim(r[P3],P4) goto P2 */
+#define OP_SorterData 133 /* synopsis: r[P2]=data */
+#define OP_RowData 134 /* synopsis: r[P2]=data */
+#define OP_Rowid 135 /* synopsis: r[P2]=PX rowid of P1 */
+#define OP_NullRow 136
+#define OP_SeekEnd 137
+#define OP_IdxInsert 138 /* synopsis: key=r[P2] */
+#define OP_SorterInsert 139 /* synopsis: key=r[P2] */
+#define OP_IdxDelete 140 /* synopsis: key=r[P2@P3] */
+#define OP_DeferredSeek 141 /* synopsis: Move P3 to P1.rowid if needed */
+#define OP_IdxRowid 142 /* synopsis: r[P2]=rowid */
+#define OP_FinishSeek 143
+#define OP_Destroy 144
+#define OP_Clear 145
+#define OP_ResetSorter 146
+#define OP_CreateBtree 147 /* synopsis: r[P2]=root iDb=P1 flags=P3 */
+#define OP_SqlExec 148
+#define OP_ParseSchema 149
+#define OP_LoadAnalysis 150
+#define OP_DropTable 151
+#define OP_DropIndex 152
#define OP_Real 153 /* same as TK_FLOAT, synopsis: r[P2]=P4 */
-#define OP_IntegrityCk 154
-#define OP_RowSetAdd 155 /* synopsis: rowset(P1)=r[P2] */
-#define OP_Param 156
-#define OP_FkCounter 157 /* synopsis: fkctr[P1]+=P2 */
-#define OP_MemMax 158 /* synopsis: r[P1]=max(r[P1],r[P2]) */
-#define OP_OffsetLimit 159 /* synopsis: if r[P1]>0 then r[P2]=r[P1]+max(0,r[P3]) else r[P2]=(-1) */
-#define OP_AggInverse 160 /* synopsis: accum=r[P3] inverse(r[P2@P5]) */
-#define OP_AggStep 161 /* synopsis: accum=r[P3] step(r[P2@P5]) */
-#define OP_AggStep1 162 /* synopsis: accum=r[P3] step(r[P2@P5]) */
-#define OP_AggValue 163 /* synopsis: r[P3]=value N=P2 */
-#define OP_AggFinal 164 /* synopsis: accum=r[P1] N=P2 */
-#define OP_Expire 165
-#define OP_CursorLock 166
-#define OP_CursorUnlock 167
-#define OP_TableLock 168 /* synopsis: iDb=P1 root=P2 write=P3 */
-#define OP_VBegin 169
-#define OP_VCreate 170
-#define OP_VDestroy 171
-#define OP_VOpen 172
-#define OP_VInitIn 173 /* synopsis: r[P2]=ValueList(P1,P3) */
-#define OP_VColumn 174 /* synopsis: r[P3]=vcolumn(P2) */
-#define OP_VRename 175
-#define OP_Pagecount 176
-#define OP_MaxPgcnt 177
-#define OP_FilterAdd 178 /* synopsis: filter(P1) += key(P3@P4) */
-#define OP_Trace 179
-#define OP_CursorHint 180
-#define OP_ReleaseReg 181 /* synopsis: release r[P1@P2] mask P3 */
-#define OP_Noop 182
-#define OP_Explain 183
-#define OP_Abortable 184
+#define OP_DropTrigger 154
+#define OP_IntegrityCk 155
+#define OP_RowSetAdd 156 /* synopsis: rowset(P1)=r[P2] */
+#define OP_Param 157
+#define OP_FkCounter 158 /* synopsis: fkctr[P1]+=P2 */
+#define OP_MemMax 159 /* synopsis: r[P1]=max(r[P1],r[P2]) */
+#define OP_OffsetLimit 160 /* synopsis: if r[P1]>0 then r[P2]=r[P1]+max(0,r[P3]) else r[P2]=(-1) */
+#define OP_AggInverse 161 /* synopsis: accum=r[P3] inverse(r[P2@P5]) */
+#define OP_AggStep 162 /* synopsis: accum=r[P3] step(r[P2@P5]) */
+#define OP_AggStep1 163 /* synopsis: accum=r[P3] step(r[P2@P5]) */
+#define OP_AggValue 164 /* synopsis: r[P3]=value N=P2 */
+#define OP_AggFinal 165 /* synopsis: accum=r[P1] N=P2 */
+#define OP_Expire 166
+#define OP_CursorLock 167
+#define OP_CursorUnlock 168
+#define OP_TableLock 169 /* synopsis: iDb=P1 root=P2 write=P3 */
+#define OP_VBegin 170
+#define OP_VCreate 171
+#define OP_VDestroy 172
+#define OP_VOpen 173
+#define OP_VInitIn 174 /* synopsis: r[P2]=ValueList(P1,P3) */
+#define OP_VColumn 175 /* synopsis: r[P3]=vcolumn(P2) */
+#define OP_VRename 176
+#define OP_Pagecount 177
+#define OP_MaxPgcnt 178
+#define OP_ClrSubtype 179 /* synopsis: r[P1].subtype = 0 */
+#define OP_FilterAdd 180 /* synopsis: filter(P1) += key(P3@P4) */
+#define OP_Trace 181
+#define OP_CursorHint 182
+#define OP_ReleaseReg 183 /* synopsis: release r[P1@P2] mask P3 */
+#define OP_Noop 184
+#define OP_Explain 185
+#define OP_Abortable 186
/* Properties such as "out2" or "jump" that are specified in
** comments following the "case" for each opcode in the vdbe.c
@@ -15709,30 +16143,30 @@ typedef struct VdbeOpList VdbeOpList;
#define OPFLG_OUT2 0x10 /* out2: P2 is an output */
#define OPFLG_OUT3 0x20 /* out3: P3 is an output */
#define OPFLG_INITIALIZER {\
-/* 0 */ 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x00, 0x10,\
-/* 8 */ 0x00, 0x01, 0x00, 0x01, 0x01, 0x01, 0x03, 0x03,\
-/* 16 */ 0x01, 0x01, 0x03, 0x12, 0x03, 0x03, 0x01, 0x09,\
-/* 24 */ 0x09, 0x09, 0x09, 0x01, 0x09, 0x09, 0x09, 0x09,\
-/* 32 */ 0x09, 0x09, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,\
-/* 40 */ 0x01, 0x01, 0x01, 0x26, 0x26, 0x23, 0x0b, 0x01,\
-/* 48 */ 0x01, 0x03, 0x03, 0x03, 0x0b, 0x0b, 0x0b, 0x0b,\
-/* 56 */ 0x0b, 0x0b, 0x01, 0x03, 0x03, 0x01, 0x01, 0x01,\
+/* 0 */ 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x01, 0x00,\
+/* 8 */ 0x01, 0x01, 0x01, 0x01, 0x03, 0x03, 0x01, 0x01,\
+/* 16 */ 0x03, 0x03, 0x01, 0x12, 0x01, 0x09, 0x09, 0x09,\
+/* 24 */ 0x09, 0x01, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09,\
+/* 32 */ 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,\
+/* 40 */ 0x01, 0x01, 0x01, 0x26, 0x26, 0x01, 0x23, 0x0b,\
+/* 48 */ 0x01, 0x01, 0x03, 0x03, 0x0b, 0x0b, 0x0b, 0x0b,\
+/* 56 */ 0x0b, 0x0b, 0x01, 0x03, 0x03, 0x03, 0x01, 0x01,\
/* 64 */ 0x01, 0x00, 0x00, 0x02, 0x02, 0x08, 0x00, 0x10,\
-/* 72 */ 0x10, 0x10, 0x10, 0x00, 0x10, 0x10, 0x00, 0x00,\
-/* 80 */ 0x10, 0x10, 0x00, 0x00, 0x00, 0x02, 0x02, 0x02,\
-/* 88 */ 0x00, 0x00, 0x12, 0x1e, 0x20, 0x00, 0x00, 0x00,\
-/* 96 */ 0x00, 0x10, 0x10, 0x00, 0x00, 0x00, 0x26, 0x26,\
+/* 72 */ 0x10, 0x10, 0x00, 0x10, 0x00, 0x10, 0x10, 0x00,\
+/* 80 */ 0x00, 0x10, 0x10, 0x00, 0x00, 0x00, 0x02, 0x02,\
+/* 88 */ 0x02, 0x00, 0x00, 0x12, 0x1e, 0x20, 0x00, 0x00,\
+/* 96 */ 0x00, 0x00, 0x10, 0x10, 0x00, 0x00, 0x26, 0x26,\
/* 104 */ 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26,\
/* 112 */ 0x00, 0x00, 0x12, 0x00, 0x00, 0x10, 0x00, 0x00,\
-/* 120 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x10, 0x00,\
-/* 128 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00,\
-/* 136 */ 0x00, 0x04, 0x04, 0x00, 0x00, 0x10, 0x00, 0x10,\
-/* 144 */ 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00,\
-/* 152 */ 0x00, 0x10, 0x00, 0x06, 0x10, 0x00, 0x04, 0x1a,\
-/* 160 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\
-/* 168 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00,\
-/* 176 */ 0x10, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\
-/* 184 */ 0x00,}
+/* 120 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x10,\
+/* 128 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10,\
+/* 136 */ 0x00, 0x00, 0x04, 0x04, 0x00, 0x00, 0x10, 0x00,\
+/* 144 */ 0x10, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00,\
+/* 152 */ 0x00, 0x10, 0x00, 0x00, 0x06, 0x10, 0x00, 0x04,\
+/* 160 */ 0x1a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\
+/* 168 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00,\
+/* 176 */ 0x00, 0x10, 0x10, 0x02, 0x00, 0x00, 0x00, 0x00,\
+/* 184 */ 0x00, 0x00, 0x00,}
/* The resolve3P2Values() routine is able to run faster if it knows
** the value of the largest JUMP opcode. The smaller the maximum
@@ -15778,8 +16212,10 @@ SQLITE_PRIVATE void sqlite3VdbeVerifyNoResultRow(Vdbe *p);
#endif
#if defined(SQLITE_DEBUG)
SQLITE_PRIVATE void sqlite3VdbeVerifyAbortable(Vdbe *p, int);
+SQLITE_PRIVATE void sqlite3VdbeNoJumpsOutsideSubrtn(Vdbe*,int,int,int);
#else
# define sqlite3VdbeVerifyAbortable(A,B)
+# define sqlite3VdbeNoJumpsOutsideSubrtn(A,B,C,D)
#endif
SQLITE_PRIVATE VdbeOp *sqlite3VdbeAddOpList(Vdbe*, int nOp, VdbeOpList const *aOp,int iLineno);
#ifndef SQLITE_OMIT_EXPLAIN
@@ -15806,6 +16242,7 @@ SQLITE_PRIVATE void sqlite3VdbeChangeP1(Vdbe*, int addr, int P1);
SQLITE_PRIVATE void sqlite3VdbeChangeP2(Vdbe*, int addr, int P2);
SQLITE_PRIVATE void sqlite3VdbeChangeP3(Vdbe*, int addr, int P3);
SQLITE_PRIVATE void sqlite3VdbeChangeP5(Vdbe*, u16 P5);
+SQLITE_PRIVATE void sqlite3VdbeTypeofColumn(Vdbe*, int);
SQLITE_PRIVATE void sqlite3VdbeJumpHere(Vdbe*, int addr);
SQLITE_PRIVATE void sqlite3VdbeJumpHereOrPopInst(Vdbe*, int addr);
SQLITE_PRIVATE int sqlite3VdbeChangeToNoop(Vdbe*, int addr);
@@ -15820,11 +16257,11 @@ SQLITE_PRIVATE void sqlite3VdbeAppendP4(Vdbe*, void *pP4, int p4type);
SQLITE_PRIVATE void sqlite3VdbeSetP4KeyInfo(Parse*, Index*);
SQLITE_PRIVATE void sqlite3VdbeUsesBtree(Vdbe*, int);
SQLITE_PRIVATE VdbeOp *sqlite3VdbeGetOp(Vdbe*, int);
+SQLITE_PRIVATE VdbeOp *sqlite3VdbeGetLastOp(Vdbe*);
SQLITE_PRIVATE int sqlite3VdbeMakeLabel(Parse*);
SQLITE_PRIVATE void sqlite3VdbeRunOnlyOnce(Vdbe*);
SQLITE_PRIVATE void sqlite3VdbeReusable(Vdbe*);
SQLITE_PRIVATE void sqlite3VdbeDelete(Vdbe*);
-SQLITE_PRIVATE void sqlite3VdbeClearObject(sqlite3*,Vdbe*);
SQLITE_PRIVATE void sqlite3VdbeMakeReady(Vdbe*,Parse*);
SQLITE_PRIVATE int sqlite3VdbeFinalize(Vdbe*);
SQLITE_PRIVATE void sqlite3VdbeResolveLabel(Vdbe*, int);
@@ -16169,290 +16606,6 @@ SQLITE_PRIVATE int sqlite3PCacheIsDirty(PCache *pCache);
/************** End of pcache.h **********************************************/
/************** Continuing where we left off in sqliteInt.h ******************/
-/************** Include os.h in the middle of sqliteInt.h ********************/
-/************** Begin file os.h **********************************************/
-/*
-** 2001 September 16
-**
-** The author disclaims copyright to this source code. In place of
-** a legal notice, here is a blessing:
-**
-** May you do good and not evil.
-** May you find forgiveness for yourself and forgive others.
-** May you share freely, never taking more than you give.
-**
-******************************************************************************
-**
-** This header file (together with is companion C source-code file
-** "os.c") attempt to abstract the underlying operating system so that
-** the SQLite library will work on both POSIX and windows systems.
-**
-** This header file is #include-ed by sqliteInt.h and thus ends up
-** being included by every source file.
-*/
-#ifndef _SQLITE_OS_H_
-#define _SQLITE_OS_H_
-
-/*
-** Attempt to automatically detect the operating system and setup the
-** necessary pre-processor macros for it.
-*/
-/************** Include os_setup.h in the middle of os.h *********************/
-/************** Begin file os_setup.h ****************************************/
-/*
-** 2013 November 25
-**
-** The author disclaims copyright to this source code. In place of
-** a legal notice, here is a blessing:
-**
-** May you do good and not evil.
-** May you find forgiveness for yourself and forgive others.
-** May you share freely, never taking more than you give.
-**
-******************************************************************************
-**
-** This file contains pre-processor directives related to operating system
-** detection and/or setup.
-*/
-#ifndef SQLITE_OS_SETUP_H
-#define SQLITE_OS_SETUP_H
-
-/*
-** Figure out if we are dealing with Unix, Windows, or some other operating
-** system.
-**
-** After the following block of preprocess macros, all of SQLITE_OS_UNIX,
-** SQLITE_OS_WIN, and SQLITE_OS_OTHER will defined to either 1 or 0. One of
-** the three will be 1. The other two will be 0.
-*/
-#if defined(SQLITE_OS_OTHER)
-# if SQLITE_OS_OTHER==1
-# undef SQLITE_OS_UNIX
-# define SQLITE_OS_UNIX 0
-# undef SQLITE_OS_WIN
-# define SQLITE_OS_WIN 0
-# else
-# undef SQLITE_OS_OTHER
-# endif
-#endif
-#if !defined(SQLITE_OS_UNIX) && !defined(SQLITE_OS_OTHER)
-# define SQLITE_OS_OTHER 0
-# ifndef SQLITE_OS_WIN
-# if defined(_WIN32) || defined(WIN32) || defined(__CYGWIN__) || \
- defined(__MINGW32__) || defined(__BORLANDC__)
-# define SQLITE_OS_WIN 1
-# define SQLITE_OS_UNIX 0
-# else
-# define SQLITE_OS_WIN 0
-# define SQLITE_OS_UNIX 1
-# endif
-# else
-# define SQLITE_OS_UNIX 0
-# endif
-#else
-# ifndef SQLITE_OS_WIN
-# define SQLITE_OS_WIN 0
-# endif
-#endif
-
-#endif /* SQLITE_OS_SETUP_H */
-
-/************** End of os_setup.h ********************************************/
-/************** Continuing where we left off in os.h *************************/
-
-/* If the SET_FULLSYNC macro is not defined above, then make it
-** a no-op
-*/
-#ifndef SET_FULLSYNC
-# define SET_FULLSYNC(x,y)
-#endif
-
-/* Maximum pathname length. Note: FILENAME_MAX defined by stdio.h
-*/
-#ifndef SQLITE_MAX_PATHLEN
-# define SQLITE_MAX_PATHLEN FILENAME_MAX
-#endif
-
-/*
-** The default size of a disk sector
-*/
-#ifndef SQLITE_DEFAULT_SECTOR_SIZE
-# define SQLITE_DEFAULT_SECTOR_SIZE 4096
-#endif
-
-/*
-** Temporary files are named starting with this prefix followed by 16 random
-** alphanumeric characters, and no file extension. They are stored in the
-** OS's standard temporary file directory, and are deleted prior to exit.
-** If sqlite is being embedded in another program, you may wish to change the
-** prefix to reflect your program's name, so that if your program exits
-** prematurely, old temporary files can be easily identified. This can be done
-** using -DSQLITE_TEMP_FILE_PREFIX=myprefix_ on the compiler command line.
-**
-** 2006-10-31: The default prefix used to be "sqlite_". But then
-** Mcafee started using SQLite in their anti-virus product and it
-** started putting files with the "sqlite" name in the c:/temp folder.
-** This annoyed many windows users. Those users would then do a
-** Google search for "sqlite", find the telephone numbers of the
-** developers and call to wake them up at night and complain.
-** For this reason, the default name prefix is changed to be "sqlite"
-** spelled backwards. So the temp files are still identified, but
-** anybody smart enough to figure out the code is also likely smart
-** enough to know that calling the developer will not help get rid
-** of the file.
-*/
-#ifndef SQLITE_TEMP_FILE_PREFIX
-# define SQLITE_TEMP_FILE_PREFIX "etilqs_"
-#endif
-
-/*
-** The following values may be passed as the second argument to
-** sqlite3OsLock(). The various locks exhibit the following semantics:
-**
-** SHARED: Any number of processes may hold a SHARED lock simultaneously.
-** RESERVED: A single process may hold a RESERVED lock on a file at
-** any time. Other processes may hold and obtain new SHARED locks.
-** PENDING: A single process may hold a PENDING lock on a file at
-** any one time. Existing SHARED locks may persist, but no new
-** SHARED locks may be obtained by other processes.
-** EXCLUSIVE: An EXCLUSIVE lock precludes all other locks.
-**
-** PENDING_LOCK may not be passed directly to sqlite3OsLock(). Instead, a
-** process that requests an EXCLUSIVE lock may actually obtain a PENDING
-** lock. This can be upgraded to an EXCLUSIVE lock by a subsequent call to
-** sqlite3OsLock().
-*/
-#define NO_LOCK 0
-#define SHARED_LOCK 1
-#define RESERVED_LOCK 2
-#define PENDING_LOCK 3
-#define EXCLUSIVE_LOCK 4
-
-/*
-** File Locking Notes: (Mostly about windows but also some info for Unix)
-**
-** We cannot use LockFileEx() or UnlockFileEx() on Win95/98/ME because
-** those functions are not available. So we use only LockFile() and
-** UnlockFile().
-**
-** LockFile() prevents not just writing but also reading by other processes.
-** A SHARED_LOCK is obtained by locking a single randomly-chosen
-** byte out of a specific range of bytes. The lock byte is obtained at
-** random so two separate readers can probably access the file at the
-** same time, unless they are unlucky and choose the same lock byte.
-** An EXCLUSIVE_LOCK is obtained by locking all bytes in the range.
-** There can only be one writer. A RESERVED_LOCK is obtained by locking
-** a single byte of the file that is designated as the reserved lock byte.
-** A PENDING_LOCK is obtained by locking a designated byte different from
-** the RESERVED_LOCK byte.
-**
-** On WinNT/2K/XP systems, LockFileEx() and UnlockFileEx() are available,
-** which means we can use reader/writer locks. When reader/writer locks
-** are used, the lock is placed on the same range of bytes that is used
-** for probabilistic locking in Win95/98/ME. Hence, the locking scheme
-** will support two or more Win95 readers or two or more WinNT readers.
-** But a single Win95 reader will lock out all WinNT readers and a single
-** WinNT reader will lock out all other Win95 readers.
-**
-** The following #defines specify the range of bytes used for locking.
-** SHARED_SIZE is the number of bytes available in the pool from which
-** a random byte is selected for a shared lock. The pool of bytes for
-** shared locks begins at SHARED_FIRST.
-**
-** The same locking strategy and
-** byte ranges are used for Unix. This leaves open the possibility of having
-** clients on win95, winNT, and unix all talking to the same shared file
-** and all locking correctly. To do so would require that samba (or whatever
-** tool is being used for file sharing) implements locks correctly between
-** windows and unix. I'm guessing that isn't likely to happen, but by
-** using the same locking range we are at least open to the possibility.
-**
-** Locking in windows is manditory. For this reason, we cannot store
-** actual data in the bytes used for locking. The pager never allocates
-** the pages involved in locking therefore. SHARED_SIZE is selected so
-** that all locks will fit on a single page even at the minimum page size.
-** PENDING_BYTE defines the beginning of the locks. By default PENDING_BYTE
-** is set high so that we don't have to allocate an unused page except
-** for very large databases. But one should test the page skipping logic
-** by setting PENDING_BYTE low and running the entire regression suite.
-**
-** Changing the value of PENDING_BYTE results in a subtly incompatible
-** file format. Depending on how it is changed, you might not notice
-** the incompatibility right away, even running a full regression test.
-** The default location of PENDING_BYTE is the first byte past the
-** 1GB boundary.
-**
-*/
-#ifdef SQLITE_OMIT_WSD
-# define PENDING_BYTE (0x40000000)
-#else
-# define PENDING_BYTE sqlite3PendingByte
-#endif
-#define RESERVED_BYTE (PENDING_BYTE+1)
-#define SHARED_FIRST (PENDING_BYTE+2)
-#define SHARED_SIZE 510
-
-/*
-** Wrapper around OS specific sqlite3_os_init() function.
-*/
-SQLITE_PRIVATE int sqlite3OsInit(void);
-
-/*
-** Functions for accessing sqlite3_file methods
-*/
-SQLITE_PRIVATE void sqlite3OsClose(sqlite3_file*);
-SQLITE_PRIVATE int sqlite3OsRead(sqlite3_file*, void*, int amt, i64 offset);
-SQLITE_PRIVATE int sqlite3OsWrite(sqlite3_file*, const void*, int amt, i64 offset);
-SQLITE_PRIVATE int sqlite3OsTruncate(sqlite3_file*, i64 size);
-SQLITE_PRIVATE int sqlite3OsSync(sqlite3_file*, int);
-SQLITE_PRIVATE int sqlite3OsFileSize(sqlite3_file*, i64 *pSize);
-SQLITE_PRIVATE int sqlite3OsLock(sqlite3_file*, int);
-SQLITE_PRIVATE int sqlite3OsUnlock(sqlite3_file*, int);
-SQLITE_PRIVATE int sqlite3OsCheckReservedLock(sqlite3_file *id, int *pResOut);
-SQLITE_PRIVATE int sqlite3OsFileControl(sqlite3_file*,int,void*);
-SQLITE_PRIVATE void sqlite3OsFileControlHint(sqlite3_file*,int,void*);
-#define SQLITE_FCNTL_DB_UNCHANGED 0xca093fa0
-SQLITE_PRIVATE int sqlite3OsSectorSize(sqlite3_file *id);
-SQLITE_PRIVATE int sqlite3OsDeviceCharacteristics(sqlite3_file *id);
-#ifndef SQLITE_OMIT_WAL
-SQLITE_PRIVATE int sqlite3OsShmMap(sqlite3_file *,int,int,int,void volatile **);
-SQLITE_PRIVATE int sqlite3OsShmLock(sqlite3_file *id, int, int, int);
-SQLITE_PRIVATE void sqlite3OsShmBarrier(sqlite3_file *id);
-SQLITE_PRIVATE int sqlite3OsShmUnmap(sqlite3_file *id, int);
-#endif /* SQLITE_OMIT_WAL */
-SQLITE_PRIVATE int sqlite3OsFetch(sqlite3_file *id, i64, int, void **);
-SQLITE_PRIVATE int sqlite3OsUnfetch(sqlite3_file *, i64, void *);
-
-
-/*
-** Functions for accessing sqlite3_vfs methods
-*/
-SQLITE_PRIVATE int sqlite3OsOpen(sqlite3_vfs *, const char *, sqlite3_file*, int, int *);
-SQLITE_PRIVATE int sqlite3OsDelete(sqlite3_vfs *, const char *, int);
-SQLITE_PRIVATE int sqlite3OsAccess(sqlite3_vfs *, const char *, int, int *pResOut);
-SQLITE_PRIVATE int sqlite3OsFullPathname(sqlite3_vfs *, const char *, int, char *);
-#ifndef SQLITE_OMIT_LOAD_EXTENSION
-SQLITE_PRIVATE void *sqlite3OsDlOpen(sqlite3_vfs *, const char *);
-SQLITE_PRIVATE void sqlite3OsDlError(sqlite3_vfs *, int, char *);
-SQLITE_PRIVATE void (*sqlite3OsDlSym(sqlite3_vfs *, void *, const char *))(void);
-SQLITE_PRIVATE void sqlite3OsDlClose(sqlite3_vfs *, void *);
-#endif /* SQLITE_OMIT_LOAD_EXTENSION */
-SQLITE_PRIVATE int sqlite3OsRandomness(sqlite3_vfs *, int, char *);
-SQLITE_PRIVATE int sqlite3OsSleep(sqlite3_vfs *, int);
-SQLITE_PRIVATE int sqlite3OsGetLastError(sqlite3_vfs*);
-SQLITE_PRIVATE int sqlite3OsCurrentTimeInt64(sqlite3_vfs *, sqlite3_int64*);
-
-/*
-** Convenience functions for opening and closing files using
-** sqlite3_malloc() to obtain space for the file-handle structure.
-*/
-SQLITE_PRIVATE int sqlite3OsOpenMalloc(sqlite3_vfs *, const char *, sqlite3_file **, int,int*);
-SQLITE_PRIVATE void sqlite3OsCloseFree(sqlite3_file *);
-
-#endif /* _SQLITE_OS_H_ */
-
-/************** End of os.h **************************************************/
-/************** Continuing where we left off in sqliteInt.h ******************/
/************** Include mutex.h in the middle of sqliteInt.h *****************/
/************** Begin file mutex.h *******************************************/
/*
@@ -16698,6 +16851,7 @@ struct Lookaside {
#endif /* SQLITE_OMIT_TWOSIZE_LOOKASIDE */
void *pStart; /* First byte of available memory space */
void *pEnd; /* First byte past end of available space */
+ void *pTrueEnd; /* True value of pEnd, when db->pnBytesFreed!=0 */
};
struct LookasideSlot {
LookasideSlot *pNext; /* Next buffer in the list of free buffers */
@@ -17039,6 +17193,10 @@ struct sqlite3 {
#define SQLITE_BloomFilter 0x00080000 /* Use a Bloom filter on searches */
#define SQLITE_BloomPulldown 0x00100000 /* Run Bloom filters early */
#define SQLITE_BalancedMerge 0x00200000 /* Balance multi-way merges */
+#define SQLITE_ReleaseReg 0x00400000 /* Use OP_ReleaseReg for testing */
+#define SQLITE_FlttnUnionAll 0x00800000 /* Disable the UNION ALL flattener */
+ /* TH3 expects this value ^^^^^^^^^^ See flatten04.test */
+#define SQLITE_IndexedExpr 0x01000000 /* Pull exprs from index when able */
#define SQLITE_AllOpts 0xffffffff /* All optimizations */
/*
@@ -17141,7 +17299,7 @@ struct FuncDestructor {
#define SQLITE_FUNC_SLOCHNG 0x2000 /* "Slow Change". Value constant during a
** single query - might change over time */
#define SQLITE_FUNC_TEST 0x4000 /* Built-in testing functions */
-#define SQLITE_FUNC_OFFSET 0x8000 /* Built-in sqlite_offset() function */
+/* 0x8000 -- available for reuse */
#define SQLITE_FUNC_WINDOW 0x00010000 /* Built-in window-only function */
#define SQLITE_FUNC_INTERNAL 0x00040000 /* For use by NestedParse() only */
#define SQLITE_FUNC_DIRECT 0x00080000 /* Not for use in TRIGGERs or VIEWs */
@@ -17158,6 +17316,7 @@ struct FuncDestructor {
#define INLINEFUNC_expr_compare 3
#define INLINEFUNC_affinity 4
#define INLINEFUNC_iif 5
+#define INLINEFUNC_sqlite_offset 6
#define INLINEFUNC_unlikely 99 /* Default case */
/*
@@ -17384,6 +17543,7 @@ struct Column {
#define COLFLAG_NOTAVAIL 0x0080 /* STORED column not yet calculated */
#define COLFLAG_BUSY 0x0100 /* Blocks recursion on GENERATED columns */
#define COLFLAG_HASCOLL 0x0200 /* Has collating sequence name in zCnName */
+#define COLFLAG_NOEXPAND 0x0400 /* Omit this column when expanding "*" */
#define COLFLAG_GENERATED 0x0060 /* Combo: _STORED, _VIRTUAL */
#define COLFLAG_NOINSERT 0x0062 /* Combo: _HIDDEN, _STORED, _VIRTUAL */
@@ -17609,7 +17769,7 @@ struct Table {
#ifndef SQLITE_OMIT_VIRTUALTABLE
# define IsVirtual(X) ((X)->eTabType==TABTYP_VTAB)
# define ExprIsVtab(X) \
- ((X)->op==TK_COLUMN && (X)->y.pTab!=0 && (X)->y.pTab->eTabType==TABTYP_VTAB)
+ ((X)->op==TK_COLUMN && (X)->y.pTab->eTabType==TABTYP_VTAB)
#else
# define IsVirtual(X) 0
# define ExprIsVtab(X) 0
@@ -17790,6 +17950,11 @@ struct KeyInfo {
struct UnpackedRecord {
KeyInfo *pKeyInfo; /* Collation and sort-order information */
Mem *aMem; /* Values */
+ union {
+ char *z; /* Cache of aMem[0].z for vdbeRecordCompareString() */
+ i64 i; /* Cache of aMem[0].u.i for vdbeRecordCompareInt() */
+ } u;
+ int n; /* Cache of aMem[0].n used by vdbeRecordCompareString() */
u16 nField; /* Number of entries in apMem[] */
i8 default_rc; /* Comparison result if keys are equal */
u8 errCode; /* Error detected by xRecordCompare (CORRUPT or NOMEM) */
@@ -17821,10 +17986,22 @@ struct UnpackedRecord {
** The Index.onError field determines whether or not the indexed columns
** must be unique and what to do if they are not. When Index.onError=OE_None,
** it means this is not a unique index. Otherwise it is a unique index
-** and the value of Index.onError indicate the which conflict resolution
-** algorithm to employ whenever an attempt is made to insert a non-unique
+** and the value of Index.onError indicates which conflict resolution
+** algorithm to employ when an attempt is made to insert a non-unique
** element.
**
+** The colNotIdxed bitmask is used in combination with SrcItem.colUsed
+** for a fast test to see if an index can serve as a covering index.
+** colNotIdxed has a 1 bit for every column of the original table that
+** is *not* available in the index. Thus the expression
+** "colUsed & colNotIdxed" will be non-zero if the index is not a
+** covering index. The most significant bit of of colNotIdxed will always
+** be true (note-20221022-a). If a column beyond the 63rd column of the
+** table is used, the "colUsed & colNotIdxed" test will always be non-zero
+** and we have to assume either that the index is not covering, or use
+** an alternative (slower) algorithm to determine whether or not
+** the index is covering.
+**
** While parsing a CREATE TABLE or CREATE INDEX statement in order to
** generate VDBE code (as opposed to parsing one read from an sqlite_schema
** table as part of parsing an existing database schema), transient instances
@@ -17860,6 +18037,8 @@ struct Index {
unsigned bNoQuery:1; /* Do not use this index to optimize queries */
unsigned bAscKeyBug:1; /* True if the bba7b69f9849b5bf bug applies */
unsigned bHasVCol:1; /* Index references one or more VIRTUAL columns */
+ unsigned bHasExpr:1; /* Index contains an expression, either a literal
+ ** expression, or a reference to a VIRTUAL column */
#ifdef SQLITE_ENABLE_STAT4
int nSample; /* Number of elements in aSample[] */
int nSampleCol; /* Size of IndexSample.anEq[] and so on */
@@ -17868,7 +18047,7 @@ struct Index {
tRowcnt *aiRowEst; /* Non-logarithmic stat1 data for this index */
tRowcnt nRowEst0; /* Non-logarithmic number of rows in the index */
#endif
- Bitmask colNotIdxed; /* 0 for unindexed columns in pTab */
+ Bitmask colNotIdxed; /* Unindexed columns in pTab */
};
/*
@@ -18098,7 +18277,7 @@ struct Expr {
** TK_SELECT_COLUMN: column of the result vector */
i16 iAgg; /* Which entry in pAggInfo->aCol[] or ->aFunc[] */
union {
- int iRightJoinTable; /* If EP_FromJoin, the right table of the join */
+ int iJoin; /* If EP_OuterON or EP_InnerON, the right table */
int iOfst; /* else: start of token from start of statement */
} w;
AggInfo *pAggInfo; /* Used by TK_AGG_COLUMN and TK_AGG_FUNCTION */
@@ -18119,29 +18298,29 @@ struct Expr {
** EP_Agg == NC_HasAgg == SF_HasAgg
** EP_Win == NC_HasWin
*/
-#define EP_FromJoin 0x000001 /* Originates in ON/USING clause of outer join */
-#define EP_Distinct 0x000002 /* Aggregate function with DISTINCT keyword */
-#define EP_HasFunc 0x000004 /* Contains one or more functions of any kind */
-#define EP_FixedCol 0x000008 /* TK_Column with a known fixed value */
+#define EP_OuterON 0x000001 /* Originates in ON/USING clause of outer join */
+#define EP_InnerON 0x000002 /* Originates in ON/USING of an inner join */
+#define EP_Distinct 0x000004 /* Aggregate function with DISTINCT keyword */
+#define EP_HasFunc 0x000008 /* Contains one or more functions of any kind */
#define EP_Agg 0x000010 /* Contains one or more aggregate functions */
-#define EP_VarSelect 0x000020 /* pSelect is correlated, not constant */
-#define EP_DblQuoted 0x000040 /* token.z was originally in "..." */
-#define EP_InfixFunc 0x000080 /* True for an infix function: LIKE, GLOB, etc */
-#define EP_Collate 0x000100 /* Tree contains a TK_COLLATE operator */
-#define EP_Commuted 0x000200 /* Comparison operator has been commuted */
-#define EP_IntValue 0x000400 /* Integer value contained in u.iValue */
-#define EP_xIsSelect 0x000800 /* x.pSelect is valid (otherwise x.pList is) */
-#define EP_Skip 0x001000 /* Operator does not contribute to affinity */
-#define EP_Reduced 0x002000 /* Expr struct EXPR_REDUCEDSIZE bytes only */
-#define EP_TokenOnly 0x004000 /* Expr struct EXPR_TOKENONLYSIZE bytes only */
+#define EP_FixedCol 0x000020 /* TK_Column with a known fixed value */
+#define EP_VarSelect 0x000040 /* pSelect is correlated, not constant */
+#define EP_DblQuoted 0x000080 /* token.z was originally in "..." */
+#define EP_InfixFunc 0x000100 /* True for an infix function: LIKE, GLOB, etc */
+#define EP_Collate 0x000200 /* Tree contains a TK_COLLATE operator */
+#define EP_Commuted 0x000400 /* Comparison operator has been commuted */
+#define EP_IntValue 0x000800 /* Integer value contained in u.iValue */
+#define EP_xIsSelect 0x001000 /* x.pSelect is valid (otherwise x.pList is) */
+#define EP_Skip 0x002000 /* Operator does not contribute to affinity */
+#define EP_Reduced 0x004000 /* Expr struct EXPR_REDUCEDSIZE bytes only */
#define EP_Win 0x008000 /* Contains window functions */
-#define EP_MemToken 0x010000 /* Need to sqlite3DbFree() Expr.zToken */
-#define EP_IfNullRow 0x020000 /* The TK_IF_NULL_ROW opcode */
-#define EP_Unlikely 0x040000 /* unlikely() or likelihood() function */
-#define EP_ConstFunc 0x080000 /* A SQLITE_FUNC_CONSTANT or _SLOCHNG function */
-#define EP_CanBeNull 0x100000 /* Can be null despite NOT NULL constraint */
-#define EP_Subquery 0x200000 /* Tree contains a TK_SELECT operator */
- /* 0x400000 // Available */
+#define EP_TokenOnly 0x010000 /* Expr struct EXPR_TOKENONLYSIZE bytes only */
+ /* 0x020000 // Available for reuse */
+#define EP_IfNullRow 0x040000 /* The TK_IF_NULL_ROW opcode */
+#define EP_Unlikely 0x080000 /* unlikely() or likelihood() function */
+#define EP_ConstFunc 0x100000 /* A SQLITE_FUNC_CONSTANT or _SLOCHNG function */
+#define EP_CanBeNull 0x200000 /* Can be null despite NOT NULL constraint */
+#define EP_Subquery 0x400000 /* Tree contains a TK_SELECT operator */
#define EP_Leaf 0x800000 /* Expr.pLeft, .pRight, .u.pSelect all NULL */
#define EP_WinFunc 0x1000000 /* TK_FUNCTION with Expr.y.pWin set */
#define EP_Subrtn 0x2000000 /* Uses Expr.y.sub. TK_IN, _SELECT, or _EXISTS */
@@ -18164,8 +18343,8 @@ struct Expr {
#define ExprHasAllProperty(E,P) (((E)->flags&(P))==(P))
#define ExprSetProperty(E,P) (E)->flags|=(P)
#define ExprClearProperty(E,P) (E)->flags&=~(P)
-#define ExprAlwaysTrue(E) (((E)->flags&(EP_FromJoin|EP_IsTrue))==EP_IsTrue)
-#define ExprAlwaysFalse(E) (((E)->flags&(EP_FromJoin|EP_IsFalse))==EP_IsFalse)
+#define ExprAlwaysTrue(E) (((E)->flags&(EP_OuterON|EP_IsTrue))==EP_IsTrue)
+#define ExprAlwaysFalse(E) (((E)->flags&(EP_OuterON|EP_IsFalse))==EP_IsFalse)
/* Macros used to ensure that the correct members of unions are accessed
** in Expr.
@@ -18252,12 +18431,18 @@ struct ExprList {
struct ExprList_item { /* For each expression in the list */
Expr *pExpr; /* The parse tree for this expression */
char *zEName; /* Token associated with this expression */
- u8 sortFlags; /* Mask of KEYINFO_ORDER_* flags */
- unsigned eEName :2; /* Meaning of zEName */
- unsigned done :1; /* A flag to indicate when processing is finished */
- unsigned reusable :1; /* Constant expression is reusable */
- unsigned bSorterRef :1; /* Defer evaluation until after sorting */
- unsigned bNulls: 1; /* True if explicit "NULLS FIRST/LAST" */
+ struct {
+ u8 sortFlags; /* Mask of KEYINFO_ORDER_* flags */
+ unsigned eEName :2; /* Meaning of zEName */
+ unsigned done :1; /* Indicates when processing is finished */
+ unsigned reusable :1; /* Constant expression is reusable */
+ unsigned bSorterRef :1; /* Defer evaluation until after sorting */
+ unsigned bNulls :1; /* True if explicit "NULLS FIRST/LAST" */
+ unsigned bUsed :1; /* This column used in a SF_NestedFrom subquery */
+ unsigned bUsingTerm:1; /* Term from the USING clause of a NestedFrom */
+ unsigned bNoExpand: 1; /* Term is an auxiliary in NestedFrom and should
+ ** not be expanded by "*" in parent queries */
+ } fg;
union {
struct { /* Used by any ExprList other than Parse.pConsExpr */
u16 iOrderByCol; /* For ORDER BY, column number in result set */
@@ -18292,17 +18477,37 @@ struct ExprList {
** If "a" is the k-th column of table "t", then IdList.a[0].idx==k.
*/
struct IdList {
+ int nId; /* Number of identifiers on the list */
+ u8 eU4; /* Which element of a.u4 is valid */
struct IdList_item {
char *zName; /* Name of the identifier */
- int idx; /* Index in some Table.aCol[] of a column named zName */
- } *a;
- int nId; /* Number of identifiers on the list */
+ union {
+ int idx; /* Index in some Table.aCol[] of a column named zName */
+ Expr *pExpr; /* Expr to implement a USING variable -- NOT USED */
+ } u4;
+ } a[1];
};
/*
+** Allowed values for IdList.eType, which determines which value of the a.u4
+** is valid.
+*/
+#define EU4_NONE 0 /* Does not use IdList.a.u4 */
+#define EU4_IDX 1 /* Uses IdList.a.u4.idx */
+#define EU4_EXPR 2 /* Uses IdList.a.u4.pExpr -- NOT CURRENTLY USED */
+
+/*
** The SrcItem object represents a single term in the FROM clause of a query.
** The SrcList object is mostly an array of SrcItems.
**
+** The jointype starts out showing the join type between the current table
+** and the next table on the list. The parser builds the list this way.
+** But sqlite3SrcListShiftJoinType() later shifts the jointypes so that each
+** jointype expresses the join between the table and the previous table.
+**
+** In the colUsed field, the high-order bit (bit 63) is set if the table
+** contains more than 63 columns and the 64-th or later column is used.
+**
** Union member validity:
**
** u1.zIndexedBy fg.isIndexedBy && !fg.isTabFunc
@@ -18326,44 +18531,48 @@ struct SrcItem {
unsigned isIndexedBy :1; /* True if there is an INDEXED BY clause */
unsigned isTabFunc :1; /* True if table-valued-function syntax */
unsigned isCorrelated :1; /* True if sub-query is correlated */
+ unsigned isMaterialized:1; /* This is a materialized view */
unsigned viaCoroutine :1; /* Implemented as a co-routine */
unsigned isRecursive :1; /* True for recursive reference in WITH */
unsigned fromDDL :1; /* Comes from sqlite_schema */
unsigned isCte :1; /* This is a CTE */
unsigned notCte :1; /* This item may not match a CTE */
+ unsigned isUsing :1; /* u3.pUsing is valid */
+ unsigned isOn :1; /* u3.pOn was once valid and non-NULL */
+ unsigned isSynthUsing :1; /* u3.pUsing is synthensized from NATURAL */
+ unsigned isNestedFrom :1; /* pSelect is a SF_NestedFrom subquery */
} fg;
int iCursor; /* The VDBE cursor number used to access this table */
- Expr *pOn; /* The ON clause of a join */
- IdList *pUsing; /* The USING clause of a join */
- Bitmask colUsed; /* Bit N (1<<N) set if column N of pTab is used */
+ union {
+ Expr *pOn; /* fg.isUsing==0 => The ON clause of a join */
+ IdList *pUsing; /* fg.isUsing==1 => The USING clause of a join */
+ } u3;
+ Bitmask colUsed; /* Bit N set if column N used. Details above for N>62 */
union {
char *zIndexedBy; /* Identifier from "INDEXED BY <zIndex>" clause */
ExprList *pFuncArg; /* Arguments to table-valued-function */
} u1;
union {
Index *pIBIndex; /* Index structure corresponding to u1.zIndexedBy */
- CteUse *pCteUse; /* CTE Usage info info fg.isCte is true */
+ CteUse *pCteUse; /* CTE Usage info when fg.isCte is true */
} u2;
};
/*
-** The following structure describes the FROM clause of a SELECT statement.
-** Each table or subquery in the FROM clause is a separate element of
-** the SrcList.a[] array.
-**
-** With the addition of multiple database support, the following structure
-** can also be used to describe a particular table such as the table that
-** is modified by an INSERT, DELETE, or UPDATE statement. In standard SQL,
-** such a table must be a simple name: ID. But in SQLite, the table can
-** now be identified by a database name, a dot, then the table name: ID.ID.
-**
-** The jointype starts out showing the join type between the current table
-** and the next table on the list. The parser builds the list this way.
-** But sqlite3SrcListShiftJoinType() later shifts the jointypes so that each
-** jointype expresses the join between the table and the previous table.
+** The OnOrUsing object represents either an ON clause or a USING clause.
+** It can never be both at the same time, but it can be neither.
+*/
+struct OnOrUsing {
+ Expr *pOn; /* The ON clause of a join */
+ IdList *pUsing; /* The USING clause of a join */
+};
+
+/*
+** This object represents one or more tables that are the source of
+** content for an SQL statement. For example, a single SrcList object
+** is used to hold the FROM clause of a SELECT statement. SrcList also
+** represents the target tables for DELETE, INSERT, and UPDATE statements.
**
-** In the colUsed field, the high-order bit (bit 63) is set if the table
-** contains more than 63 columns and the 64-th or later column is used.
*/
struct SrcList {
int nSrc; /* Number of tables or subqueries in the FROM clause */
@@ -18374,14 +18583,15 @@ struct SrcList {
/*
** Permitted values of the SrcList.a.jointype field
*/
-#define JT_INNER 0x0001 /* Any kind of inner or cross join */
-#define JT_CROSS 0x0002 /* Explicit use of the CROSS keyword */
-#define JT_NATURAL 0x0004 /* True for a "natural" join */
-#define JT_LEFT 0x0008 /* Left outer join */
-#define JT_RIGHT 0x0010 /* Right outer join */
-#define JT_OUTER 0x0020 /* The "OUTER" keyword is present */
-#define JT_ERROR 0x0040 /* unknown or unsupported join type */
-
+#define JT_INNER 0x01 /* Any kind of inner or cross join */
+#define JT_CROSS 0x02 /* Explicit use of the CROSS keyword */
+#define JT_NATURAL 0x04 /* True for a "natural" join */
+#define JT_LEFT 0x08 /* Left outer join */
+#define JT_RIGHT 0x10 /* Right outer join */
+#define JT_OUTER 0x20 /* The "OUTER" keyword is present */
+#define JT_LTORJ 0x40 /* One of the LEFT operands of a RIGHT JOIN
+ ** Mnemonic: Left Table Of Right Join */
+#define JT_ERROR 0x80 /* unknown or unsupported join type */
/*
** Flags appropriate for the wctrlFlags parameter of sqlite3WhereBegin()
@@ -18404,7 +18614,7 @@ struct SrcList {
#define WHERE_SORTBYGROUP 0x0200 /* Support sqlite3WhereIsSorted() */
#define WHERE_AGG_DISTINCT 0x0400 /* Query is "SELECT agg(DISTINCT ...)" */
#define WHERE_ORDERBY_LIMIT 0x0800 /* ORDERBY+LIMIT on the inner loop */
- /* 0x1000 not currently used */
+#define WHERE_RIGHT_JOIN 0x1000 /* Processing a RIGHT JOIN */
/* 0x2000 not currently used */
#define WHERE_USE_LIMIT 0x4000 /* Use the LIMIT in cost estimates */
/* 0x8000 not currently used */
@@ -18600,6 +18810,9 @@ struct Select {
#define SF_CopyCte 0x4000000 /* SELECT statement is a copy of a CTE */
#define SF_OrderByReqd 0x8000000 /* The ORDER BY clause may not be omitted */
+/* True if S exists and has SF_NestedFrom */
+#define IsNestedFrom(S) ((S)!=0 && ((S)->selFlags&SF_NestedFrom)!=0)
+
/*
** The results of a SELECT can be distributed in several ways, as defined
** by one of the following macros. The "SRT" prefix means "SELECT Result
@@ -18704,7 +18917,7 @@ struct SelectDest {
int iSDParm2; /* A second parameter for the eDest disposal method */
int iSdst; /* Base register where results are written */
int nSdst; /* Number of registers allocated */
- char *zAffSdst; /* Affinity used when eDest==SRT_Set */
+ char *zAffSdst; /* Affinity used for SRT_Set, SRT_Table, and similar */
ExprList *pOrderBy; /* Key columns for SRT_Queue and SRT_DistQueue */
};
@@ -18770,6 +18983,28 @@ struct TriggerPrg {
#endif
/*
+** For each index X that has as one of its arguments either an expression
+** or the name of a virtual generated column, and if X is in scope such that
+** the value of the expression can simply be read from the index, then
+** there is an instance of this object on the Parse.pIdxExpr list.
+**
+** During code generation, while generating code to evaluate expressions,
+** this list is consulted and if a matching expression is found, the value
+** is read from the index rather than being recomputed.
+*/
+struct IndexedExpr {
+ Expr *pExpr; /* The expression contained in the index */
+ int iDataCur; /* The data cursor associated with the index */
+ int iIdxCur; /* The index cursor */
+ int iIdxCol; /* The index column that contains value of pExpr */
+ u8 bMaybeNullRow; /* True if we need an OP_IfNullRow check */
+ IndexedExpr *pIENext; /* Next in a list of all indexed expressions */
+#ifdef SQLITE_ENABLE_EXPLAIN_COMMENTS
+ const char *zIdxName; /* Name of index, used only for bytecode comments */
+#endif
+};
+
+/*
** An instance of the ParseCleanup object specifies an operation that
** should be performed after parsing to deallocation resources obtained
** during the parse and which are no longer needed.
@@ -18810,7 +19045,8 @@ struct Parse {
u8 hasCompound; /* Need to invoke convertCompoundSelectToSubquery() */
u8 okConstFactor; /* OK to factor out constants */
u8 disableLookaside; /* Number of times lookaside has been disabled */
- u8 disableVtab; /* Disable all virtual tables for this parse */
+ u8 prepFlags; /* SQLITE_PREPARE_* flags */
+ u8 withinRJSubrtn; /* Nesting level for RIGHT JOIN body subroutines */
#if defined(SQLITE_DEBUG) || defined(SQLITE_COVERAGE_TEST)
u8 earlyCleanup; /* OOM inside sqlite3ParserAddCleanup() */
#endif
@@ -18826,6 +19062,7 @@ struct Parse {
int nLabelAlloc; /* Number of slots in aLabel */
int *aLabel; /* Space to hold the labels */
ExprList *pConstExpr;/* Constant expressions */
+ IndexedExpr *pIdxExpr;/* List of expressions used by active indexes */
Token constraintName;/* Name of the constraint currently being parsed */
yDbMask writeMask; /* Start a write transaction on these databases */
yDbMask cookieMask; /* Bitmask of schema verified databases */
@@ -18983,20 +19220,20 @@ struct AuthContext {
#define OPFLAG_PREFORMAT 0x80 /* OP_Insert uses preformatted cell */
/*
- * Each trigger present in the database schema is stored as an instance of
- * struct Trigger.
- *
- * Pointers to instances of struct Trigger are stored in two ways.
- * 1. In the "trigHash" hash table (part of the sqlite3* that represents the
- * database). This allows Trigger structures to be retrieved by name.
- * 2. All triggers associated with a single table form a linked list, using the
- * pNext member of struct Trigger. A pointer to the first element of the
- * linked list is stored as the "pTrigger" member of the associated
- * struct Table.
- *
- * The "step_list" member points to the first element of a linked list
- * containing the SQL statements specified as the trigger program.
- */
+** Each trigger present in the database schema is stored as an instance of
+** struct Trigger.
+**
+** Pointers to instances of struct Trigger are stored in two ways.
+** 1. In the "trigHash" hash table (part of the sqlite3* that represents the
+** database). This allows Trigger structures to be retrieved by name.
+** 2. All triggers associated with a single table form a linked list, using the
+** pNext member of struct Trigger. A pointer to the first element of the
+** linked list is stored as the "pTrigger" member of the associated
+** struct Table.
+**
+** The "step_list" member points to the first element of a linked list
+** containing the SQL statements specified as the trigger program.
+*/
struct Trigger {
char *zName; /* The name of the trigger */
char *table; /* The table or view to which the trigger applies */
@@ -19023,43 +19260,48 @@ struct Trigger {
#define TRIGGER_AFTER 2
/*
- * An instance of struct TriggerStep is used to store a single SQL statement
- * that is a part of a trigger-program.
- *
- * Instances of struct TriggerStep are stored in a singly linked list (linked
- * using the "pNext" member) referenced by the "step_list" member of the
- * associated struct Trigger instance. The first element of the linked list is
- * the first step of the trigger-program.
- *
- * The "op" member indicates whether this is a "DELETE", "INSERT", "UPDATE" or
- * "SELECT" statement. The meanings of the other members is determined by the
- * value of "op" as follows:
- *
- * (op == TK_INSERT)
- * orconf -> stores the ON CONFLICT algorithm
- * pSelect -> If this is an INSERT INTO ... SELECT ... statement, then
- * this stores a pointer to the SELECT statement. Otherwise NULL.
- * zTarget -> Dequoted name of the table to insert into.
- * pExprList -> If this is an INSERT INTO ... VALUES ... statement, then
- * this stores values to be inserted. Otherwise NULL.
- * pIdList -> If this is an INSERT INTO ... (<column-names>) VALUES ...
- * statement, then this stores the column-names to be
- * inserted into.
- *
- * (op == TK_DELETE)
- * zTarget -> Dequoted name of the table to delete from.
- * pWhere -> The WHERE clause of the DELETE statement if one is specified.
- * Otherwise NULL.
- *
- * (op == TK_UPDATE)
- * zTarget -> Dequoted name of the table to update.
- * pWhere -> The WHERE clause of the UPDATE statement if one is specified.
- * Otherwise NULL.
- * pExprList -> A list of the columns to update and the expressions to update
- * them to. See sqlite3Update() documentation of "pChanges"
- * argument.
- *
- */
+** An instance of struct TriggerStep is used to store a single SQL statement
+** that is a part of a trigger-program.
+**
+** Instances of struct TriggerStep are stored in a singly linked list (linked
+** using the "pNext" member) referenced by the "step_list" member of the
+** associated struct Trigger instance. The first element of the linked list is
+** the first step of the trigger-program.
+**
+** The "op" member indicates whether this is a "DELETE", "INSERT", "UPDATE" or
+** "SELECT" statement. The meanings of the other members is determined by the
+** value of "op" as follows:
+**
+** (op == TK_INSERT)
+** orconf -> stores the ON CONFLICT algorithm
+** pSelect -> The content to be inserted - either a SELECT statement or
+** a VALUES clause.
+** zTarget -> Dequoted name of the table to insert into.
+** pIdList -> If this is an INSERT INTO ... (<column-names>) VALUES ...
+** statement, then this stores the column-names to be
+** inserted into.
+** pUpsert -> The ON CONFLICT clauses for an Upsert
+**
+** (op == TK_DELETE)
+** zTarget -> Dequoted name of the table to delete from.
+** pWhere -> The WHERE clause of the DELETE statement if one is specified.
+** Otherwise NULL.
+**
+** (op == TK_UPDATE)
+** zTarget -> Dequoted name of the table to update.
+** pWhere -> The WHERE clause of the UPDATE statement if one is specified.
+** Otherwise NULL.
+** pExprList -> A list of the columns to update and the expressions to update
+** them to. See sqlite3Update() documentation of "pChanges"
+** argument.
+**
+** (op == TK_SELECT)
+** pSelect -> The SELECT statement
+**
+** (op == TK_RETURNING)
+** pExprList -> The list of expressions that follow the RETURNING keyword.
+**
+*/
struct TriggerStep {
u8 op; /* One of TK_DELETE, TK_UPDATE, TK_INSERT, TK_SELECT,
** or TK_RETURNING */
@@ -19256,15 +19498,15 @@ struct Walker {
struct RefSrcList *pRefSrcList; /* sqlite3ReferencesSrcList() */
int *aiCol; /* array of column indexes */
struct IdxCover *pIdxCover; /* Check for index coverage */
- struct IdxExprTrans *pIdxTrans; /* Convert idxed expr to column */
ExprList *pGroupBy; /* GROUP BY clause */
Select *pSelect; /* HAVING to WHERE clause ctx */
struct WindowRewrite *pRewrite; /* Window rewrite context */
struct WhereConst *pConst; /* WHERE clause constants */
struct RenameCtx *pRename; /* RENAME COLUMN context */
struct Table *pTab; /* Table of generated column */
+ struct CoveringIndexCheck *pCovIdxCk; /* Check for covering index */
SrcItem *pSrcItem; /* A single FROM clause item */
- DbFixer *pFix;
+ DbFixer *pFix; /* See sqlite3FixSelect() */
} u;
};
@@ -19414,7 +19656,7 @@ struct Window {
Window **ppThis; /* Pointer to this object in Select.pWin list */
Window *pNextWin; /* Next window function belonging to this SELECT */
Expr *pFilter; /* The FILTER expression */
- FuncDef *pFunc; /* The function */
+ FuncDef *pWFunc; /* The function */
int iEphCsr; /* Partition buffer or Peer buffer */
int regAccum; /* Accumulator */
int regResult; /* Interim result */
@@ -19570,6 +19812,7 @@ SQLITE_PRIVATE void *sqlite3DbReallocOrFree(sqlite3 *, void *, u64);
SQLITE_PRIVATE void *sqlite3DbRealloc(sqlite3 *, void *, u64);
SQLITE_PRIVATE void sqlite3DbFree(sqlite3*, void*);
SQLITE_PRIVATE void sqlite3DbFreeNN(sqlite3*, void*);
+SQLITE_PRIVATE void sqlite3DbNNFreeNN(sqlite3*, void*);
SQLITE_PRIVATE int sqlite3MallocSize(const void*);
SQLITE_PRIVATE int sqlite3DbMallocSize(sqlite3*, const void*);
SQLITE_PRIVATE void *sqlite3PageMalloc(int);
@@ -19590,12 +19833,16 @@ SQLITE_PRIVATE int sqlite3HeapNearlyFull(void);
*/
#ifdef SQLITE_USE_ALLOCA
# define sqlite3StackAllocRaw(D,N) alloca(N)
+# define sqlite3StackAllocRawNN(D,N) alloca(N)
# define sqlite3StackAllocZero(D,N) memset(alloca(N), 0, N)
# define sqlite3StackFree(D,P)
+# define sqlite3StackFreeNN(D,P)
#else
# define sqlite3StackAllocRaw(D,N) sqlite3DbMallocRaw(D,N)
+# define sqlite3StackAllocRawNN(D,N) sqlite3DbMallocRawNN(D,N)
# define sqlite3StackAllocZero(D,N) sqlite3DbMallocZero(D,N)
# define sqlite3StackFree(D,P) sqlite3DbFree(D,P)
+# define sqlite3StackFreeNN(D,P) sqlite3DbFreeNN(D,P)
#endif
/* Do not allow both MEMSYS5 and MEMSYS3 to be defined together. If they
@@ -19669,18 +19916,53 @@ SQLITE_PRIVATE void *sqlite3TestTextToPtr(const char*);
#endif
#if defined(SQLITE_DEBUG)
+SQLITE_PRIVATE void sqlite3TreeViewLine(TreeView*, const char *zFormat, ...);
SQLITE_PRIVATE void sqlite3TreeViewExpr(TreeView*, const Expr*, u8);
SQLITE_PRIVATE void sqlite3TreeViewBareExprList(TreeView*, const ExprList*, const char*);
SQLITE_PRIVATE void sqlite3TreeViewExprList(TreeView*, const ExprList*, u8, const char*);
+SQLITE_PRIVATE void sqlite3TreeViewBareIdList(TreeView*, const IdList*, const char*);
+SQLITE_PRIVATE void sqlite3TreeViewIdList(TreeView*, const IdList*, u8, const char*);
+SQLITE_PRIVATE void sqlite3TreeViewColumnList(TreeView*, const Column*, int, u8);
SQLITE_PRIVATE void sqlite3TreeViewSrcList(TreeView*, const SrcList*);
SQLITE_PRIVATE void sqlite3TreeViewSelect(TreeView*, const Select*, u8);
SQLITE_PRIVATE void sqlite3TreeViewWith(TreeView*, const With*, u8);
+SQLITE_PRIVATE void sqlite3TreeViewUpsert(TreeView*, const Upsert*, u8);
+#if TREETRACE_ENABLED
+SQLITE_PRIVATE void sqlite3TreeViewDelete(const With*, const SrcList*, const Expr*,
+ const ExprList*,const Expr*, const Trigger*);
+SQLITE_PRIVATE void sqlite3TreeViewInsert(const With*, const SrcList*,
+ const IdList*, const Select*, const ExprList*,
+ int, const Upsert*, const Trigger*);
+SQLITE_PRIVATE void sqlite3TreeViewUpdate(const With*, const SrcList*, const ExprList*,
+ const Expr*, int, const ExprList*, const Expr*,
+ const Upsert*, const Trigger*);
+#endif
+#ifndef SQLITE_OMIT_TRIGGER
+SQLITE_PRIVATE void sqlite3TreeViewTriggerStep(TreeView*, const TriggerStep*, u8, u8);
+SQLITE_PRIVATE void sqlite3TreeViewTrigger(TreeView*, const Trigger*, u8, u8);
+#endif
#ifndef SQLITE_OMIT_WINDOWFUNC
SQLITE_PRIVATE void sqlite3TreeViewWindow(TreeView*, const Window*, u8);
SQLITE_PRIVATE void sqlite3TreeViewWinFunc(TreeView*, const Window*, u8);
#endif
+SQLITE_PRIVATE void sqlite3ShowExpr(const Expr*);
+SQLITE_PRIVATE void sqlite3ShowExprList(const ExprList*);
+SQLITE_PRIVATE void sqlite3ShowIdList(const IdList*);
+SQLITE_PRIVATE void sqlite3ShowSrcList(const SrcList*);
+SQLITE_PRIVATE void sqlite3ShowSelect(const Select*);
+SQLITE_PRIVATE void sqlite3ShowWith(const With*);
+SQLITE_PRIVATE void sqlite3ShowUpsert(const Upsert*);
+#ifndef SQLITE_OMIT_TRIGGER
+SQLITE_PRIVATE void sqlite3ShowTriggerStep(const TriggerStep*);
+SQLITE_PRIVATE void sqlite3ShowTriggerStepList(const TriggerStep*);
+SQLITE_PRIVATE void sqlite3ShowTrigger(const Trigger*);
+SQLITE_PRIVATE void sqlite3ShowTriggerList(const Trigger*);
+#endif
+#ifndef SQLITE_OMIT_WINDOWFUNC
+SQLITE_PRIVATE void sqlite3ShowWindow(const Window*);
+SQLITE_PRIVATE void sqlite3ShowWinFunc(const Window*);
+#endif
#endif
-
SQLITE_PRIVATE void sqlite3SetString(char **, sqlite3*, const char*);
SQLITE_PRIVATE void sqlite3ErrorMsg(Parse*, const char*, ...);
@@ -19829,13 +20111,14 @@ SQLITE_PRIVATE SrcList *sqlite3SrcListEnlarge(Parse*, SrcList*, int, int);
SQLITE_PRIVATE SrcList *sqlite3SrcListAppendList(Parse *pParse, SrcList *p1, SrcList *p2);
SQLITE_PRIVATE SrcList *sqlite3SrcListAppend(Parse*, SrcList*, Token*, Token*);
SQLITE_PRIVATE SrcList *sqlite3SrcListAppendFromTerm(Parse*, SrcList*, Token*, Token*,
- Token*, Select*, Expr*, IdList*);
+ Token*, Select*, OnOrUsing*);
SQLITE_PRIVATE void sqlite3SrcListIndexedBy(Parse *, SrcList *, Token *);
SQLITE_PRIVATE void sqlite3SrcListFuncArgs(Parse*, SrcList*, ExprList*);
SQLITE_PRIVATE int sqlite3IndexedByLookup(Parse *, SrcItem *);
-SQLITE_PRIVATE void sqlite3SrcListShiftJoinType(SrcList*);
+SQLITE_PRIVATE void sqlite3SrcListShiftJoinType(Parse*,SrcList*);
SQLITE_PRIVATE void sqlite3SrcListAssignCursors(Parse*, SrcList*);
SQLITE_PRIVATE void sqlite3IdListDelete(sqlite3*, IdList*);
+SQLITE_PRIVATE void sqlite3ClearOnOrUsing(sqlite3*, OnOrUsing*);
SQLITE_PRIVATE void sqlite3SrcListDelete(sqlite3*, SrcList*);
SQLITE_PRIVATE Index *sqlite3AllocateIndexObject(sqlite3*,i16,int,char**);
SQLITE_PRIVATE void sqlite3CreateIndex(Parse*,Token*,Token*,SrcList*,ExprList*,int,Token*,
@@ -20033,7 +20316,8 @@ SQLITE_PRIVATE SrcList *sqlite3TriggerStepSrc(Parse*, TriggerStep*);
SQLITE_PRIVATE int sqlite3JoinType(Parse*, Token*, Token*, Token*);
SQLITE_PRIVATE int sqlite3ColumnIndex(Table *pTab, const char *zCol);
-SQLITE_PRIVATE void sqlite3SetJoinExpr(Expr*,int);
+SQLITE_PRIVATE void sqlite3SrcItemColumnUsed(SrcItem*,int);
+SQLITE_PRIVATE void sqlite3SetJoinExpr(Expr*,int,u32);
SQLITE_PRIVATE void sqlite3CreateForeignKey(Parse*, ExprList*, Token*, ExprList*, int);
SQLITE_PRIVATE void sqlite3DeferForeignKey(Parse*, int);
#ifndef SQLITE_OMIT_AUTHORIZATION
@@ -20057,6 +20341,7 @@ SQLITE_PRIVATE int sqlite3FixSelect(DbFixer*, Select*);
SQLITE_PRIVATE int sqlite3FixExpr(DbFixer*, Expr*);
SQLITE_PRIVATE int sqlite3FixTriggerStep(DbFixer*, TriggerStep*);
SQLITE_PRIVATE int sqlite3RealSameAsInt(double,sqlite3_int64);
+SQLITE_PRIVATE i64 sqlite3RealToI64(double);
SQLITE_PRIVATE void sqlite3Int64ToText(i64,char*);
SQLITE_PRIVATE int sqlite3AtoF(const char *z, double*, int, u8);
SQLITE_PRIVATE int sqlite3GetInt32(const char *, int*);
@@ -20102,6 +20387,7 @@ SQLITE_PRIVATE int sqlite3VarintLen(u64 v);
SQLITE_PRIVATE const char *sqlite3IndexAffinityStr(sqlite3*, Index*);
+SQLITE_PRIVATE char *sqlite3TableAffinityStr(sqlite3*,const Table*);
SQLITE_PRIVATE void sqlite3TableAffinity(Vdbe*, Table*, int);
SQLITE_PRIVATE char sqlite3CompareAffinity(const Expr *pExpr, char aff2);
SQLITE_PRIVATE int sqlite3IndexAffinityOk(const Expr *pExpr, char idx_affinity);
@@ -20173,7 +20459,6 @@ SQLITE_PRIVATE const unsigned char sqlite3OpcodeProperty[];
SQLITE_PRIVATE const char sqlite3StrBINARY[];
SQLITE_PRIVATE const unsigned char sqlite3StdTypeLen[];
SQLITE_PRIVATE const char sqlite3StdTypeAffinity[];
-SQLITE_PRIVATE const char sqlite3StdTypeMap[];
SQLITE_PRIVATE const char *sqlite3StdType[];
SQLITE_PRIVATE const unsigned char sqlite3UpperToLower[];
SQLITE_PRIVATE const unsigned char *sqlite3aLTb;
@@ -20379,7 +20664,7 @@ SQLITE_PRIVATE int sqlite3VtabBegin(sqlite3 *, VTable *);
SQLITE_PRIVATE FuncDef *sqlite3VtabOverloadFunction(sqlite3 *,FuncDef*, int nArg, Expr*);
#if (defined(SQLITE_ENABLE_DBPAGE_VTAB) || defined(SQLITE_TEST)) \
&& !defined(SQLITE_OMIT_VIRTUALTABLE)
-SQLITE_PRIVATE void sqlite3VtabWriteAll(sqlite3_index_info*);
+SQLITE_PRIVATE void sqlite3VtabUsesAllSchemas(sqlite3_index_info*);
#endif
SQLITE_PRIVATE sqlite3_int64 sqlite3StmtCurrentTime(sqlite3_context*);
SQLITE_PRIVATE int sqlite3VdbeParameterIndex(Vdbe*, const char*, int);
@@ -20617,6 +20902,10 @@ SQLITE_PRIVATE void sqlite3VectorErrorMsg(Parse*, Expr*);
SQLITE_PRIVATE const char **sqlite3CompileOptions(int *pnOpt);
#endif
+#if SQLITE_OS_UNIX && defined(SQLITE_OS_KV_OPTIONAL)
+SQLITE_PRIVATE int sqlite3KvvfsInit(void);
+#endif
+
#endif /* SQLITEINT_H */
/************** End of sqliteInt.h *******************************************/
@@ -20848,7 +21137,7 @@ SQLITE_API extern int sqlite3_open_file_count;
** autoconf-based build
*/
#if defined(_HAVE_SQLITE_CONFIG_H) && !defined(SQLITECONFIG_H)
-/* #include "config.h" */
+/* #include "sqlite_cfg.h" */
#define SQLITECONFIG_H 1
#endif
@@ -21013,6 +21302,9 @@ static const char * const sqlite3azCompileOpt[] = {
#ifdef SQLITE_DISABLE_SKIPAHEAD_DISTINCT
"DISABLE_SKIPAHEAD_DISTINCT",
#endif
+#ifdef SQLITE_DQS
+ "DQS=" CTIMEOPT_VAL(SQLITE_DQS),
+#endif
#ifdef SQLITE_ENABLE_8_3_NAMES
"ENABLE_8_3_NAMES=" CTIMEOPT_VAL(SQLITE_ENABLE_8_3_NAMES),
#endif
@@ -21127,9 +21419,6 @@ static const char * const sqlite3azCompileOpt[] = {
#ifdef SQLITE_ENABLE_RTREE
"ENABLE_RTREE",
#endif
-#ifdef SQLITE_ENABLE_SELECTTRACE
- "ENABLE_SELECTTRACE",
-#endif
#ifdef SQLITE_ENABLE_SESSION
"ENABLE_SESSION",
#endif
@@ -21151,6 +21440,9 @@ static const char * const sqlite3azCompileOpt[] = {
#ifdef SQLITE_ENABLE_STMT_SCANSTATUS
"ENABLE_STMT_SCANSTATUS",
#endif
+#ifdef SQLITE_ENABLE_TREETRACE
+ "ENABLE_TREETRACE",
+#endif
#ifdef SQLITE_ENABLE_UNKNOWN_SQL_FUNCTION
"ENABLE_UNKNOWN_SQL_FUNCTION",
#endif
@@ -21503,9 +21795,6 @@ static const char * const sqlite3azCompileOpt[] = {
#ifdef SQLITE_OMIT_XFER_OPT
"OMIT_XFER_OPT",
#endif
-#ifdef SQLITE_PCACHE_SEPARATE_HEADER
- "PCACHE_SEPARATE_HEADER",
-#endif
#ifdef SQLITE_PERFORMANCE_TRACE
"PERFORMANCE_TRACE",
#endif
@@ -21900,6 +22189,9 @@ SQLITE_PRIVATE SQLITE_WSD struct Sqlite3Config sqlite3Config = {
0x7ffffffe, /* iOnceResetThreshold */
SQLITE_DEFAULT_SORTERREF_SIZE, /* szSorterRef */
0, /* iPrngSeed */
+#ifdef SQLITE_DEBUG
+ {0,0,0,0,0,0} /* aTune */
+#endif
};
/*
@@ -21954,7 +22246,7 @@ SQLITE_PRIVATE int sqlite3PendingByte = 0x40000000;
/*
** Tracing flags set by SQLITE_TESTCTRL_TRACEFLAGS.
*/
-SQLITE_PRIVATE u32 sqlite3SelectTrace = 0;
+SQLITE_PRIVATE u32 sqlite3TreeTrace = 0;
SQLITE_PRIVATE u32 sqlite3WhereTrace = 0;
/* #include "opcodes.h" */
@@ -21982,10 +22274,6 @@ SQLITE_PRIVATE const char sqlite3StrBINARY[] = "BINARY";
**
** sqlite3StdTypeAffinity[] The affinity associated with each entry
** in sqlite3StdType[].
-**
-** sqlite3StdTypeMap[] The type value (as returned from
-** sqlite3_column_type() or sqlite3_value_type())
-** for each entry in sqlite3StdType[].
*/
SQLITE_PRIVATE const unsigned char sqlite3StdTypeLen[] = { 3, 4, 3, 7, 4, 4 };
SQLITE_PRIVATE const char sqlite3StdTypeAffinity[] = {
@@ -21996,14 +22284,6 @@ SQLITE_PRIVATE const char sqlite3StdTypeAffinity[] = {
SQLITE_AFF_REAL,
SQLITE_AFF_TEXT
};
-SQLITE_PRIVATE const char sqlite3StdTypeMap[] = {
- 0,
- SQLITE_BLOB,
- SQLITE_INTEGER,
- SQLITE_INTEGER,
- SQLITE_FLOAT,
- SQLITE_TEXT
-};
SQLITE_PRIVATE const char *sqlite3StdType[] = {
"ANY",
"BLOB",
@@ -22121,7 +22401,7 @@ struct VdbeCursor {
Bool isEphemeral:1; /* True for an ephemeral table */
Bool useRandomRowid:1; /* Generate new record numbers semi-randomly */
Bool isOrdered:1; /* True if the table is not BTREE_UNORDERED */
- Bool hasBeenDuped:1; /* This cursor was source or target of OP_OpenDup */
+ Bool noReuse:1; /* OpenEphemeral may not reuse this cursor */
u16 seekHit; /* See the OP_SeekHit and OP_IfNoHope opcodes */
union { /* pBtx for isEphermeral. pAltMap otherwise */
Btree *pBtx; /* Separate file holding temporary table */
@@ -22169,6 +22449,11 @@ struct VdbeCursor {
u32 aType[1]; /* Type values record decode. MUST BE LAST */
};
+/* Return true if P is a null-only cursor
+*/
+#define IsNullCursor(P) \
+ ((P)->eCurType==CURTYPE_PSEUDO && (P)->nullRow && (P)->seekResult==0)
+
/*
** A value for VdbeCursor.cacheStatus that means the cache is always invalid.
@@ -22243,16 +22528,16 @@ struct sqlite3_value {
const char *zPType; /* Pointer type when MEM_Term|MEM_Subtype|MEM_Null */
FuncDef *pDef; /* Used only when flags==MEM_Agg */
} u;
+ char *z; /* String or BLOB value */
+ int n; /* Number of characters in string value, excluding '\0' */
u16 flags; /* Some combination of MEM_Null, MEM_Str, MEM_Dyn, etc. */
u8 enc; /* SQLITE_UTF8, SQLITE_UTF16BE, SQLITE_UTF16LE */
u8 eSubtype; /* Subtype for this value */
- int n; /* Number of characters in string value, excluding '\0' */
- char *z; /* String or BLOB value */
/* ShallowCopy only needs to copy the information above */
- char *zMalloc; /* Space to hold MEM_Str or MEM_Blob if szMalloc>0 */
+ sqlite3 *db; /* The associated database connection */
int szMalloc; /* Size of the zMalloc allocation */
u32 uTemp; /* Transient storage for serial_type in OP_MakeRecord */
- sqlite3 *db; /* The associated database connection */
+ char *zMalloc; /* Space to hold MEM_Str or MEM_Blob if szMalloc>0 */
void (*xDel)(void*);/* Destructor for Mem.z - only valid if MEM_Dyn */
#ifdef SQLITE_DEBUG
Mem *pScopyFrom; /* This Mem is a shallow copy of pScopyFrom */
@@ -22264,11 +22549,43 @@ struct sqlite3_value {
** Size of struct Mem not including the Mem.zMalloc member or anything that
** follows.
*/
-#define MEMCELLSIZE offsetof(Mem,zMalloc)
+#define MEMCELLSIZE offsetof(Mem,db)
-/* One or more of the following flags are set to indicate the validOK
+/* One or more of the following flags are set to indicate the
** representations of the value stored in the Mem struct.
**
+** * MEM_Null An SQL NULL value
+**
+** * MEM_Null|MEM_Zero An SQL NULL with the virtual table
+** UPDATE no-change flag set
+**
+** * MEM_Null|MEM_Term| An SQL NULL, but also contains a
+** MEM_Subtype pointer accessible using
+** sqlite3_value_pointer().
+**
+** * MEM_Null|MEM_Cleared Special SQL NULL that compares non-equal
+** to other NULLs even using the IS operator.
+**
+** * MEM_Str A string, stored in Mem.z with
+** length Mem.n. Zero-terminated if
+** MEM_Term is set. This flag is
+** incompatible with MEM_Blob and
+** MEM_Null, but can appear with MEM_Int,
+** MEM_Real, and MEM_IntReal.
+**
+** * MEM_Blob A blob, stored in Mem.z length Mem.n.
+** Incompatible with MEM_Str, MEM_Null,
+** MEM_Int, MEM_Real, and MEM_IntReal.
+**
+** * MEM_Blob|MEM_Zero A blob in Mem.z of length Mem.n plus
+** MEM.u.i extra 0x00 bytes at the end.
+**
+** * MEM_Int Integer stored in Mem.u.i.
+**
+** * MEM_Real Real stored in Mem.u.r.
+**
+** * MEM_IntReal Real stored as an integer in Mem.u.i.
+**
** If the MEM_Null flag is set, then the value is an SQL NULL value.
** For a pointer type created using sqlite3_bind_pointer() or
** sqlite3_result_pointer() the MEM_Term and MEM_Subtype flags are also set.
@@ -22279,6 +22596,7 @@ struct sqlite3_value {
** set, then the string is nul terminated. The MEM_Int and MEM_Real
** flags may coexist with the MEM_Str flag.
*/
+#define MEM_Undefined 0x0000 /* Value is undefined */
#define MEM_Null 0x0001 /* Value is NULL (or a pointer) */
#define MEM_Str 0x0002 /* Value is a string */
#define MEM_Int 0x0004 /* Value is an integer */
@@ -22286,28 +22604,24 @@ struct sqlite3_value {
#define MEM_Blob 0x0010 /* Value is a BLOB */
#define MEM_IntReal 0x0020 /* MEM_Int that stringifies like MEM_Real */
#define MEM_AffMask 0x003f /* Mask of affinity bits */
+
+/* Extra bits that modify the meanings of the core datatypes above
+*/
#define MEM_FromBind 0x0040 /* Value originates from sqlite3_bind() */
-#define MEM_Undefined 0x0080 /* Value is undefined */
+ /* 0x0080 // Available */
#define MEM_Cleared 0x0100 /* NULL set by OP_Null, not from data */
-#define MEM_TypeMask 0xc1bf /* Mask of type bits */
-
+#define MEM_Term 0x0200 /* String in Mem.z is zero terminated */
+#define MEM_Zero 0x0400 /* Mem.i contains count of 0s appended to blob */
+#define MEM_Subtype 0x0800 /* Mem.eSubtype is valid */
+#define MEM_TypeMask 0x0dbf /* Mask of type bits */
-/* Whenever Mem contains a valid string or blob representation, one of
-** the following flags must be set to determine the memory management
-** policy for Mem.z. The MEM_Term flag tells us whether or not the
-** string is \000 or \u0000 terminated
+/* Bits that determine the storage for Mem.z for a string or blob or
+** aggregate accumulator.
*/
-#define MEM_Term 0x0200 /* String in Mem.z is zero terminated */
-#define MEM_Dyn 0x0400 /* Need to call Mem.xDel() on Mem.z */
-#define MEM_Static 0x0800 /* Mem.z points to a static string */
-#define MEM_Ephem 0x1000 /* Mem.z points to an ephemeral string */
-#define MEM_Agg 0x2000 /* Mem.z points to an agg function context */
-#define MEM_Zero 0x4000 /* Mem.i contains count of 0s appended to blob */
-#define MEM_Subtype 0x8000 /* Mem.eSubtype is valid */
-#ifdef SQLITE_OMIT_INCRBLOB
- #undef MEM_Zero
- #define MEM_Zero 0x0000
-#endif
+#define MEM_Dyn 0x1000 /* Need to call Mem.xDel() on Mem.z */
+#define MEM_Static 0x2000 /* Mem.z points to a static string */
+#define MEM_Ephem 0x4000 /* Mem.z points to an ephemeral string */
+#define MEM_Agg 0x8000 /* Mem.z points to an agg function context */
/* Return TRUE if Mem X contains dynamically allocated content - anything
** that needs to be deallocated to avoid a leak.
@@ -22329,11 +22643,15 @@ struct sqlite3_value {
&& (X)->n==0 && (X)->u.nZero==0)
/*
-** Return true if a memory cell is not marked as invalid. This macro
+** Return true if a memory cell has been initialized and is valid.
** is for use inside assert() statements only.
+**
+** A Memory cell is initialized if at least one of the
+** MEM_Null, MEM_Str, MEM_Int, MEM_Real, MEM_Blob, or MEM_IntReal bits
+** is set. It is "undefined" if all those bits are zero.
*/
#ifdef SQLITE_DEBUG
-#define memIsValid(M) ((M)->flags & MEM_Undefined)==0
+#define memIsValid(M) ((M)->flags & MEM_AffMask)!=0
#endif
/*
@@ -22371,6 +22689,7 @@ struct sqlite3_context {
Vdbe *pVdbe; /* The VM that owns this context */
int iOp; /* Instruction number of OP_Function */
int isError; /* Error code returned by the function. */
+ u8 enc; /* Encoding to use for results */
u8 skipFlag; /* Skip accumulator loading if true */
u8 argc; /* Number of arguments */
sqlite3_value *argv[1]; /* Argument set */
@@ -22416,10 +22735,9 @@ struct DblquoteStr {
*/
struct Vdbe {
sqlite3 *db; /* The database connection that owns this statement */
- Vdbe *pPrev,*pNext; /* Linked list of VDBEs with the same Vdbe.db */
+ Vdbe **ppVPrev,*pVNext; /* Linked list of VDBEs with the same Vdbe.db */
Parse *pParse; /* Parsing context used to create this Vdbe */
ynVar nVar; /* Number of entries in aVar[] */
- u32 iVdbeMagic; /* Magic number defining state of the SQL statement */
int nMem; /* Number of memory locations currently allocated */
int nCursor; /* Number of slots in apCsr[] */
u32 cacheCtr; /* VdbeCursor row cache generation counter */
@@ -22457,11 +22775,10 @@ struct Vdbe {
u8 errorAction; /* Recovery action to do in case of an error */
u8 minWriteFileFormat; /* Minimum file format for writable database files */
u8 prepFlags; /* SQLITE_PREPARE_* flags */
- u8 doingRerun; /* True if rerunning after an auto-reprepare */
+ u8 eVdbeState; /* On of the VDBE_*_STATE values */
bft expired:2; /* 1: recompile VM immediately 2: when convenient */
bft explain:2; /* True if EXPLAIN present on SQL command */
bft changeCntOn:1; /* True to update the change-counter */
- bft runOnlyOnce:1; /* Automatically expire on reset */
bft usesStmtJournal:1; /* True if uses a statement journal */
bft readOnly:1; /* True for statements that do not write */
bft bIsReader:1; /* True for statements that read */
@@ -22488,13 +22805,12 @@ struct Vdbe {
};
/*
-** The following are allowed values for Vdbe.magic
+** The following are allowed values for Vdbe.eVdbeState
*/
-#define VDBE_MAGIC_INIT 0x16bceaa5 /* Building a VDBE program */
-#define VDBE_MAGIC_RUN 0x2df20da3 /* VDBE is ready to execute */
-#define VDBE_MAGIC_HALT 0x319c2973 /* VDBE has completed execution */
-#define VDBE_MAGIC_RESET 0x48fa9f76 /* Reset and ready to run again */
-#define VDBE_MAGIC_DEAD 0x5606c3c8 /* The VDBE has been deallocated */
+#define VDBE_INIT_STATE 0 /* Prepared statement under construction */
+#define VDBE_READY_STATE 1 /* Ready to run but not yet started */
+#define VDBE_RUN_STATE 2 /* Run in progress */
+#define VDBE_HALT_STATE 3 /* Finished. Need reset() or finalize() */
/*
** Structure used to store the context required by the
@@ -22535,18 +22851,31 @@ struct ValueList {
sqlite3_value *pOut; /* Register to hold each decoded output value */
};
+/* Size of content associated with serial types that fit into a
+** single-byte varint.
+*/
+#ifndef SQLITE_AMALGAMATION
+SQLITE_PRIVATE const u8 sqlite3SmallTypeSizes[];
+#endif
+
/*
** Function prototypes
*/
SQLITE_PRIVATE void sqlite3VdbeError(Vdbe*, const char *, ...);
SQLITE_PRIVATE void sqlite3VdbeFreeCursor(Vdbe *, VdbeCursor*);
+SQLITE_PRIVATE void sqlite3VdbeFreeCursorNN(Vdbe*,VdbeCursor*);
void sqliteVdbePopStack(Vdbe*,int);
+SQLITE_PRIVATE int SQLITE_NOINLINE sqlite3VdbeHandleMovedCursor(VdbeCursor *p);
SQLITE_PRIVATE int SQLITE_NOINLINE sqlite3VdbeFinishMoveto(VdbeCursor*);
-SQLITE_PRIVATE int sqlite3VdbeCursorMoveto(VdbeCursor**, u32*);
SQLITE_PRIVATE int sqlite3VdbeCursorRestore(VdbeCursor*);
SQLITE_PRIVATE u32 sqlite3VdbeSerialTypeLen(u32);
SQLITE_PRIVATE u8 sqlite3VdbeOneByteSerialTypeLen(u8);
-SQLITE_PRIVATE u32 sqlite3VdbeSerialPut(unsigned char*, Mem*, u32);
+#ifdef SQLITE_MIXED_ENDIAN_64BIT_FLOAT
+SQLITE_PRIVATE u64 sqlite3FloatSwap(u64 in);
+# define swapMixedEndianFloat(X) X = sqlite3FloatSwap(X)
+#else
+# define swapMixedEndianFloat(X)
+#endif
SQLITE_PRIVATE void sqlite3VdbeSerialGet(const unsigned char*, u32, Mem*);
SQLITE_PRIVATE void sqlite3VdbeDeleteAuxData(sqlite3*, AuxData**, int, int);
@@ -22604,6 +22933,7 @@ SQLITE_PRIVATE int sqlite3VdbeMemCast(Mem*,u8,u8);
SQLITE_PRIVATE int sqlite3VdbeMemFromBtree(BtCursor*,u32,u32,Mem*);
SQLITE_PRIVATE int sqlite3VdbeMemFromBtreeZeroOffset(BtCursor*,u32,Mem*);
SQLITE_PRIVATE void sqlite3VdbeMemRelease(Mem *p);
+SQLITE_PRIVATE void sqlite3VdbeMemReleaseMalloc(Mem*p);
SQLITE_PRIVATE int sqlite3VdbeMemFinalize(Mem*, FuncDef*);
#ifndef SQLITE_OMIT_WINDOWFUNC
SQLITE_PRIVATE int sqlite3VdbeMemAggValue(Mem*, Mem*, FuncDef*);
@@ -22963,6 +23293,8 @@ SQLITE_API int sqlite3_db_status(
sqlite3BtreeEnterAll(db);
db->pnBytesFreed = &nByte;
+ assert( db->lookaside.pEnd==db->lookaside.pTrueEnd );
+ db->lookaside.pEnd = db->lookaside.pStart;
for(i=0; i<db->nDb; i++){
Schema *pSchema = db->aDb[i].pSchema;
if( ALWAYS(pSchema!=0) ){
@@ -22988,6 +23320,7 @@ SQLITE_API int sqlite3_db_status(
}
}
db->pnBytesFreed = 0;
+ db->lookaside.pEnd = db->lookaside.pTrueEnd;
sqlite3BtreeLeaveAll(db);
*pHighwater = 0;
@@ -23005,10 +23338,12 @@ SQLITE_API int sqlite3_db_status(
int nByte = 0; /* Used to accumulate return value */
db->pnBytesFreed = &nByte;
- for(pVdbe=db->pVdbe; pVdbe; pVdbe=pVdbe->pNext){
- sqlite3VdbeClearObject(db, pVdbe);
- sqlite3DbFree(db, pVdbe);
+ assert( db->lookaside.pEnd==db->lookaside.pTrueEnd );
+ db->lookaside.pEnd = db->lookaside.pStart;
+ for(pVdbe=db->pVdbe; pVdbe; pVdbe=pVdbe->pVNext){
+ sqlite3VdbeDelete(pVdbe);
}
+ db->lookaside.pEnd = db->lookaside.pTrueEnd;
db->pnBytesFreed = 0;
*pHighwater = 0; /* IMP: R-64479-57858 */
@@ -23344,7 +23679,7 @@ static void computeJD(DateTime *p){
p->iJD = (sqlite3_int64)((X1 + X2 + D + B - 1524.5 ) * 86400000);
p->validJD = 1;
if( p->validHMS ){
- p->iJD += p->h*3600000 + p->m*60000 + (sqlite3_int64)(p->s*1000);
+ p->iJD += p->h*3600000 + p->m*60000 + (sqlite3_int64)(p->s*1000 + 0.5);
if( p->validTZ ){
p->iJD -= p->tz*60000;
p->validYMD = 0;
@@ -23853,7 +24188,7 @@ static int parseModifier(
*/
if( sqlite3_strnicmp(z, "weekday ", 8)==0
&& sqlite3AtoF(&z[8], &r, sqlite3Strlen30(&z[8]), SQLITE_UTF8)>0
- && (n=(int)r)==r && n>=0 && r<7 ){
+ && r>=0.0 && r<7.0 && (n=(int)r)==r ){
sqlite3_int64 Z;
computeYMD_HMS(p);
p->validTZ = 0;
@@ -24534,9 +24869,11 @@ SQLITE_PRIVATE int sqlite3OsFileSize(sqlite3_file *id, i64 *pSize){
}
SQLITE_PRIVATE int sqlite3OsLock(sqlite3_file *id, int lockType){
DO_OS_MALLOC_TEST(id);
+ assert( lockType>=SQLITE_LOCK_SHARED && lockType<=SQLITE_LOCK_EXCLUSIVE );
return id->pMethods->xLock(id, lockType);
}
SQLITE_PRIVATE int sqlite3OsUnlock(sqlite3_file *id, int lockType){
+ assert( lockType==SQLITE_LOCK_NONE || lockType==SQLITE_LOCK_SHARED );
return id->pMethods->xUnlock(id, lockType);
}
SQLITE_PRIVATE int sqlite3OsCheckReservedLock(sqlite3_file *id, int *pResOut){
@@ -24651,6 +24988,7 @@ SQLITE_PRIVATE int sqlite3OsOpen(
** down into the VFS layer. Some SQLITE_OPEN_ flags (for example,
** SQLITE_OPEN_FULLMUTEX or SQLITE_OPEN_SHAREDCACHE) are blocked before
** reaching the VFS. */
+ assert( zPath || (flags & SQLITE_OPEN_EXCLUSIVE) );
rc = pVfs->xOpen(pVfs, zPath, pFile, flags & 0x1087f7f, pFlagsOut);
assert( rc==SQLITE_OK || pFile->pMethods==0 );
return rc;
@@ -26962,8 +27300,17 @@ static void *memsys5Realloc(void *pPrior, int nBytes){
*/
static int memsys5Roundup(int n){
int iFullSz;
- if( n > 0x40000000 ) return 0;
- for(iFullSz=mem5.szAtom; iFullSz<n; iFullSz *= 2);
+ if( n<=mem5.szAtom*2 ){
+ if( n<=mem5.szAtom ) return mem5.szAtom;
+ return mem5.szAtom*2;
+ }
+ if( n>0x10000000 ){
+ if( n>0x40000000 ) return 0;
+ if( n>0x20000000 ) return 0x40000000;
+ return 0x20000000;
+ }
+ for(iFullSz=mem5.szAtom*8; iFullSz<n; iFullSz *= 4);
+ if( (iFullSz/2)>=(i64)n ) return iFullSz/2;
return iFullSz;
}
@@ -28865,17 +29212,33 @@ static void mallocWithAlarm(int n, void **pp){
}
/*
+** Maximum size of any single memory allocation.
+**
+** This is not a limit on the total amount of memory used. This is
+** a limit on the size parameter to sqlite3_malloc() and sqlite3_realloc().
+**
+** The upper bound is slightly less than 2GiB: 0x7ffffeff == 2,147,483,391
+** This provides a 256-byte safety margin for defense against 32-bit
+** signed integer overflow bugs when computing memory allocation sizes.
+** Parnoid applications might want to reduce the maximum allocation size
+** further for an even larger safety margin. 0x3fffffff or 0x0fffffff
+** or even smaller would be reasonable upper bounds on the size of a memory
+** allocations for most applications.
+*/
+#ifndef SQLITE_MAX_ALLOCATION_SIZE
+# define SQLITE_MAX_ALLOCATION_SIZE 2147483391
+#endif
+#if SQLITE_MAX_ALLOCATION_SIZE>2147483391
+# error Maximum size for SQLITE_MAX_ALLOCATION_SIZE is 2147483391
+#endif
+
+/*
** Allocate memory. This routine is like sqlite3_malloc() except that it
** assumes the memory subsystem has already been initialized.
*/
SQLITE_PRIVATE void *sqlite3Malloc(u64 n){
void *p;
- if( n==0 || n>=0x7fffff00 ){
- /* A memory allocation of a number of bytes which is near the maximum
- ** signed integer value might cause an integer overflow inside of the
- ** xMalloc(). Hence we limit the maximum size to 0x7fffff00, giving
- ** 255 bytes of overhead. SQLite itself will never use anything near
- ** this amount. The only way to reach the limit is with sqlite3_malloc() */
+ if( n==0 || n>SQLITE_MAX_ALLOCATION_SIZE ){
p = 0;
}else if( sqlite3GlobalConfig.bMemstat ){
sqlite3_mutex_enter(mem0.mutex);
@@ -28911,7 +29274,7 @@ SQLITE_API void *sqlite3_malloc64(sqlite3_uint64 n){
*/
#ifndef SQLITE_OMIT_LOOKASIDE
static int isLookaside(sqlite3 *db, const void *p){
- return SQLITE_WITHIN(p, db->lookaside.pStart, db->lookaside.pEnd);
+ return SQLITE_WITHIN(p, db->lookaside.pStart, db->lookaside.pTrueEnd);
}
#else
#define isLookaside(A,B) 0
@@ -28935,18 +29298,16 @@ static int lookasideMallocSize(sqlite3 *db, const void *p){
SQLITE_PRIVATE int sqlite3DbMallocSize(sqlite3 *db, const void *p){
assert( p!=0 );
#ifdef SQLITE_DEBUG
- if( db==0 || !isLookaside(db,p) ){
- if( db==0 ){
- assert( sqlite3MemdebugNoType(p, (u8)~MEMTYPE_HEAP) );
- assert( sqlite3MemdebugHasType(p, MEMTYPE_HEAP) );
- }else{
- assert( sqlite3MemdebugHasType(p, (MEMTYPE_LOOKASIDE|MEMTYPE_HEAP)) );
- assert( sqlite3MemdebugNoType(p, (u8)~(MEMTYPE_LOOKASIDE|MEMTYPE_HEAP)) );
- }
+ if( db==0 ){
+ assert( sqlite3MemdebugNoType(p, (u8)~MEMTYPE_HEAP) );
+ assert( sqlite3MemdebugHasType(p, MEMTYPE_HEAP) );
+ }else if( !isLookaside(db,p) ){
+ assert( sqlite3MemdebugHasType(p, (MEMTYPE_LOOKASIDE|MEMTYPE_HEAP)) );
+ assert( sqlite3MemdebugNoType(p, (u8)~(MEMTYPE_LOOKASIDE|MEMTYPE_HEAP)) );
}
#endif
if( db ){
- if( ((uptr)p)<(uptr)(db->lookaside.pEnd) ){
+ if( ((uptr)p)<(uptr)(db->lookaside.pTrueEnd) ){
#ifndef SQLITE_OMIT_TWOSIZE_LOOKASIDE
if( ((uptr)p)>=(uptr)(db->lookaside.pMiddle) ){
assert( sqlite3_mutex_held(db->mutex) );
@@ -29002,14 +29363,11 @@ SQLITE_PRIVATE void sqlite3DbFreeNN(sqlite3 *db, void *p){
assert( db==0 || sqlite3_mutex_held(db->mutex) );
assert( p!=0 );
if( db ){
- if( db->pnBytesFreed ){
- measureAllocationSize(db, p);
- return;
- }
if( ((uptr)p)<(uptr)(db->lookaside.pEnd) ){
#ifndef SQLITE_OMIT_TWOSIZE_LOOKASIDE
if( ((uptr)p)>=(uptr)(db->lookaside.pMiddle) ){
LookasideSlot *pBuf = (LookasideSlot*)p;
+ assert( db->pnBytesFreed==0 );
#ifdef SQLITE_DEBUG
memset(p, 0xaa, LOOKASIDE_SMALL); /* Trash freed content */
#endif
@@ -29020,6 +29378,7 @@ SQLITE_PRIVATE void sqlite3DbFreeNN(sqlite3 *db, void *p){
#endif /* SQLITE_OMIT_TWOSIZE_LOOKASIDE */
if( ((uptr)p)>=(uptr)(db->lookaside.pStart) ){
LookasideSlot *pBuf = (LookasideSlot*)p;
+ assert( db->pnBytesFreed==0 );
#ifdef SQLITE_DEBUG
memset(p, 0xaa, db->lookaside.szTrue); /* Trash freed content */
#endif
@@ -29028,6 +29387,10 @@ SQLITE_PRIVATE void sqlite3DbFreeNN(sqlite3 *db, void *p){
return;
}
}
+ if( db->pnBytesFreed ){
+ measureAllocationSize(db, p);
+ return;
+ }
}
assert( sqlite3MemdebugHasType(p, (MEMTYPE_LOOKASIDE|MEMTYPE_HEAP)) );
assert( sqlite3MemdebugNoType(p, (u8)~(MEMTYPE_LOOKASIDE|MEMTYPE_HEAP)) );
@@ -29035,6 +29398,43 @@ SQLITE_PRIVATE void sqlite3DbFreeNN(sqlite3 *db, void *p){
sqlite3MemdebugSetType(p, MEMTYPE_HEAP);
sqlite3_free(p);
}
+SQLITE_PRIVATE void sqlite3DbNNFreeNN(sqlite3 *db, void *p){
+ assert( db!=0 );
+ assert( sqlite3_mutex_held(db->mutex) );
+ assert( p!=0 );
+ if( ((uptr)p)<(uptr)(db->lookaside.pEnd) ){
+#ifndef SQLITE_OMIT_TWOSIZE_LOOKASIDE
+ if( ((uptr)p)>=(uptr)(db->lookaside.pMiddle) ){
+ LookasideSlot *pBuf = (LookasideSlot*)p;
+ assert( db->pnBytesFreed==0 );
+#ifdef SQLITE_DEBUG
+ memset(p, 0xaa, LOOKASIDE_SMALL); /* Trash freed content */
+#endif
+ pBuf->pNext = db->lookaside.pSmallFree;
+ db->lookaside.pSmallFree = pBuf;
+ return;
+ }
+#endif /* SQLITE_OMIT_TWOSIZE_LOOKASIDE */
+ if( ((uptr)p)>=(uptr)(db->lookaside.pStart) ){
+ LookasideSlot *pBuf = (LookasideSlot*)p;
+ assert( db->pnBytesFreed==0 );
+#ifdef SQLITE_DEBUG
+ memset(p, 0xaa, db->lookaside.szTrue); /* Trash freed content */
+#endif
+ pBuf->pNext = db->lookaside.pFree;
+ db->lookaside.pFree = pBuf;
+ return;
+ }
+ }
+ if( db->pnBytesFreed ){
+ measureAllocationSize(db, p);
+ return;
+ }
+ assert( sqlite3MemdebugHasType(p, (MEMTYPE_LOOKASIDE|MEMTYPE_HEAP)) );
+ assert( sqlite3MemdebugNoType(p, (u8)~(MEMTYPE_LOOKASIDE|MEMTYPE_HEAP)) );
+ sqlite3MemdebugSetType(p, MEMTYPE_HEAP);
+ sqlite3_free(p);
+}
SQLITE_PRIVATE void sqlite3DbFree(sqlite3 *db, void *p){
assert( db==0 || sqlite3_mutex_held(db->mutex) );
if( p ) sqlite3DbFreeNN(db, p);
@@ -29370,8 +29770,13 @@ SQLITE_PRIVATE void *sqlite3OomFault(sqlite3 *db){
}
DisableLookaside;
if( db->pParse ){
+ Parse *pParse;
sqlite3ErrorMsg(db->pParse, "out of memory");
db->pParse->rc = SQLITE_NOMEM_BKPT;
+ for(pParse=db->pParse->pOuterParse; pParse; pParse = pParse->pOuterParse){
+ pParse->nErr++;
+ pParse->rc = SQLITE_NOMEM;
+ }
}
}
return 0;
@@ -30237,8 +30642,8 @@ SQLITE_API void sqlite3_str_vappendf(
case etSQLESCAPE: /* %q: Escape ' characters */
case etSQLESCAPE2: /* %Q: Escape ' and enclose in '...' */
case etSQLESCAPE3: { /* %w: Escape " characters */
- int i, j, k, n, isnull;
- int needQuote;
+ i64 i, j, k, n;
+ int needQuote, isnull;
char ch;
char q = ((xtype==etSQLESCAPE3)?'"':'\''); /* Quote character */
char *escarg;
@@ -30318,8 +30723,14 @@ SQLITE_API void sqlite3_str_vappendf(
sqlite3_str_appendall(pAccum, pItem->zName);
}else if( pItem->zAlias ){
sqlite3_str_appendall(pAccum, pItem->zAlias);
- }else if( ALWAYS(pItem->pSelect) ){
- sqlite3_str_appendf(pAccum, "SUBQUERY %u", pItem->pSelect->selId);
+ }else{
+ Select *pSel = pItem->pSelect;
+ assert( pSel!=0 );
+ if( pSel->selFlags & SF_NestedFrom ){
+ sqlite3_str_appendf(pAccum, "(join-%u)", pSel->selId);
+ }else{
+ sqlite3_str_appendf(pAccum, "(subquery-%u)", pSel->selId);
+ }
}
length = width = 0;
break;
@@ -30382,7 +30793,9 @@ SQLITE_PRIVATE void sqlite3RecordErrorByteOffset(sqlite3 *db, const char *z){
** as the error offset.
*/
SQLITE_PRIVATE void sqlite3RecordErrorOffsetOfExpr(sqlite3 *db, const Expr *pExpr){
- while( pExpr && (ExprHasProperty(pExpr,EP_FromJoin) || pExpr->w.iOfst<=0) ){
+ while( pExpr
+ && (ExprHasProperty(pExpr,EP_OuterON|EP_InnerON) || pExpr->w.iOfst<=0)
+ ){
pExpr = pExpr->pLeft;
}
if( pExpr==0 ) return;
@@ -30842,40 +31255,44 @@ SQLITE_API void sqlite3_str_appendf(StrAccum *p, const char *zFormat, ...){
** Add a new subitem to the tree. The moreToFollow flag indicates that this
** is not the last item in the tree.
*/
-static TreeView *sqlite3TreeViewPush(TreeView *p, u8 moreToFollow){
+static void sqlite3TreeViewPush(TreeView **pp, u8 moreToFollow){
+ TreeView *p = *pp;
if( p==0 ){
- p = sqlite3_malloc64( sizeof(*p) );
- if( p==0 ) return 0;
+ *pp = p = sqlite3_malloc64( sizeof(*p) );
+ if( p==0 ) return;
memset(p, 0, sizeof(*p));
}else{
p->iLevel++;
}
assert( moreToFollow==0 || moreToFollow==1 );
- if( p->iLevel<sizeof(p->bLine) ) p->bLine[p->iLevel] = moreToFollow;
- return p;
+ if( p->iLevel<(int)sizeof(p->bLine) ) p->bLine[p->iLevel] = moreToFollow;
}
/*
** Finished with one layer of the tree
*/
-static void sqlite3TreeViewPop(TreeView *p){
+static void sqlite3TreeViewPop(TreeView **pp){
+ TreeView *p = *pp;
if( p==0 ) return;
p->iLevel--;
- if( p->iLevel<0 ) sqlite3_free(p);
+ if( p->iLevel<0 ){
+ sqlite3_free(p);
+ *pp = 0;
+ }
}
/*
** Generate a single line of output for the tree, with a prefix that contains
** all the appropriate tree lines
*/
-static void sqlite3TreeViewLine(TreeView *p, const char *zFormat, ...){
+SQLITE_PRIVATE void sqlite3TreeViewLine(TreeView *p, const char *zFormat, ...){
va_list ap;
int i;
StrAccum acc;
- char zBuf[500];
+ char zBuf[1000];
sqlite3StrAccumInit(&acc, 0, zBuf, sizeof(zBuf), 0);
if( p ){
- for(i=0; i<p->iLevel && i<sizeof(p->bLine)-1; i++){
+ for(i=0; i<p->iLevel && i<(int)sizeof(p->bLine)-1; i++){
sqlite3_str_append(&acc, p->bLine[i] ? "| " : " ", 4);
}
sqlite3_str_append(&acc, p->bLine[i] ? "|-- " : "'-- ", 4);
@@ -30896,11 +31313,58 @@ static void sqlite3TreeViewLine(TreeView *p, const char *zFormat, ...){
** Shorthand for starting a new tree item that consists of a single label
*/
static void sqlite3TreeViewItem(TreeView *p, const char *zLabel,u8 moreFollows){
- p = sqlite3TreeViewPush(p, moreFollows);
+ sqlite3TreeViewPush(&p, moreFollows);
sqlite3TreeViewLine(p, "%s", zLabel);
}
/*
+** Show a list of Column objects in tree format.
+*/
+SQLITE_PRIVATE void sqlite3TreeViewColumnList(
+ TreeView *pView,
+ const Column *aCol,
+ int nCol,
+ u8 moreToFollow
+){
+ int i;
+ sqlite3TreeViewPush(&pView, moreToFollow);
+ sqlite3TreeViewLine(pView, "COLUMNS");
+ for(i=0; i<nCol; i++){
+ u16 flg = aCol[i].colFlags;
+ int colMoreToFollow = i<(nCol - 1);
+ sqlite3TreeViewPush(&pView, colMoreToFollow);
+ sqlite3TreeViewLine(pView, 0);
+ printf(" %s", aCol[i].zCnName);
+ switch( aCol[i].eCType ){
+ case COLTYPE_ANY: printf(" ANY"); break;
+ case COLTYPE_BLOB: printf(" BLOB"); break;
+ case COLTYPE_INT: printf(" INT"); break;
+ case COLTYPE_INTEGER: printf(" INTEGER"); break;
+ case COLTYPE_REAL: printf(" REAL"); break;
+ case COLTYPE_TEXT: printf(" TEXT"); break;
+ case COLTYPE_CUSTOM: {
+ if( flg & COLFLAG_HASTYPE ){
+ const char *z = aCol[i].zCnName;
+ z += strlen(z)+1;
+ printf(" X-%s", z);
+ break;
+ }
+ }
+ }
+ if( flg & COLFLAG_PRIMKEY ) printf(" PRIMARY KEY");
+ if( flg & COLFLAG_HIDDEN ) printf(" HIDDEN");
+#ifdef COLFLAG_NOEXPAND
+ if( flg & COLFLAG_NOEXPAND ) printf(" NO-EXPAND");
+#endif
+ if( flg ) printf(" flags=%04x", flg);
+ printf("\n");
+ fflush(stdout);
+ sqlite3TreeViewPop(&pView);
+ }
+ sqlite3TreeViewPop(&pView);
+}
+
+/*
** Generate a human-readable description of a WITH clause.
*/
SQLITE_PRIVATE void sqlite3TreeViewWith(TreeView *pView, const With *pWith, u8 moreToFollow){
@@ -30913,7 +31377,7 @@ SQLITE_PRIVATE void sqlite3TreeViewWith(TreeView *pView, const With *pWith, u8 m
sqlite3TreeViewLine(pView, "WITH (0x%p)", pWith);
}
if( pWith->nCte>0 ){
- pView = sqlite3TreeViewPush(pView, 1);
+ sqlite3TreeViewPush(&pView, moreToFollow);
for(i=0; i<pWith->nCte; i++){
StrAccum x;
char zLine[1000];
@@ -30929,6 +31393,10 @@ SQLITE_PRIVATE void sqlite3TreeViewWith(TreeView *pView, const With *pWith, u8 m
}
sqlite3_str_appendf(&x, ")");
}
+ if( pCte->eM10d!=M10d_Any ){
+ sqlite3_str_appendf(&x, " %sMATERIALIZED",
+ pCte->eM10d==M10d_No ? "NOT " : "");
+ }
if( pCte->pUse ){
sqlite3_str_appendf(&x, " (pUse=0x%p, nUse=%d)", pCte->pUse,
pCte->pUse->nUse);
@@ -30936,9 +31404,9 @@ SQLITE_PRIVATE void sqlite3TreeViewWith(TreeView *pView, const With *pWith, u8 m
sqlite3StrAccumFinish(&x);
sqlite3TreeViewItem(pView, zLine, i<pWith->nCte-1);
sqlite3TreeViewSelect(pView, pCte->pSelect, 0);
- sqlite3TreeViewPop(pView);
+ sqlite3TreeViewPop(&pView);
}
- sqlite3TreeViewPop(pView);
+ sqlite3TreeViewPop(&pView);
}
}
@@ -30947,10 +31415,12 @@ SQLITE_PRIVATE void sqlite3TreeViewWith(TreeView *pView, const With *pWith, u8 m
*/
SQLITE_PRIVATE void sqlite3TreeViewSrcList(TreeView *pView, const SrcList *pSrc){
int i;
+ if( pSrc==0 ) return;
for(i=0; i<pSrc->nSrc; i++){
const SrcItem *pItem = &pSrc->a[i];
StrAccum x;
- char zLine[100];
+ int n = 0;
+ char zLine[1000];
sqlite3StrAccumInit(&x, 0, zLine, sizeof(zLine), 0);
x.printfFlags |= SQLITE_PRINTF_INTERNAL;
sqlite3_str_appendf(&x, "{%d:*} %!S", pItem->iCursor, pItem);
@@ -30958,26 +31428,48 @@ SQLITE_PRIVATE void sqlite3TreeViewSrcList(TreeView *pView, const SrcList *pSrc)
sqlite3_str_appendf(&x, " tab=%Q nCol=%d ptr=%p used=%llx",
pItem->pTab->zName, pItem->pTab->nCol, pItem->pTab, pItem->colUsed);
}
- if( pItem->fg.jointype & JT_LEFT ){
+ if( (pItem->fg.jointype & (JT_LEFT|JT_RIGHT))==(JT_LEFT|JT_RIGHT) ){
+ sqlite3_str_appendf(&x, " FULL-OUTER-JOIN");
+ }else if( pItem->fg.jointype & JT_LEFT ){
sqlite3_str_appendf(&x, " LEFT-JOIN");
+ }else if( pItem->fg.jointype & JT_RIGHT ){
+ sqlite3_str_appendf(&x, " RIGHT-JOIN");
}else if( pItem->fg.jointype & JT_CROSS ){
sqlite3_str_appendf(&x, " CROSS-JOIN");
}
+ if( pItem->fg.jointype & JT_LTORJ ){
+ sqlite3_str_appendf(&x, " LTORJ");
+ }
if( pItem->fg.fromDDL ){
sqlite3_str_appendf(&x, " DDL");
}
if( pItem->fg.isCte ){
sqlite3_str_appendf(&x, " CteUse=0x%p", pItem->u2.pCteUse);
}
+ if( pItem->fg.isOn || (pItem->fg.isUsing==0 && pItem->u3.pOn!=0) ){
+ sqlite3_str_appendf(&x, " ON");
+ }
sqlite3StrAccumFinish(&x);
sqlite3TreeViewItem(pView, zLine, i<pSrc->nSrc-1);
+ n = 0;
+ if( pItem->pSelect ) n++;
+ if( pItem->fg.isTabFunc ) n++;
+ if( pItem->fg.isUsing ) n++;
+ if( pItem->fg.isUsing ){
+ sqlite3TreeViewIdList(pView, pItem->u3.pUsing, (--n)>0, "USING");
+ }
if( pItem->pSelect ){
- sqlite3TreeViewSelect(pView, pItem->pSelect, 0);
+ if( pItem->pTab ){
+ Table *pTab = pItem->pTab;
+ sqlite3TreeViewColumnList(pView, pTab->aCol, pTab->nCol, 1);
+ }
+ assert( (int)pItem->fg.isNestedFrom == IsNestedFrom(pItem->pSelect) );
+ sqlite3TreeViewSelect(pView, pItem->pSelect, (--n)>0);
}
if( pItem->fg.isTabFunc ){
sqlite3TreeViewExprList(pView, pItem->u1.pFuncArg, 0, "func-args:");
}
- sqlite3TreeViewPop(pView);
+ sqlite3TreeViewPop(&pView);
}
}
@@ -30991,11 +31483,11 @@ SQLITE_PRIVATE void sqlite3TreeViewSelect(TreeView *pView, const Select *p, u8 m
sqlite3TreeViewLine(pView, "nil-SELECT");
return;
}
- pView = sqlite3TreeViewPush(pView, moreToFollow);
+ sqlite3TreeViewPush(&pView, moreToFollow);
if( p->pWith ){
sqlite3TreeViewWith(pView, p->pWith, 1);
cnt = 1;
- sqlite3TreeViewPush(pView, 1);
+ sqlite3TreeViewPush(&pView, 1);
}
do{
if( p->selFlags & SF_WhereBegin ){
@@ -31009,7 +31501,7 @@ SQLITE_PRIVATE void sqlite3TreeViewSelect(TreeView *pView, const Select *p, u8 m
(int)p->nSelectRow
);
}
- if( cnt++ ) sqlite3TreeViewPop(pView);
+ if( cnt++ ) sqlite3TreeViewPop(&pView);
if( p->pPrior ){
n = 1000;
}else{
@@ -31032,24 +31524,24 @@ SQLITE_PRIVATE void sqlite3TreeViewSelect(TreeView *pView, const Select *p, u8 m
#ifndef SQLITE_OMIT_WINDOWFUNC
if( p->pWin ){
Window *pX;
- pView = sqlite3TreeViewPush(pView, (n--)>0);
+ sqlite3TreeViewPush(&pView, (n--)>0);
sqlite3TreeViewLine(pView, "window-functions");
for(pX=p->pWin; pX; pX=pX->pNextWin){
sqlite3TreeViewWinFunc(pView, pX, pX->pNextWin!=0);
}
- sqlite3TreeViewPop(pView);
+ sqlite3TreeViewPop(&pView);
}
#endif
if( p->pSrc && p->pSrc->nSrc ){
- pView = sqlite3TreeViewPush(pView, (n--)>0);
+ sqlite3TreeViewPush(&pView, (n--)>0);
sqlite3TreeViewLine(pView, "FROM");
sqlite3TreeViewSrcList(pView, p->pSrc);
- sqlite3TreeViewPop(pView);
+ sqlite3TreeViewPop(&pView);
}
if( p->pWhere ){
sqlite3TreeViewItem(pView, "WHERE", (n--)>0);
sqlite3TreeViewExpr(pView, p->pWhere, 0);
- sqlite3TreeViewPop(pView);
+ sqlite3TreeViewPop(&pView);
}
if( p->pGroupBy ){
sqlite3TreeViewExprList(pView, p->pGroupBy, (n--)>0, "GROUPBY");
@@ -31057,7 +31549,7 @@ SQLITE_PRIVATE void sqlite3TreeViewSelect(TreeView *pView, const Select *p, u8 m
if( p->pHaving ){
sqlite3TreeViewItem(pView, "HAVING", (n--)>0);
sqlite3TreeViewExpr(pView, p->pHaving, 0);
- sqlite3TreeViewPop(pView);
+ sqlite3TreeViewPop(&pView);
}
#ifndef SQLITE_OMIT_WINDOWFUNC
if( p->pWinDefn ){
@@ -31066,7 +31558,7 @@ SQLITE_PRIVATE void sqlite3TreeViewSelect(TreeView *pView, const Select *p, u8 m
for(pX=p->pWinDefn; pX; pX=pX->pNextWin){
sqlite3TreeViewWindow(pView, pX, pX->pNextWin!=0);
}
- sqlite3TreeViewPop(pView);
+ sqlite3TreeViewPop(&pView);
}
#endif
if( p->pOrderBy ){
@@ -31078,9 +31570,9 @@ SQLITE_PRIVATE void sqlite3TreeViewSelect(TreeView *pView, const Select *p, u8 m
if( p->pLimit->pRight ){
sqlite3TreeViewItem(pView, "OFFSET", (n--)>0);
sqlite3TreeViewExpr(pView, p->pLimit->pRight, 0);
- sqlite3TreeViewPop(pView);
+ sqlite3TreeViewPop(&pView);
}
- sqlite3TreeViewPop(pView);
+ sqlite3TreeViewPop(&pView);
}
if( p->pPrior ){
const char *zOp = "UNION";
@@ -31093,7 +31585,7 @@ SQLITE_PRIVATE void sqlite3TreeViewSelect(TreeView *pView, const Select *p, u8 m
}
p = p->pPrior;
}while( p!=0 );
- sqlite3TreeViewPop(pView);
+ sqlite3TreeViewPop(&pView);
}
#ifndef SQLITE_OMIT_WINDOWFUNC
@@ -31109,24 +31601,24 @@ SQLITE_PRIVATE void sqlite3TreeViewBound(
switch( eBound ){
case TK_UNBOUNDED: {
sqlite3TreeViewItem(pView, "UNBOUNDED", moreToFollow);
- sqlite3TreeViewPop(pView);
+ sqlite3TreeViewPop(&pView);
break;
}
case TK_CURRENT: {
sqlite3TreeViewItem(pView, "CURRENT", moreToFollow);
- sqlite3TreeViewPop(pView);
+ sqlite3TreeViewPop(&pView);
break;
}
case TK_PRECEDING: {
sqlite3TreeViewItem(pView, "PRECEDING", moreToFollow);
sqlite3TreeViewExpr(pView, pExpr, 0);
- sqlite3TreeViewPop(pView);
+ sqlite3TreeViewPop(&pView);
break;
}
case TK_FOLLOWING: {
sqlite3TreeViewItem(pView, "FOLLOWING", moreToFollow);
sqlite3TreeViewExpr(pView, pExpr, 0);
- sqlite3TreeViewPop(pView);
+ sqlite3TreeViewPop(&pView);
break;
}
}
@@ -31139,12 +31631,13 @@ SQLITE_PRIVATE void sqlite3TreeViewBound(
*/
SQLITE_PRIVATE void sqlite3TreeViewWindow(TreeView *pView, const Window *pWin, u8 more){
int nElement = 0;
+ if( pWin==0 ) return;
if( pWin->pFilter ){
sqlite3TreeViewItem(pView, "FILTER", 1);
sqlite3TreeViewExpr(pView, pWin->pFilter, 0);
- sqlite3TreeViewPop(pView);
+ sqlite3TreeViewPop(&pView);
}
- pView = sqlite3TreeViewPush(pView, more);
+ sqlite3TreeViewPush(&pView, more);
if( pWin->zName ){
sqlite3TreeViewLine(pView, "OVER %s (%p)", pWin->zName, pWin);
}else{
@@ -31155,9 +31648,9 @@ SQLITE_PRIVATE void sqlite3TreeViewWindow(TreeView *pView, const Window *pWin, u
if( pWin->eFrmType ) nElement++;
if( pWin->eExclude ) nElement++;
if( pWin->zBase ){
- sqlite3TreeViewPush(pView, (--nElement)>0);
+ sqlite3TreeViewPush(&pView, (--nElement)>0);
sqlite3TreeViewLine(pView, "window: %s", pWin->zBase);
- sqlite3TreeViewPop(pView);
+ sqlite3TreeViewPop(&pView);
}
if( pWin->pPartition ){
sqlite3TreeViewExprList(pView, pWin->pPartition, nElement>0,"PARTITION-BY");
@@ -31175,7 +31668,7 @@ SQLITE_PRIVATE void sqlite3TreeViewWindow(TreeView *pView, const Window *pWin, u
sqlite3TreeViewItem(pView, zBuf, (--nElement)>0);
sqlite3TreeViewBound(pView, pWin->eStart, pWin->pStart, 1);
sqlite3TreeViewBound(pView, pWin->eEnd, pWin->pEnd, 0);
- sqlite3TreeViewPop(pView);
+ sqlite3TreeViewPop(&pView);
}
if( pWin->eExclude ){
char zBuf[30];
@@ -31190,11 +31683,11 @@ SQLITE_PRIVATE void sqlite3TreeViewWindow(TreeView *pView, const Window *pWin, u
zExclude = zBuf;
break;
}
- sqlite3TreeViewPush(pView, 0);
+ sqlite3TreeViewPush(&pView, 0);
sqlite3TreeViewLine(pView, "EXCLUDE %s", zExclude);
- sqlite3TreeViewPop(pView);
+ sqlite3TreeViewPop(&pView);
}
- sqlite3TreeViewPop(pView);
+ sqlite3TreeViewPop(&pView);
}
#endif /* SQLITE_OMIT_WINDOWFUNC */
@@ -31203,11 +31696,12 @@ SQLITE_PRIVATE void sqlite3TreeViewWindow(TreeView *pView, const Window *pWin, u
** Generate a human-readable explanation for a Window Function object
*/
SQLITE_PRIVATE void sqlite3TreeViewWinFunc(TreeView *pView, const Window *pWin, u8 more){
- pView = sqlite3TreeViewPush(pView, more);
+ if( pWin==0 ) return;
+ sqlite3TreeViewPush(&pView, more);
sqlite3TreeViewLine(pView, "WINFUNC %s(%d)",
- pWin->pFunc->zName, pWin->pFunc->nArg);
+ pWin->pWFunc->zName, pWin->pWFunc->nArg);
sqlite3TreeViewWindow(pView, pWin, 0);
- sqlite3TreeViewPop(pView);
+ sqlite3TreeViewPop(&pView);
}
#endif /* SQLITE_OMIT_WINDOWFUNC */
@@ -31218,10 +31712,10 @@ SQLITE_PRIVATE void sqlite3TreeViewExpr(TreeView *pView, const Expr *pExpr, u8 m
const char *zBinOp = 0; /* Binary operator */
const char *zUniOp = 0; /* Unary operator */
char zFlgs[200];
- pView = sqlite3TreeViewPush(pView, moreToFollow);
+ sqlite3TreeViewPush(&pView, moreToFollow);
if( pExpr==0 ){
sqlite3TreeViewLine(pView, "nil");
- sqlite3TreeViewPop(pView);
+ sqlite3TreeViewPop(&pView);
return;
}
if( pExpr->flags || pExpr->affExpr || pExpr->vvaFlags ){
@@ -31229,8 +31723,11 @@ SQLITE_PRIVATE void sqlite3TreeViewExpr(TreeView *pView, const Expr *pExpr, u8 m
sqlite3StrAccumInit(&x, 0, zFlgs, sizeof(zFlgs), 0);
sqlite3_str_appendf(&x, " fg.af=%x.%c",
pExpr->flags, pExpr->affExpr ? pExpr->affExpr : 'n');
- if( ExprHasProperty(pExpr, EP_FromJoin) ){
- sqlite3_str_appendf(&x, " iRJT=%d", pExpr->w.iRightJoinTable);
+ if( ExprHasProperty(pExpr, EP_OuterON) ){
+ sqlite3_str_appendf(&x, " outer.iJoin=%d", pExpr->w.iJoin);
+ }
+ if( ExprHasProperty(pExpr, EP_InnerON) ){
+ sqlite3_str_appendf(&x, " inner.iJoin=%d", pExpr->w.iJoin);
}
if( ExprHasProperty(pExpr, EP_FromDDL) ){
sqlite3_str_appendf(&x, " DDL");
@@ -31454,7 +31951,17 @@ SQLITE_PRIVATE void sqlite3TreeViewExpr(TreeView *pView, const Expr *pExpr, u8 m
break;
}
case TK_IN: {
- sqlite3TreeViewLine(pView, "IN flags=0x%x", pExpr->flags);
+ sqlite3_str *pStr = sqlite3_str_new(0);
+ char *z;
+ sqlite3_str_appendf(pStr, "IN flags=0x%x", pExpr->flags);
+ if( pExpr->iTable ) sqlite3_str_appendf(pStr, " iTable=%d",pExpr->iTable);
+ if( ExprHasProperty(pExpr, EP_Subrtn) ){
+ sqlite3_str_appendf(pStr, " subrtn(%d,%d)",
+ pExpr->y.sub.regReturn, pExpr->y.sub.iAddr);
+ }
+ z = sqlite3_str_finish(pStr);
+ sqlite3TreeViewLine(pView, z);
+ sqlite3_free(z);
sqlite3TreeViewExpr(pView, pExpr->pLeft, 1);
if( ExprUseXSelect(pExpr) ){
sqlite3TreeViewSelect(pView, pExpr->x.pSelect, 0);
@@ -31578,7 +32085,7 @@ SQLITE_PRIVATE void sqlite3TreeViewExpr(TreeView *pView, const Expr *pExpr, u8 m
sqlite3TreeViewLine(pView, "%s%s", zUniOp, zFlgs);
sqlite3TreeViewExpr(pView, pExpr->pLeft, 0);
}
- sqlite3TreeViewPop(pView);
+ sqlite3TreeViewPop(&pView);
}
@@ -31600,13 +32107,25 @@ SQLITE_PRIVATE void sqlite3TreeViewBareExprList(
int j = pList->a[i].u.x.iOrderByCol;
char *zName = pList->a[i].zEName;
int moreToFollow = i<pList->nExpr - 1;
- if( pList->a[i].eEName!=ENAME_NAME ) zName = 0;
if( j || zName ){
- sqlite3TreeViewPush(pView, moreToFollow);
+ sqlite3TreeViewPush(&pView, moreToFollow);
moreToFollow = 0;
sqlite3TreeViewLine(pView, 0);
if( zName ){
- fprintf(stdout, "AS %s ", zName);
+ switch( pList->a[i].fg.eEName ){
+ default:
+ fprintf(stdout, "AS %s ", zName);
+ break;
+ case ENAME_TAB:
+ fprintf(stdout, "TABLE-ALIAS-NAME(\"%s\") ", zName);
+ if( pList->a[i].fg.bUsed ) fprintf(stdout, "(used) ");
+ if( pList->a[i].fg.bUsingTerm ) fprintf(stdout, "(USING-term) ");
+ if( pList->a[i].fg.bNoExpand ) fprintf(stdout, "(NoExpand) ");
+ break;
+ case ENAME_SPAN:
+ fprintf(stdout, "SPAN(\"%s\") ", zName);
+ break;
+ }
}
if( j ){
fprintf(stdout, "iOrderByCol=%d", j);
@@ -31616,7 +32135,7 @@ SQLITE_PRIVATE void sqlite3TreeViewBareExprList(
}
sqlite3TreeViewExpr(pView, pList->a[i].pExpr, moreToFollow);
if( j || zName ){
- sqlite3TreeViewPop(pView);
+ sqlite3TreeViewPop(&pView);
}
}
}
@@ -31627,10 +32146,377 @@ SQLITE_PRIVATE void sqlite3TreeViewExprList(
u8 moreToFollow,
const char *zLabel
){
- pView = sqlite3TreeViewPush(pView, moreToFollow);
+ sqlite3TreeViewPush(&pView, moreToFollow);
sqlite3TreeViewBareExprList(pView, pList, zLabel);
- sqlite3TreeViewPop(pView);
+ sqlite3TreeViewPop(&pView);
+}
+
+/*
+** Generate a human-readable explanation of an id-list.
+*/
+SQLITE_PRIVATE void sqlite3TreeViewBareIdList(
+ TreeView *pView,
+ const IdList *pList,
+ const char *zLabel
+){
+ if( zLabel==0 || zLabel[0]==0 ) zLabel = "LIST";
+ if( pList==0 ){
+ sqlite3TreeViewLine(pView, "%s (empty)", zLabel);
+ }else{
+ int i;
+ sqlite3TreeViewLine(pView, "%s", zLabel);
+ for(i=0; i<pList->nId; i++){
+ char *zName = pList->a[i].zName;
+ int moreToFollow = i<pList->nId - 1;
+ if( zName==0 ) zName = "(null)";
+ sqlite3TreeViewPush(&pView, moreToFollow);
+ sqlite3TreeViewLine(pView, 0);
+ if( pList->eU4==EU4_NONE ){
+ fprintf(stdout, "%s\n", zName);
+ }else if( pList->eU4==EU4_IDX ){
+ fprintf(stdout, "%s (%d)\n", zName, pList->a[i].u4.idx);
+ }else{
+ assert( pList->eU4==EU4_EXPR );
+ if( pList->a[i].u4.pExpr==0 ){
+ fprintf(stdout, "%s (pExpr=NULL)\n", zName);
+ }else{
+ fprintf(stdout, "%s\n", zName);
+ sqlite3TreeViewPush(&pView, i<pList->nId-1);
+ sqlite3TreeViewExpr(pView, pList->a[i].u4.pExpr, 0);
+ sqlite3TreeViewPop(&pView);
+ }
+ }
+ sqlite3TreeViewPop(&pView);
+ }
+ }
+}
+SQLITE_PRIVATE void sqlite3TreeViewIdList(
+ TreeView *pView,
+ const IdList *pList,
+ u8 moreToFollow,
+ const char *zLabel
+){
+ sqlite3TreeViewPush(&pView, moreToFollow);
+ sqlite3TreeViewBareIdList(pView, pList, zLabel);
+ sqlite3TreeViewPop(&pView);
+}
+
+/*
+** Generate a human-readable explanation of a list of Upsert objects
+*/
+SQLITE_PRIVATE void sqlite3TreeViewUpsert(
+ TreeView *pView,
+ const Upsert *pUpsert,
+ u8 moreToFollow
+){
+ if( pUpsert==0 ) return;
+ sqlite3TreeViewPush(&pView, moreToFollow);
+ while( pUpsert ){
+ int n;
+ sqlite3TreeViewPush(&pView, pUpsert->pNextUpsert!=0 || moreToFollow);
+ sqlite3TreeViewLine(pView, "ON CONFLICT DO %s",
+ pUpsert->isDoUpdate ? "UPDATE" : "NOTHING");
+ n = (pUpsert->pUpsertSet!=0) + (pUpsert->pUpsertWhere!=0);
+ sqlite3TreeViewExprList(pView, pUpsert->pUpsertTarget, (n--)>0, "TARGET");
+ sqlite3TreeViewExprList(pView, pUpsert->pUpsertSet, (n--)>0, "SET");
+ if( pUpsert->pUpsertWhere ){
+ sqlite3TreeViewItem(pView, "WHERE", (n--)>0);
+ sqlite3TreeViewExpr(pView, pUpsert->pUpsertWhere, 0);
+ sqlite3TreeViewPop(&pView);
+ }
+ sqlite3TreeViewPop(&pView);
+ pUpsert = pUpsert->pNextUpsert;
+ }
+ sqlite3TreeViewPop(&pView);
+}
+
+#if TREETRACE_ENABLED
+/*
+** Generate a human-readable diagram of the data structure that go
+** into generating an DELETE statement.
+*/
+SQLITE_PRIVATE void sqlite3TreeViewDelete(
+ const With *pWith,
+ const SrcList *pTabList,
+ const Expr *pWhere,
+ const ExprList *pOrderBy,
+ const Expr *pLimit,
+ const Trigger *pTrigger
+){
+ int n = 0;
+ TreeView *pView = 0;
+ sqlite3TreeViewPush(&pView, 0);
+ sqlite3TreeViewLine(pView, "DELETE");
+ if( pWith ) n++;
+ if( pTabList ) n++;
+ if( pWhere ) n++;
+ if( pOrderBy ) n++;
+ if( pLimit ) n++;
+ if( pTrigger ) n++;
+ if( pWith ){
+ sqlite3TreeViewPush(&pView, (--n)>0);
+ sqlite3TreeViewWith(pView, pWith, 0);
+ sqlite3TreeViewPop(&pView);
+ }
+ if( pTabList ){
+ sqlite3TreeViewPush(&pView, (--n)>0);
+ sqlite3TreeViewLine(pView, "FROM");
+ sqlite3TreeViewSrcList(pView, pTabList);
+ sqlite3TreeViewPop(&pView);
+ }
+ if( pWhere ){
+ sqlite3TreeViewPush(&pView, (--n)>0);
+ sqlite3TreeViewLine(pView, "WHERE");
+ sqlite3TreeViewExpr(pView, pWhere, 0);
+ sqlite3TreeViewPop(&pView);
+ }
+ if( pOrderBy ){
+ sqlite3TreeViewExprList(pView, pOrderBy, (--n)>0, "ORDER-BY");
+ }
+ if( pLimit ){
+ sqlite3TreeViewPush(&pView, (--n)>0);
+ sqlite3TreeViewLine(pView, "LIMIT");
+ sqlite3TreeViewExpr(pView, pLimit, 0);
+ sqlite3TreeViewPop(&pView);
+ }
+ if( pTrigger ){
+ sqlite3TreeViewTrigger(pView, pTrigger, (--n)>0, 1);
+ }
+ sqlite3TreeViewPop(&pView);
}
+#endif /* TREETRACE_ENABLED */
+
+#if TREETRACE_ENABLED
+/*
+** Generate a human-readable diagram of the data structure that go
+** into generating an INSERT statement.
+*/
+SQLITE_PRIVATE void sqlite3TreeViewInsert(
+ const With *pWith,
+ const SrcList *pTabList,
+ const IdList *pColumnList,
+ const Select *pSelect,
+ const ExprList *pExprList,
+ int onError,
+ const Upsert *pUpsert,
+ const Trigger *pTrigger
+){
+ TreeView *pView = 0;
+ int n = 0;
+ const char *zLabel = "INSERT";
+ switch( onError ){
+ case OE_Replace: zLabel = "REPLACE"; break;
+ case OE_Ignore: zLabel = "INSERT OR IGNORE"; break;
+ case OE_Rollback: zLabel = "INSERT OR ROLLBACK"; break;
+ case OE_Abort: zLabel = "INSERT OR ABORT"; break;
+ case OE_Fail: zLabel = "INSERT OR FAIL"; break;
+ }
+ sqlite3TreeViewPush(&pView, 0);
+ sqlite3TreeViewLine(pView, zLabel);
+ if( pWith ) n++;
+ if( pTabList ) n++;
+ if( pColumnList ) n++;
+ if( pSelect ) n++;
+ if( pExprList ) n++;
+ if( pUpsert ) n++;
+ if( pTrigger ) n++;
+ if( pWith ){
+ sqlite3TreeViewPush(&pView, (--n)>0);
+ sqlite3TreeViewWith(pView, pWith, 0);
+ sqlite3TreeViewPop(&pView);
+ }
+ if( pTabList ){
+ sqlite3TreeViewPush(&pView, (--n)>0);
+ sqlite3TreeViewLine(pView, "INTO");
+ sqlite3TreeViewSrcList(pView, pTabList);
+ sqlite3TreeViewPop(&pView);
+ }
+ if( pColumnList ){
+ sqlite3TreeViewIdList(pView, pColumnList, (--n)>0, "COLUMNS");
+ }
+ if( pSelect ){
+ sqlite3TreeViewPush(&pView, (--n)>0);
+ sqlite3TreeViewLine(pView, "DATA-SOURCE");
+ sqlite3TreeViewSelect(pView, pSelect, 0);
+ sqlite3TreeViewPop(&pView);
+ }
+ if( pExprList ){
+ sqlite3TreeViewExprList(pView, pExprList, (--n)>0, "VALUES");
+ }
+ if( pUpsert ){
+ sqlite3TreeViewPush(&pView, (--n)>0);
+ sqlite3TreeViewLine(pView, "UPSERT");
+ sqlite3TreeViewUpsert(pView, pUpsert, 0);
+ sqlite3TreeViewPop(&pView);
+ }
+ if( pTrigger ){
+ sqlite3TreeViewTrigger(pView, pTrigger, (--n)>0, 1);
+ }
+ sqlite3TreeViewPop(&pView);
+}
+#endif /* TREETRACE_ENABLED */
+
+#if TREETRACE_ENABLED
+/*
+** Generate a human-readable diagram of the data structure that go
+** into generating an UPDATE statement.
+*/
+SQLITE_PRIVATE void sqlite3TreeViewUpdate(
+ const With *pWith,
+ const SrcList *pTabList,
+ const ExprList *pChanges,
+ const Expr *pWhere,
+ int onError,
+ const ExprList *pOrderBy,
+ const Expr *pLimit,
+ const Upsert *pUpsert,
+ const Trigger *pTrigger
+){
+ int n = 0;
+ TreeView *pView = 0;
+ const char *zLabel = "UPDATE";
+ switch( onError ){
+ case OE_Replace: zLabel = "UPDATE OR REPLACE"; break;
+ case OE_Ignore: zLabel = "UPDATE OR IGNORE"; break;
+ case OE_Rollback: zLabel = "UPDATE OR ROLLBACK"; break;
+ case OE_Abort: zLabel = "UPDATE OR ABORT"; break;
+ case OE_Fail: zLabel = "UPDATE OR FAIL"; break;
+ }
+ sqlite3TreeViewPush(&pView, 0);
+ sqlite3TreeViewLine(pView, zLabel);
+ if( pWith ) n++;
+ if( pTabList ) n++;
+ if( pChanges ) n++;
+ if( pWhere ) n++;
+ if( pOrderBy ) n++;
+ if( pLimit ) n++;
+ if( pUpsert ) n++;
+ if( pTrigger ) n++;
+ if( pWith ){
+ sqlite3TreeViewPush(&pView, (--n)>0);
+ sqlite3TreeViewWith(pView, pWith, 0);
+ sqlite3TreeViewPop(&pView);
+ }
+ if( pTabList ){
+ sqlite3TreeViewPush(&pView, (--n)>0);
+ sqlite3TreeViewLine(pView, "FROM");
+ sqlite3TreeViewSrcList(pView, pTabList);
+ sqlite3TreeViewPop(&pView);
+ }
+ if( pChanges ){
+ sqlite3TreeViewExprList(pView, pChanges, (--n)>0, "SET");
+ }
+ if( pWhere ){
+ sqlite3TreeViewPush(&pView, (--n)>0);
+ sqlite3TreeViewLine(pView, "WHERE");
+ sqlite3TreeViewExpr(pView, pWhere, 0);
+ sqlite3TreeViewPop(&pView);
+ }
+ if( pOrderBy ){
+ sqlite3TreeViewExprList(pView, pOrderBy, (--n)>0, "ORDER-BY");
+ }
+ if( pLimit ){
+ sqlite3TreeViewPush(&pView, (--n)>0);
+ sqlite3TreeViewLine(pView, "LIMIT");
+ sqlite3TreeViewExpr(pView, pLimit, 0);
+ sqlite3TreeViewPop(&pView);
+ }
+ if( pUpsert ){
+ sqlite3TreeViewPush(&pView, (--n)>0);
+ sqlite3TreeViewLine(pView, "UPSERT");
+ sqlite3TreeViewUpsert(pView, pUpsert, 0);
+ sqlite3TreeViewPop(&pView);
+ }
+ if( pTrigger ){
+ sqlite3TreeViewTrigger(pView, pTrigger, (--n)>0, 1);
+ }
+ sqlite3TreeViewPop(&pView);
+}
+#endif /* TREETRACE_ENABLED */
+
+#ifndef SQLITE_OMIT_TRIGGER
+/*
+** Show a human-readable graph of a TriggerStep
+*/
+SQLITE_PRIVATE void sqlite3TreeViewTriggerStep(
+ TreeView *pView,
+ const TriggerStep *pStep,
+ u8 moreToFollow,
+ u8 showFullList
+){
+ int cnt = 0;
+ if( pStep==0 ) return;
+ sqlite3TreeViewPush(&pView,
+ moreToFollow || (showFullList && pStep->pNext!=0));
+ do{
+ if( cnt++ && pStep->pNext==0 ){
+ sqlite3TreeViewPop(&pView);
+ sqlite3TreeViewPush(&pView, 0);
+ }
+ sqlite3TreeViewLine(pView, "%s", pStep->zSpan ? pStep->zSpan : "RETURNING");
+ }while( showFullList && (pStep = pStep->pNext)!=0 );
+ sqlite3TreeViewPop(&pView);
+}
+
+/*
+** Show a human-readable graph of a Trigger
+*/
+SQLITE_PRIVATE void sqlite3TreeViewTrigger(
+ TreeView *pView,
+ const Trigger *pTrigger,
+ u8 moreToFollow,
+ u8 showFullList
+){
+ int cnt = 0;
+ if( pTrigger==0 ) return;
+ sqlite3TreeViewPush(&pView,
+ moreToFollow || (showFullList && pTrigger->pNext!=0));
+ do{
+ if( cnt++ && pTrigger->pNext==0 ){
+ sqlite3TreeViewPop(&pView);
+ sqlite3TreeViewPush(&pView, 0);
+ }
+ sqlite3TreeViewLine(pView, "TRIGGER %s", pTrigger->zName);
+ sqlite3TreeViewPush(&pView, 0);
+ sqlite3TreeViewTriggerStep(pView, pTrigger->step_list, 0, 1);
+ sqlite3TreeViewPop(&pView);
+ }while( showFullList && (pTrigger = pTrigger->pNext)!=0 );
+ sqlite3TreeViewPop(&pView);
+}
+#endif /* SQLITE_OMIT_TRIGGER */
+
+
+/*
+** These simplified versions of the tree-view routines omit unnecessary
+** parameters. These variants are intended to be used from a symbolic
+** debugger, such as "gdb", during interactive debugging sessions.
+**
+** This routines are given external linkage so that they will always be
+** accessible to the debugging, and to avoid warnings about unused
+** functions. But these routines only exist in debugging builds, so they
+** do not contaminate the interface.
+*/
+SQLITE_PRIVATE void sqlite3ShowExpr(const Expr *p){ sqlite3TreeViewExpr(0,p,0); }
+SQLITE_PRIVATE void sqlite3ShowExprList(const ExprList *p){ sqlite3TreeViewExprList(0,p,0,0);}
+SQLITE_PRIVATE void sqlite3ShowIdList(const IdList *p){ sqlite3TreeViewIdList(0,p,0,0); }
+SQLITE_PRIVATE void sqlite3ShowSrcList(const SrcList *p){ sqlite3TreeViewSrcList(0,p); }
+SQLITE_PRIVATE void sqlite3ShowSelect(const Select *p){ sqlite3TreeViewSelect(0,p,0); }
+SQLITE_PRIVATE void sqlite3ShowWith(const With *p){ sqlite3TreeViewWith(0,p,0); }
+SQLITE_PRIVATE void sqlite3ShowUpsert(const Upsert *p){ sqlite3TreeViewUpsert(0,p,0); }
+#ifndef SQLITE_OMIT_TRIGGER
+SQLITE_PRIVATE void sqlite3ShowTriggerStep(const TriggerStep *p){
+ sqlite3TreeViewTriggerStep(0,p,0,0);
+}
+SQLITE_PRIVATE void sqlite3ShowTriggerStepList(const TriggerStep *p){
+ sqlite3TreeViewTriggerStep(0,p,0,1);
+}
+SQLITE_PRIVATE void sqlite3ShowTrigger(const Trigger *p){ sqlite3TreeViewTrigger(0,p,0,0); }
+SQLITE_PRIVATE void sqlite3ShowTriggerList(const Trigger *p){ sqlite3TreeViewTrigger(0,p,0,1);}
+#endif
+#ifndef SQLITE_OMIT_WINDOWFUNC
+SQLITE_PRIVATE void sqlite3ShowWindow(const Window *p){ sqlite3TreeViewWindow(0,p,0); }
+SQLITE_PRIVATE void sqlite3ShowWinFunc(const Window *p){ sqlite3TreeViewWinFunc(0,p,0); }
+#endif
#endif /* SQLITE_DEBUG */
@@ -31660,16 +32546,41 @@ SQLITE_PRIVATE void sqlite3TreeViewExprList(
** This structure is the current state of the generator.
*/
static SQLITE_WSD struct sqlite3PrngType {
- unsigned char isInit; /* True if initialized */
- unsigned char i, j; /* State variables */
- unsigned char s[256]; /* State variables */
+ u32 s[16]; /* 64 bytes of chacha20 state */
+ u8 out[64]; /* Output bytes */
+ u8 n; /* Output bytes remaining */
} sqlite3Prng;
+
+/* The RFC-7539 ChaCha20 block function
+*/
+#define ROTL(a,b) (((a) << (b)) | ((a) >> (32 - (b))))
+#define QR(a, b, c, d) ( \
+ a += b, d ^= a, d = ROTL(d,16), \
+ c += d, b ^= c, b = ROTL(b,12), \
+ a += b, d ^= a, d = ROTL(d, 8), \
+ c += d, b ^= c, b = ROTL(b, 7))
+static void chacha_block(u32 *out, const u32 *in){
+ int i;
+ u32 x[16];
+ memcpy(x, in, 64);
+ for(i=0; i<10; i++){
+ QR(x[0], x[4], x[ 8], x[12]);
+ QR(x[1], x[5], x[ 9], x[13]);
+ QR(x[2], x[6], x[10], x[14]);
+ QR(x[3], x[7], x[11], x[15]);
+ QR(x[0], x[5], x[10], x[15]);
+ QR(x[1], x[6], x[11], x[12]);
+ QR(x[2], x[7], x[ 8], x[13]);
+ QR(x[3], x[4], x[ 9], x[14]);
+ }
+ for(i=0; i<16; i++) out[i] = x[i]+in[i];
+}
+
/*
** Return N random bytes.
*/
SQLITE_API void sqlite3_randomness(int N, void *pBuf){
- unsigned char t;
unsigned char *zBuf = pBuf;
/* The "wsdPrng" macro will resolve to the pseudo-random number generator
@@ -31699,53 +32610,46 @@ SQLITE_API void sqlite3_randomness(int N, void *pBuf){
sqlite3_mutex_enter(mutex);
if( N<=0 || pBuf==0 ){
- wsdPrng.isInit = 0;
+ wsdPrng.s[0] = 0;
sqlite3_mutex_leave(mutex);
return;
}
/* Initialize the state of the random number generator once,
- ** the first time this routine is called. The seed value does
- ** not need to contain a lot of randomness since we are not
- ** trying to do secure encryption or anything like that...
- **
- ** Nothing in this file or anywhere else in SQLite does any kind of
- ** encryption. The RC4 algorithm is being used as a PRNG (pseudo-random
- ** number generator) not as an encryption device.
+ ** the first time this routine is called.
*/
- if( !wsdPrng.isInit ){
+ if( wsdPrng.s[0]==0 ){
sqlite3_vfs *pVfs = sqlite3_vfs_find(0);
- int i;
- char k[256];
- wsdPrng.j = 0;
- wsdPrng.i = 0;
+ static const u32 chacha20_init[] = {
+ 0x61707865, 0x3320646e, 0x79622d32, 0x6b206574
+ };
+ memcpy(&wsdPrng.s[0], chacha20_init, 16);
if( NEVER(pVfs==0) ){
- memset(k, 0, sizeof(k));
+ memset(&wsdPrng.s[4], 0, 44);
}else{
- sqlite3OsRandomness(pVfs, 256, k);
- }
- for(i=0; i<256; i++){
- wsdPrng.s[i] = (u8)i;
+ sqlite3OsRandomness(pVfs, 44, (char*)&wsdPrng.s[4]);
}
- for(i=0; i<256; i++){
- wsdPrng.j += wsdPrng.s[i] + k[i];
- t = wsdPrng.s[wsdPrng.j];
- wsdPrng.s[wsdPrng.j] = wsdPrng.s[i];
- wsdPrng.s[i] = t;
- }
- wsdPrng.isInit = 1;
+ wsdPrng.s[15] = wsdPrng.s[12];
+ wsdPrng.s[12] = 0;
+ wsdPrng.n = 0;
}
assert( N>0 );
- do{
- wsdPrng.i++;
- t = wsdPrng.s[wsdPrng.i];
- wsdPrng.j += t;
- wsdPrng.s[wsdPrng.i] = wsdPrng.s[wsdPrng.j];
- wsdPrng.s[wsdPrng.j] = t;
- t += wsdPrng.s[wsdPrng.i];
- *(zBuf++) = wsdPrng.s[t];
- }while( --N );
+ while( 1 /* exit by break */ ){
+ if( N<=wsdPrng.n ){
+ memcpy(zBuf, &wsdPrng.out[wsdPrng.n-N], N);
+ wsdPrng.n -= N;
+ break;
+ }
+ if( wsdPrng.n>0 ){
+ memcpy(zBuf, wsdPrng.out, wsdPrng.n);
+ N -= wsdPrng.n;
+ zBuf += wsdPrng.n;
+ }
+ wsdPrng.s[12]++;
+ chacha_block((u32*)wsdPrng.out, wsdPrng.s);
+ wsdPrng.n = 64;
+ }
sqlite3_mutex_leave(mutex);
}
@@ -32785,7 +33689,7 @@ SQLITE_PRIVATE void sqlite3ErrorMsg(Parse *pParse, const char *zFormat, ...){
va_list ap;
sqlite3 *db = pParse->db;
assert( db!=0 );
- assert( db->pParse==pParse );
+ assert( db->pParse==pParse || db->pParse->pToplevel==pParse );
db->errByteOffset = -2;
va_start(ap, zFormat);
zMsg = sqlite3VMPrintf(db, zFormat, ap);
@@ -34598,53 +35502,53 @@ SQLITE_PRIVATE const char *sqlite3OpcodeName(int i){
/* 0 */ "Savepoint" OpHelp(""),
/* 1 */ "AutoCommit" OpHelp(""),
/* 2 */ "Transaction" OpHelp(""),
- /* 3 */ "SorterNext" OpHelp(""),
- /* 4 */ "Prev" OpHelp(""),
- /* 5 */ "Next" OpHelp(""),
- /* 6 */ "Checkpoint" OpHelp(""),
- /* 7 */ "JournalMode" OpHelp(""),
- /* 8 */ "Vacuum" OpHelp(""),
- /* 9 */ "VFilter" OpHelp("iplan=r[P3] zplan='P4'"),
- /* 10 */ "VUpdate" OpHelp("data=r[P3@P2]"),
- /* 11 */ "Goto" OpHelp(""),
- /* 12 */ "Gosub" OpHelp(""),
- /* 13 */ "InitCoroutine" OpHelp(""),
- /* 14 */ "Yield" OpHelp(""),
- /* 15 */ "MustBeInt" OpHelp(""),
- /* 16 */ "Jump" OpHelp(""),
- /* 17 */ "Once" OpHelp(""),
- /* 18 */ "If" OpHelp(""),
+ /* 3 */ "Checkpoint" OpHelp(""),
+ /* 4 */ "JournalMode" OpHelp(""),
+ /* 5 */ "Vacuum" OpHelp(""),
+ /* 6 */ "VFilter" OpHelp("iplan=r[P3] zplan='P4'"),
+ /* 7 */ "VUpdate" OpHelp("data=r[P3@P2]"),
+ /* 8 */ "Init" OpHelp("Start at P2"),
+ /* 9 */ "Goto" OpHelp(""),
+ /* 10 */ "Gosub" OpHelp(""),
+ /* 11 */ "InitCoroutine" OpHelp(""),
+ /* 12 */ "Yield" OpHelp(""),
+ /* 13 */ "MustBeInt" OpHelp(""),
+ /* 14 */ "Jump" OpHelp(""),
+ /* 15 */ "Once" OpHelp(""),
+ /* 16 */ "If" OpHelp(""),
+ /* 17 */ "IfNot" OpHelp(""),
+ /* 18 */ "IsType" OpHelp("if typeof(P1.P3) in P5 goto P2"),
/* 19 */ "Not" OpHelp("r[P2]= !r[P1]"),
- /* 20 */ "IfNot" OpHelp(""),
- /* 21 */ "IsNullOrType" OpHelp("if typeof(r[P1]) IN (P3,5) goto P2"),
- /* 22 */ "IfNullRow" OpHelp("if P1.nullRow then r[P3]=NULL, goto P2"),
- /* 23 */ "SeekLT" OpHelp("key=r[P3@P4]"),
- /* 24 */ "SeekLE" OpHelp("key=r[P3@P4]"),
- /* 25 */ "SeekGE" OpHelp("key=r[P3@P4]"),
- /* 26 */ "SeekGT" OpHelp("key=r[P3@P4]"),
- /* 27 */ "IfNotOpen" OpHelp("if( !csr[P1] ) goto P2"),
- /* 28 */ "IfNoHope" OpHelp("key=r[P3@P4]"),
- /* 29 */ "NoConflict" OpHelp("key=r[P3@P4]"),
- /* 30 */ "NotFound" OpHelp("key=r[P3@P4]"),
- /* 31 */ "Found" OpHelp("key=r[P3@P4]"),
- /* 32 */ "SeekRowid" OpHelp("intkey=r[P3]"),
- /* 33 */ "NotExists" OpHelp("intkey=r[P3]"),
- /* 34 */ "Last" OpHelp(""),
- /* 35 */ "IfSmaller" OpHelp(""),
- /* 36 */ "SorterSort" OpHelp(""),
- /* 37 */ "Sort" OpHelp(""),
- /* 38 */ "Rewind" OpHelp(""),
- /* 39 */ "IdxLE" OpHelp("key=r[P3@P4]"),
- /* 40 */ "IdxGT" OpHelp("key=r[P3@P4]"),
- /* 41 */ "IdxLT" OpHelp("key=r[P3@P4]"),
- /* 42 */ "IdxGE" OpHelp("key=r[P3@P4]"),
+ /* 20 */ "IfNullRow" OpHelp("if P1.nullRow then r[P3]=NULL, goto P2"),
+ /* 21 */ "SeekLT" OpHelp("key=r[P3@P4]"),
+ /* 22 */ "SeekLE" OpHelp("key=r[P3@P4]"),
+ /* 23 */ "SeekGE" OpHelp("key=r[P3@P4]"),
+ /* 24 */ "SeekGT" OpHelp("key=r[P3@P4]"),
+ /* 25 */ "IfNotOpen" OpHelp("if( !csr[P1] ) goto P2"),
+ /* 26 */ "IfNoHope" OpHelp("key=r[P3@P4]"),
+ /* 27 */ "NoConflict" OpHelp("key=r[P3@P4]"),
+ /* 28 */ "NotFound" OpHelp("key=r[P3@P4]"),
+ /* 29 */ "Found" OpHelp("key=r[P3@P4]"),
+ /* 30 */ "SeekRowid" OpHelp("intkey=r[P3]"),
+ /* 31 */ "NotExists" OpHelp("intkey=r[P3]"),
+ /* 32 */ "Last" OpHelp(""),
+ /* 33 */ "IfSmaller" OpHelp(""),
+ /* 34 */ "SorterSort" OpHelp(""),
+ /* 35 */ "Sort" OpHelp(""),
+ /* 36 */ "Rewind" OpHelp(""),
+ /* 37 */ "SorterNext" OpHelp(""),
+ /* 38 */ "Prev" OpHelp(""),
+ /* 39 */ "Next" OpHelp(""),
+ /* 40 */ "IdxLE" OpHelp("key=r[P3@P4]"),
+ /* 41 */ "IdxGT" OpHelp("key=r[P3@P4]"),
+ /* 42 */ "IdxLT" OpHelp("key=r[P3@P4]"),
/* 43 */ "Or" OpHelp("r[P3]=(r[P1] || r[P2])"),
/* 44 */ "And" OpHelp("r[P3]=(r[P1] && r[P2])"),
- /* 45 */ "RowSetRead" OpHelp("r[P3]=rowset(P1)"),
- /* 46 */ "RowSetTest" OpHelp("if r[P3] in rowset(P1) goto P2"),
- /* 47 */ "Program" OpHelp(""),
- /* 48 */ "FkIfZero" OpHelp("if fkctr[P1]==0 goto P2"),
- /* 49 */ "IfPos" OpHelp("if r[P1]>0 then r[P1]-=P3, goto P2"),
+ /* 45 */ "IdxGE" OpHelp("key=r[P3@P4]"),
+ /* 46 */ "RowSetRead" OpHelp("r[P3]=rowset(P1)"),
+ /* 47 */ "RowSetTest" OpHelp("if r[P3] in rowset(P1) goto P2"),
+ /* 48 */ "Program" OpHelp(""),
+ /* 49 */ "FkIfZero" OpHelp("if fkctr[P1]==0 goto P2"),
/* 50 */ "IsNull" OpHelp("if r[P1]==NULL goto P2"),
/* 51 */ "NotNull" OpHelp("if r[P1]!=NULL goto P2"),
/* 52 */ "Ne" OpHelp("IF r[P3]!=r[P1]"),
@@ -34654,12 +35558,12 @@ SQLITE_PRIVATE const char *sqlite3OpcodeName(int i){
/* 56 */ "Lt" OpHelp("IF r[P3]<r[P1]"),
/* 57 */ "Ge" OpHelp("IF r[P3]>=r[P1]"),
/* 58 */ "ElseEq" OpHelp(""),
- /* 59 */ "IfNotZero" OpHelp("if r[P1]!=0 then r[P1]--, goto P2"),
- /* 60 */ "DecrJumpZero" OpHelp("if (--r[P1])==0 goto P2"),
- /* 61 */ "IncrVacuum" OpHelp(""),
- /* 62 */ "VNext" OpHelp(""),
- /* 63 */ "Filter" OpHelp("if key(P3@P4) not in filter(P1) goto P2"),
- /* 64 */ "Init" OpHelp("Start at P2"),
+ /* 59 */ "IfPos" OpHelp("if r[P1]>0 then r[P1]-=P3, goto P2"),
+ /* 60 */ "IfNotZero" OpHelp("if r[P1]!=0 then r[P1]--, goto P2"),
+ /* 61 */ "DecrJumpZero" OpHelp("if (--r[P1])==0 goto P2"),
+ /* 62 */ "IncrVacuum" OpHelp(""),
+ /* 63 */ "VNext" OpHelp(""),
+ /* 64 */ "Filter" OpHelp("if key(P3@P4) not in filter(P1) goto P2"),
/* 65 */ "PureFunc" OpHelp("r[P3]=func(r[P2@NP])"),
/* 66 */ "Function" OpHelp("r[P3]=func(r[P2@NP])"),
/* 67 */ "Return" OpHelp(""),
@@ -34669,34 +35573,34 @@ SQLITE_PRIVATE const char *sqlite3OpcodeName(int i){
/* 71 */ "Integer" OpHelp("r[P2]=P1"),
/* 72 */ "Int64" OpHelp("r[P2]=P4"),
/* 73 */ "String" OpHelp("r[P2]='P4' (len=P1)"),
- /* 74 */ "Null" OpHelp("r[P2..P3]=NULL"),
- /* 75 */ "SoftNull" OpHelp("r[P1]=NULL"),
- /* 76 */ "Blob" OpHelp("r[P2]=P4 (len=P1)"),
- /* 77 */ "Variable" OpHelp("r[P2]=parameter(P1,P4)"),
- /* 78 */ "Move" OpHelp("r[P2@P3]=r[P1@P3]"),
- /* 79 */ "Copy" OpHelp("r[P2@P3+1]=r[P1@P3+1]"),
- /* 80 */ "SCopy" OpHelp("r[P2]=r[P1]"),
- /* 81 */ "IntCopy" OpHelp("r[P2]=r[P1]"),
- /* 82 */ "FkCheck" OpHelp(""),
- /* 83 */ "ResultRow" OpHelp("output=r[P1@P2]"),
- /* 84 */ "CollSeq" OpHelp(""),
- /* 85 */ "AddImm" OpHelp("r[P1]=r[P1]+P2"),
- /* 86 */ "RealAffinity" OpHelp(""),
- /* 87 */ "Cast" OpHelp("affinity(r[P1])"),
- /* 88 */ "Permutation" OpHelp(""),
- /* 89 */ "Compare" OpHelp("r[P1@P3] <-> r[P2@P3]"),
- /* 90 */ "IsTrue" OpHelp("r[P2] = coalesce(r[P1]==TRUE,P3) ^ P4"),
- /* 91 */ "ZeroOrNull" OpHelp("r[P2] = 0 OR NULL"),
- /* 92 */ "Offset" OpHelp("r[P3] = sqlite_offset(P1)"),
- /* 93 */ "Column" OpHelp("r[P3]=PX"),
- /* 94 */ "TypeCheck" OpHelp("typecheck(r[P1@P2])"),
- /* 95 */ "Affinity" OpHelp("affinity(r[P1@P2])"),
- /* 96 */ "MakeRecord" OpHelp("r[P3]=mkrec(r[P1@P2])"),
- /* 97 */ "Count" OpHelp("r[P2]=count()"),
- /* 98 */ "ReadCookie" OpHelp(""),
- /* 99 */ "SetCookie" OpHelp(""),
- /* 100 */ "ReopenIdx" OpHelp("root=P2 iDb=P3"),
- /* 101 */ "OpenRead" OpHelp("root=P2 iDb=P3"),
+ /* 74 */ "BeginSubrtn" OpHelp("r[P2]=NULL"),
+ /* 75 */ "Null" OpHelp("r[P2..P3]=NULL"),
+ /* 76 */ "SoftNull" OpHelp("r[P1]=NULL"),
+ /* 77 */ "Blob" OpHelp("r[P2]=P4 (len=P1)"),
+ /* 78 */ "Variable" OpHelp("r[P2]=parameter(P1,P4)"),
+ /* 79 */ "Move" OpHelp("r[P2@P3]=r[P1@P3]"),
+ /* 80 */ "Copy" OpHelp("r[P2@P3+1]=r[P1@P3+1]"),
+ /* 81 */ "SCopy" OpHelp("r[P2]=r[P1]"),
+ /* 82 */ "IntCopy" OpHelp("r[P2]=r[P1]"),
+ /* 83 */ "FkCheck" OpHelp(""),
+ /* 84 */ "ResultRow" OpHelp("output=r[P1@P2]"),
+ /* 85 */ "CollSeq" OpHelp(""),
+ /* 86 */ "AddImm" OpHelp("r[P1]=r[P1]+P2"),
+ /* 87 */ "RealAffinity" OpHelp(""),
+ /* 88 */ "Cast" OpHelp("affinity(r[P1])"),
+ /* 89 */ "Permutation" OpHelp(""),
+ /* 90 */ "Compare" OpHelp("r[P1@P3] <-> r[P2@P3]"),
+ /* 91 */ "IsTrue" OpHelp("r[P2] = coalesce(r[P1]==TRUE,P3) ^ P4"),
+ /* 92 */ "ZeroOrNull" OpHelp("r[P2] = 0 OR NULL"),
+ /* 93 */ "Offset" OpHelp("r[P3] = sqlite_offset(P1)"),
+ /* 94 */ "Column" OpHelp("r[P3]=PX cursor P1 column P2"),
+ /* 95 */ "TypeCheck" OpHelp("typecheck(r[P1@P2])"),
+ /* 96 */ "Affinity" OpHelp("affinity(r[P1@P2])"),
+ /* 97 */ "MakeRecord" OpHelp("r[P3]=mkrec(r[P1@P2])"),
+ /* 98 */ "Count" OpHelp("r[P2]=count()"),
+ /* 99 */ "ReadCookie" OpHelp(""),
+ /* 100 */ "SetCookie" OpHelp(""),
+ /* 101 */ "ReopenIdx" OpHelp("root=P2 iDb=P3"),
/* 102 */ "BitAnd" OpHelp("r[P3]=r[P1]&r[P2]"),
/* 103 */ "BitOr" OpHelp("r[P3]=r[P1]|r[P2]"),
/* 104 */ "ShiftLeft" OpHelp("r[P3]=r[P2]<<r[P1]"),
@@ -34707,85 +35611,1062 @@ SQLITE_PRIVATE const char *sqlite3OpcodeName(int i){
/* 109 */ "Divide" OpHelp("r[P3]=r[P2]/r[P1]"),
/* 110 */ "Remainder" OpHelp("r[P3]=r[P2]%r[P1]"),
/* 111 */ "Concat" OpHelp("r[P3]=r[P2]+r[P1]"),
- /* 112 */ "OpenWrite" OpHelp("root=P2 iDb=P3"),
- /* 113 */ "OpenDup" OpHelp(""),
+ /* 112 */ "OpenRead" OpHelp("root=P2 iDb=P3"),
+ /* 113 */ "OpenWrite" OpHelp("root=P2 iDb=P3"),
/* 114 */ "BitNot" OpHelp("r[P2]= ~r[P1]"),
- /* 115 */ "OpenAutoindex" OpHelp("nColumn=P2"),
- /* 116 */ "OpenEphemeral" OpHelp("nColumn=P2"),
+ /* 115 */ "OpenDup" OpHelp(""),
+ /* 116 */ "OpenAutoindex" OpHelp("nColumn=P2"),
/* 117 */ "String8" OpHelp("r[P2]='P4'"),
- /* 118 */ "SorterOpen" OpHelp(""),
- /* 119 */ "SequenceTest" OpHelp("if( cursor[P1].ctr++ ) pc = P2"),
- /* 120 */ "OpenPseudo" OpHelp("P3 columns in r[P2]"),
- /* 121 */ "Close" OpHelp(""),
- /* 122 */ "ColumnsUsed" OpHelp(""),
- /* 123 */ "SeekScan" OpHelp("Scan-ahead up to P1 rows"),
- /* 124 */ "SeekHit" OpHelp("set P2<=seekHit<=P3"),
- /* 125 */ "Sequence" OpHelp("r[P2]=cursor[P1].ctr++"),
- /* 126 */ "NewRowid" OpHelp("r[P2]=rowid"),
- /* 127 */ "Insert" OpHelp("intkey=r[P3] data=r[P2]"),
- /* 128 */ "RowCell" OpHelp(""),
- /* 129 */ "Delete" OpHelp(""),
- /* 130 */ "ResetCount" OpHelp(""),
- /* 131 */ "SorterCompare" OpHelp("if key(P1)!=trim(r[P3],P4) goto P2"),
- /* 132 */ "SorterData" OpHelp("r[P2]=data"),
- /* 133 */ "RowData" OpHelp("r[P2]=data"),
- /* 134 */ "Rowid" OpHelp("r[P2]=rowid"),
- /* 135 */ "NullRow" OpHelp(""),
- /* 136 */ "SeekEnd" OpHelp(""),
- /* 137 */ "IdxInsert" OpHelp("key=r[P2]"),
- /* 138 */ "SorterInsert" OpHelp("key=r[P2]"),
- /* 139 */ "IdxDelete" OpHelp("key=r[P2@P3]"),
- /* 140 */ "DeferredSeek" OpHelp("Move P3 to P1.rowid if needed"),
- /* 141 */ "IdxRowid" OpHelp("r[P2]=rowid"),
- /* 142 */ "FinishSeek" OpHelp(""),
- /* 143 */ "Destroy" OpHelp(""),
- /* 144 */ "Clear" OpHelp(""),
- /* 145 */ "ResetSorter" OpHelp(""),
- /* 146 */ "CreateBtree" OpHelp("r[P2]=root iDb=P1 flags=P3"),
- /* 147 */ "SqlExec" OpHelp(""),
- /* 148 */ "ParseSchema" OpHelp(""),
- /* 149 */ "LoadAnalysis" OpHelp(""),
- /* 150 */ "DropTable" OpHelp(""),
- /* 151 */ "DropIndex" OpHelp(""),
- /* 152 */ "DropTrigger" OpHelp(""),
+ /* 118 */ "OpenEphemeral" OpHelp("nColumn=P2"),
+ /* 119 */ "SorterOpen" OpHelp(""),
+ /* 120 */ "SequenceTest" OpHelp("if( cursor[P1].ctr++ ) pc = P2"),
+ /* 121 */ "OpenPseudo" OpHelp("P3 columns in r[P2]"),
+ /* 122 */ "Close" OpHelp(""),
+ /* 123 */ "ColumnsUsed" OpHelp(""),
+ /* 124 */ "SeekScan" OpHelp("Scan-ahead up to P1 rows"),
+ /* 125 */ "SeekHit" OpHelp("set P2<=seekHit<=P3"),
+ /* 126 */ "Sequence" OpHelp("r[P2]=cursor[P1].ctr++"),
+ /* 127 */ "NewRowid" OpHelp("r[P2]=rowid"),
+ /* 128 */ "Insert" OpHelp("intkey=r[P3] data=r[P2]"),
+ /* 129 */ "RowCell" OpHelp(""),
+ /* 130 */ "Delete" OpHelp(""),
+ /* 131 */ "ResetCount" OpHelp(""),
+ /* 132 */ "SorterCompare" OpHelp("if key(P1)!=trim(r[P3],P4) goto P2"),
+ /* 133 */ "SorterData" OpHelp("r[P2]=data"),
+ /* 134 */ "RowData" OpHelp("r[P2]=data"),
+ /* 135 */ "Rowid" OpHelp("r[P2]=PX rowid of P1"),
+ /* 136 */ "NullRow" OpHelp(""),
+ /* 137 */ "SeekEnd" OpHelp(""),
+ /* 138 */ "IdxInsert" OpHelp("key=r[P2]"),
+ /* 139 */ "SorterInsert" OpHelp("key=r[P2]"),
+ /* 140 */ "IdxDelete" OpHelp("key=r[P2@P3]"),
+ /* 141 */ "DeferredSeek" OpHelp("Move P3 to P1.rowid if needed"),
+ /* 142 */ "IdxRowid" OpHelp("r[P2]=rowid"),
+ /* 143 */ "FinishSeek" OpHelp(""),
+ /* 144 */ "Destroy" OpHelp(""),
+ /* 145 */ "Clear" OpHelp(""),
+ /* 146 */ "ResetSorter" OpHelp(""),
+ /* 147 */ "CreateBtree" OpHelp("r[P2]=root iDb=P1 flags=P3"),
+ /* 148 */ "SqlExec" OpHelp(""),
+ /* 149 */ "ParseSchema" OpHelp(""),
+ /* 150 */ "LoadAnalysis" OpHelp(""),
+ /* 151 */ "DropTable" OpHelp(""),
+ /* 152 */ "DropIndex" OpHelp(""),
/* 153 */ "Real" OpHelp("r[P2]=P4"),
- /* 154 */ "IntegrityCk" OpHelp(""),
- /* 155 */ "RowSetAdd" OpHelp("rowset(P1)=r[P2]"),
- /* 156 */ "Param" OpHelp(""),
- /* 157 */ "FkCounter" OpHelp("fkctr[P1]+=P2"),
- /* 158 */ "MemMax" OpHelp("r[P1]=max(r[P1],r[P2])"),
- /* 159 */ "OffsetLimit" OpHelp("if r[P1]>0 then r[P2]=r[P1]+max(0,r[P3]) else r[P2]=(-1)"),
- /* 160 */ "AggInverse" OpHelp("accum=r[P3] inverse(r[P2@P5])"),
- /* 161 */ "AggStep" OpHelp("accum=r[P3] step(r[P2@P5])"),
- /* 162 */ "AggStep1" OpHelp("accum=r[P3] step(r[P2@P5])"),
- /* 163 */ "AggValue" OpHelp("r[P3]=value N=P2"),
- /* 164 */ "AggFinal" OpHelp("accum=r[P1] N=P2"),
- /* 165 */ "Expire" OpHelp(""),
- /* 166 */ "CursorLock" OpHelp(""),
- /* 167 */ "CursorUnlock" OpHelp(""),
- /* 168 */ "TableLock" OpHelp("iDb=P1 root=P2 write=P3"),
- /* 169 */ "VBegin" OpHelp(""),
- /* 170 */ "VCreate" OpHelp(""),
- /* 171 */ "VDestroy" OpHelp(""),
- /* 172 */ "VOpen" OpHelp(""),
- /* 173 */ "VInitIn" OpHelp("r[P2]=ValueList(P1,P3)"),
- /* 174 */ "VColumn" OpHelp("r[P3]=vcolumn(P2)"),
- /* 175 */ "VRename" OpHelp(""),
- /* 176 */ "Pagecount" OpHelp(""),
- /* 177 */ "MaxPgcnt" OpHelp(""),
- /* 178 */ "FilterAdd" OpHelp("filter(P1) += key(P3@P4)"),
- /* 179 */ "Trace" OpHelp(""),
- /* 180 */ "CursorHint" OpHelp(""),
- /* 181 */ "ReleaseReg" OpHelp("release r[P1@P2] mask P3"),
- /* 182 */ "Noop" OpHelp(""),
- /* 183 */ "Explain" OpHelp(""),
- /* 184 */ "Abortable" OpHelp(""),
+ /* 154 */ "DropTrigger" OpHelp(""),
+ /* 155 */ "IntegrityCk" OpHelp(""),
+ /* 156 */ "RowSetAdd" OpHelp("rowset(P1)=r[P2]"),
+ /* 157 */ "Param" OpHelp(""),
+ /* 158 */ "FkCounter" OpHelp("fkctr[P1]+=P2"),
+ /* 159 */ "MemMax" OpHelp("r[P1]=max(r[P1],r[P2])"),
+ /* 160 */ "OffsetLimit" OpHelp("if r[P1]>0 then r[P2]=r[P1]+max(0,r[P3]) else r[P2]=(-1)"),
+ /* 161 */ "AggInverse" OpHelp("accum=r[P3] inverse(r[P2@P5])"),
+ /* 162 */ "AggStep" OpHelp("accum=r[P3] step(r[P2@P5])"),
+ /* 163 */ "AggStep1" OpHelp("accum=r[P3] step(r[P2@P5])"),
+ /* 164 */ "AggValue" OpHelp("r[P3]=value N=P2"),
+ /* 165 */ "AggFinal" OpHelp("accum=r[P1] N=P2"),
+ /* 166 */ "Expire" OpHelp(""),
+ /* 167 */ "CursorLock" OpHelp(""),
+ /* 168 */ "CursorUnlock" OpHelp(""),
+ /* 169 */ "TableLock" OpHelp("iDb=P1 root=P2 write=P3"),
+ /* 170 */ "VBegin" OpHelp(""),
+ /* 171 */ "VCreate" OpHelp(""),
+ /* 172 */ "VDestroy" OpHelp(""),
+ /* 173 */ "VOpen" OpHelp(""),
+ /* 174 */ "VInitIn" OpHelp("r[P2]=ValueList(P1,P3)"),
+ /* 175 */ "VColumn" OpHelp("r[P3]=vcolumn(P2)"),
+ /* 176 */ "VRename" OpHelp(""),
+ /* 177 */ "Pagecount" OpHelp(""),
+ /* 178 */ "MaxPgcnt" OpHelp(""),
+ /* 179 */ "ClrSubtype" OpHelp("r[P1].subtype = 0"),
+ /* 180 */ "FilterAdd" OpHelp("filter(P1) += key(P3@P4)"),
+ /* 181 */ "Trace" OpHelp(""),
+ /* 182 */ "CursorHint" OpHelp(""),
+ /* 183 */ "ReleaseReg" OpHelp("release r[P1@P2] mask P3"),
+ /* 184 */ "Noop" OpHelp(""),
+ /* 185 */ "Explain" OpHelp(""),
+ /* 186 */ "Abortable" OpHelp(""),
};
return azName[i];
}
#endif
/************** End of opcodes.c *********************************************/
+/************** Begin file os_kv.c *******************************************/
+/*
+** 2022-09-06
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+******************************************************************************
+**
+** This file contains an experimental VFS layer that operates on a
+** Key/Value storage engine where both keys and values must be pure
+** text.
+*/
+/* #include <sqliteInt.h> */
+#if SQLITE_OS_KV || (SQLITE_OS_UNIX && defined(SQLITE_OS_KV_OPTIONAL))
+
+/*****************************************************************************
+** Debugging logic
+*/
+
+/* SQLITE_KV_TRACE() is used for tracing calls to kvstorage routines. */
+#if 0
+#define SQLITE_KV_TRACE(X) printf X
+#else
+#define SQLITE_KV_TRACE(X)
+#endif
+
+/* SQLITE_KV_LOG() is used for tracing calls to the VFS interface */
+#if 0
+#define SQLITE_KV_LOG(X) printf X
+#else
+#define SQLITE_KV_LOG(X)
+#endif
+
+
+/*
+** Forward declaration of objects used by this VFS implementation
+*/
+typedef struct KVVfsFile KVVfsFile;
+
+/* A single open file. There are only two files represented by this
+** VFS - the database and the rollback journal.
+*/
+struct KVVfsFile {
+ sqlite3_file base; /* IO methods */
+ const char *zClass; /* Storage class */
+ int isJournal; /* True if this is a journal file */
+ unsigned int nJrnl; /* Space allocated for aJrnl[] */
+ char *aJrnl; /* Journal content */
+ int szPage; /* Last known page size */
+ sqlite3_int64 szDb; /* Database file size. -1 means unknown */
+};
+
+/*
+** Methods for KVVfsFile
+*/
+static int kvvfsClose(sqlite3_file*);
+static int kvvfsReadDb(sqlite3_file*, void*, int iAmt, sqlite3_int64 iOfst);
+static int kvvfsReadJrnl(sqlite3_file*, void*, int iAmt, sqlite3_int64 iOfst);
+static int kvvfsWriteDb(sqlite3_file*,const void*,int iAmt, sqlite3_int64);
+static int kvvfsWriteJrnl(sqlite3_file*,const void*,int iAmt, sqlite3_int64);
+static int kvvfsTruncateDb(sqlite3_file*, sqlite3_int64 size);
+static int kvvfsTruncateJrnl(sqlite3_file*, sqlite3_int64 size);
+static int kvvfsSyncDb(sqlite3_file*, int flags);
+static int kvvfsSyncJrnl(sqlite3_file*, int flags);
+static int kvvfsFileSizeDb(sqlite3_file*, sqlite3_int64 *pSize);
+static int kvvfsFileSizeJrnl(sqlite3_file*, sqlite3_int64 *pSize);
+static int kvvfsLock(sqlite3_file*, int);
+static int kvvfsUnlock(sqlite3_file*, int);
+static int kvvfsCheckReservedLock(sqlite3_file*, int *pResOut);
+static int kvvfsFileControlDb(sqlite3_file*, int op, void *pArg);
+static int kvvfsFileControlJrnl(sqlite3_file*, int op, void *pArg);
+static int kvvfsSectorSize(sqlite3_file*);
+static int kvvfsDeviceCharacteristics(sqlite3_file*);
+
+/*
+** Methods for sqlite3_vfs
+*/
+static int kvvfsOpen(sqlite3_vfs*, const char *, sqlite3_file*, int , int *);
+static int kvvfsDelete(sqlite3_vfs*, const char *zName, int syncDir);
+static int kvvfsAccess(sqlite3_vfs*, const char *zName, int flags, int *);
+static int kvvfsFullPathname(sqlite3_vfs*, const char *zName, int, char *zOut);
+static void *kvvfsDlOpen(sqlite3_vfs*, const char *zFilename);
+static int kvvfsRandomness(sqlite3_vfs*, int nByte, char *zOut);
+static int kvvfsSleep(sqlite3_vfs*, int microseconds);
+static int kvvfsCurrentTime(sqlite3_vfs*, double*);
+static int kvvfsCurrentTimeInt64(sqlite3_vfs*, sqlite3_int64*);
+
+static sqlite3_vfs sqlite3OsKvvfsObject = {
+ 1, /* iVersion */
+ sizeof(KVVfsFile), /* szOsFile */
+ 1024, /* mxPathname */
+ 0, /* pNext */
+ "kvvfs", /* zName */
+ 0, /* pAppData */
+ kvvfsOpen, /* xOpen */
+ kvvfsDelete, /* xDelete */
+ kvvfsAccess, /* xAccess */
+ kvvfsFullPathname, /* xFullPathname */
+ kvvfsDlOpen, /* xDlOpen */
+ 0, /* xDlError */
+ 0, /* xDlSym */
+ 0, /* xDlClose */
+ kvvfsRandomness, /* xRandomness */
+ kvvfsSleep, /* xSleep */
+ kvvfsCurrentTime, /* xCurrentTime */
+ 0, /* xGetLastError */
+ kvvfsCurrentTimeInt64 /* xCurrentTimeInt64 */
+};
+
+/* Methods for sqlite3_file objects referencing a database file
+*/
+static sqlite3_io_methods kvvfs_db_io_methods = {
+ 1, /* iVersion */
+ kvvfsClose, /* xClose */
+ kvvfsReadDb, /* xRead */
+ kvvfsWriteDb, /* xWrite */
+ kvvfsTruncateDb, /* xTruncate */
+ kvvfsSyncDb, /* xSync */
+ kvvfsFileSizeDb, /* xFileSize */
+ kvvfsLock, /* xLock */
+ kvvfsUnlock, /* xUnlock */
+ kvvfsCheckReservedLock, /* xCheckReservedLock */
+ kvvfsFileControlDb, /* xFileControl */
+ kvvfsSectorSize, /* xSectorSize */
+ kvvfsDeviceCharacteristics, /* xDeviceCharacteristics */
+ 0, /* xShmMap */
+ 0, /* xShmLock */
+ 0, /* xShmBarrier */
+ 0, /* xShmUnmap */
+ 0, /* xFetch */
+ 0 /* xUnfetch */
+};
+
+/* Methods for sqlite3_file objects referencing a rollback journal
+*/
+static sqlite3_io_methods kvvfs_jrnl_io_methods = {
+ 1, /* iVersion */
+ kvvfsClose, /* xClose */
+ kvvfsReadJrnl, /* xRead */
+ kvvfsWriteJrnl, /* xWrite */
+ kvvfsTruncateJrnl, /* xTruncate */
+ kvvfsSyncJrnl, /* xSync */
+ kvvfsFileSizeJrnl, /* xFileSize */
+ kvvfsLock, /* xLock */
+ kvvfsUnlock, /* xUnlock */
+ kvvfsCheckReservedLock, /* xCheckReservedLock */
+ kvvfsFileControlJrnl, /* xFileControl */
+ kvvfsSectorSize, /* xSectorSize */
+ kvvfsDeviceCharacteristics, /* xDeviceCharacteristics */
+ 0, /* xShmMap */
+ 0, /* xShmLock */
+ 0, /* xShmBarrier */
+ 0, /* xShmUnmap */
+ 0, /* xFetch */
+ 0 /* xUnfetch */
+};
+
+/****** Storage subsystem **************************************************/
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+/* Forward declarations for the low-level storage engine
+*/
+static int kvstorageWrite(const char*, const char *zKey, const char *zData);
+static int kvstorageDelete(const char*, const char *zKey);
+static int kvstorageRead(const char*, const char *zKey, char *zBuf, int nBuf);
+#define KVSTORAGE_KEY_SZ 32
+
+/* Expand the key name with an appropriate prefix and put the result
+** zKeyOut[]. The zKeyOut[] buffer is assumed to hold at least
+** KVSTORAGE_KEY_SZ bytes.
+*/
+static void kvstorageMakeKey(
+ const char *zClass,
+ const char *zKeyIn,
+ char *zKeyOut
+){
+ sqlite3_snprintf(KVSTORAGE_KEY_SZ, zKeyOut, "kvvfs-%s-%s", zClass, zKeyIn);
+}
+
+/* Write content into a key. zClass is the particular namespace of the
+** underlying key/value store to use - either "local" or "session".
+**
+** Both zKey and zData are zero-terminated pure text strings.
+**
+** Return the number of errors.
+*/
+static int kvstorageWrite(
+ const char *zClass,
+ const char *zKey,
+ const char *zData
+){
+ FILE *fd;
+ char zXKey[KVSTORAGE_KEY_SZ];
+ kvstorageMakeKey(zClass, zKey, zXKey);
+ fd = fopen(zXKey, "wb");
+ if( fd ){
+ SQLITE_KV_TRACE(("KVVFS-WRITE %-15s (%d) %.50s%s\n", zXKey,
+ (int)strlen(zData), zData,
+ strlen(zData)>50 ? "..." : ""));
+ fputs(zData, fd);
+ fclose(fd);
+ return 0;
+ }else{
+ return 1;
+ }
+}
+
+/* Delete a key (with its corresponding data) from the key/value
+** namespace given by zClass. If the key does not previously exist,
+** this routine is a no-op.
+*/
+static int kvstorageDelete(const char *zClass, const char *zKey){
+ char zXKey[KVSTORAGE_KEY_SZ];
+ kvstorageMakeKey(zClass, zKey, zXKey);
+ unlink(zXKey);
+ SQLITE_KV_TRACE(("KVVFS-DELETE %-15s\n", zXKey));
+ return 0;
+}
+
+/* Read the value associated with a zKey from the key/value namespace given
+** by zClass and put the text data associated with that key in the first
+** nBuf bytes of zBuf[]. The value might be truncated if zBuf is not large
+** enough to hold it all. The value put into zBuf must always be zero
+** terminated, even if it gets truncated because nBuf is not large enough.
+**
+** Return the total number of bytes in the data, without truncation, and
+** not counting the final zero terminator. Return -1 if the key does
+** not exist.
+**
+** If nBuf<=0 then this routine simply returns the size of the data without
+** actually reading it.
+*/
+static int kvstorageRead(
+ const char *zClass,
+ const char *zKey,
+ char *zBuf,
+ int nBuf
+){
+ FILE *fd;
+ struct stat buf;
+ char zXKey[KVSTORAGE_KEY_SZ];
+ kvstorageMakeKey(zClass, zKey, zXKey);
+ if( access(zXKey, R_OK)!=0
+ || stat(zXKey, &buf)!=0
+ || !S_ISREG(buf.st_mode)
+ ){
+ SQLITE_KV_TRACE(("KVVFS-READ %-15s (-1)\n", zXKey));
+ return -1;
+ }
+ if( nBuf<=0 ){
+ return (int)buf.st_size;
+ }else if( nBuf==1 ){
+ zBuf[0] = 0;
+ SQLITE_KV_TRACE(("KVVFS-READ %-15s (%d)\n", zXKey,
+ (int)buf.st_size));
+ return (int)buf.st_size;
+ }
+ if( nBuf > buf.st_size + 1 ){
+ nBuf = buf.st_size + 1;
+ }
+ fd = fopen(zXKey, "rb");
+ if( fd==0 ){
+ SQLITE_KV_TRACE(("KVVFS-READ %-15s (-1)\n", zXKey));
+ return -1;
+ }else{
+ sqlite3_int64 n = fread(zBuf, 1, nBuf-1, fd);
+ fclose(fd);
+ zBuf[n] = 0;
+ SQLITE_KV_TRACE(("KVVFS-READ %-15s (%lld) %.50s%s\n", zXKey,
+ n, zBuf, n>50 ? "..." : ""));
+ return (int)n;
+ }
+}
+
+/*
+** An internal level of indirection which enables us to replace the
+** kvvfs i/o methods with JavaScript implementations in WASM builds.
+** Maintenance reminder: if this struct changes in any way, the JSON
+** rendering of its structure must be updated in
+** sqlite3_wasm_enum_json(). There are no binary compatibility
+** concerns, so it does not need an iVersion member. This file is
+** necessarily always compiled together with sqlite3_wasm_enum_json(),
+** and JS code dynamically creates the mapping of members based on
+** that JSON description.
+*/
+typedef struct sqlite3_kvvfs_methods sqlite3_kvvfs_methods;
+struct sqlite3_kvvfs_methods {
+ int (*xRead)(const char *zClass, const char *zKey, char *zBuf, int nBuf);
+ int (*xWrite)(const char *zClass, const char *zKey, const char *zData);
+ int (*xDelete)(const char *zClass, const char *zKey);
+ const int nKeySize;
+};
+
+/*
+** This object holds the kvvfs I/O methods which may be swapped out
+** for JavaScript-side implementations in WASM builds. In such builds
+** it cannot be const, but in native builds it should be so that
+** the compiler can hopefully optimize this level of indirection out.
+** That said, kvvfs is intended primarily for use in WASM builds.
+**
+** Note that this is not explicitly flagged as static because the
+** amalgamation build will tag it with SQLITE_PRIVATE.
+*/
+#ifndef SQLITE_WASM
+const
+#endif
+SQLITE_PRIVATE sqlite3_kvvfs_methods sqlite3KvvfsMethods = {
+kvstorageRead,
+kvstorageWrite,
+kvstorageDelete,
+KVSTORAGE_KEY_SZ
+};
+
+/****** Utility subroutines ************************************************/
+
+/*
+** Encode binary into the text encoded used to persist on disk.
+** The output text is stored in aOut[], which must be at least
+** nData+1 bytes in length.
+**
+** Return the actual length of the encoded text, not counting the
+** zero terminator at the end.
+**
+** Encoding format
+** ---------------
+**
+** * Non-zero bytes are encoded as upper-case hexadecimal
+**
+** * A sequence of one or more zero-bytes that are not at the
+** beginning of the buffer are encoded as a little-endian
+** base-26 number using a..z. "a" means 0. "b" means 1,
+** "z" means 25. "ab" means 26. "ac" means 52. And so forth.
+**
+** * Because there is no overlap between the encoding characters
+** of hexadecimal and base-26 numbers, it is always clear where
+** one stops and the next begins.
+*/
+static int kvvfsEncode(const char *aData, int nData, char *aOut){
+ int i, j;
+ const unsigned char *a = (const unsigned char*)aData;
+ for(i=j=0; i<nData; i++){
+ unsigned char c = a[i];
+ if( c!=0 ){
+ aOut[j++] = "0123456789ABCDEF"[c>>4];
+ aOut[j++] = "0123456789ABCDEF"[c&0xf];
+ }else{
+ /* A sequence of 1 or more zeros is stored as a little-endian
+ ** base-26 number using a..z as the digits. So one zero is "b".
+ ** Two zeros is "c". 25 zeros is "z", 26 zeros is "ab", 27 is "bb",
+ ** and so forth.
+ */
+ int k;
+ for(k=1; i+k<nData && a[i+k]==0; k++){}
+ i += k-1;
+ while( k>0 ){
+ aOut[j++] = 'a'+(k%26);
+ k /= 26;
+ }
+ }
+ }
+ aOut[j] = 0;
+ return j;
+}
+
+static const signed char kvvfsHexValue[256] = {
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, -1, -1, -1, -1, -1, -1,
+ -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1
+};
+
+/*
+** Decode the text encoding back to binary. The binary content is
+** written into pOut, which must be at least nOut bytes in length.
+**
+** The return value is the number of bytes actually written into aOut[].
+*/
+static int kvvfsDecode(const char *a, char *aOut, int nOut){
+ int i, j;
+ int c;
+ const unsigned char *aIn = (const unsigned char*)a;
+ i = 0;
+ j = 0;
+ while( 1 ){
+ c = kvvfsHexValue[aIn[i]];
+ if( c<0 ){
+ int n = 0;
+ int mult = 1;
+ c = aIn[i];
+ if( c==0 ) break;
+ while( c>='a' && c<='z' ){
+ n += (c - 'a')*mult;
+ mult *= 26;
+ c = aIn[++i];
+ }
+ if( j+n>nOut ) return -1;
+ memset(&aOut[j], 0, n);
+ j += n;
+ c = aIn[i];
+ if( c==0 ) break;
+ }else{
+ aOut[j] = c<<4;
+ c = kvvfsHexValue[aIn[++i]];
+ if( c<0 ) break;
+ aOut[j++] += c;
+ i++;
+ }
+ }
+ return j;
+}
+
+/*
+** Decode a complete journal file. Allocate space in pFile->aJrnl
+** and store the decoding there. Or leave pFile->aJrnl set to NULL
+** if an error is encountered.
+**
+** The first few characters of the text encoding will be a little-endian
+** base-26 number (digits a..z) that is the total number of bytes
+** in the decoded journal file image. This base-26 number is followed
+** by a single space, then the encoding of the journal. The space
+** separator is required to act as a terminator for the base-26 number.
+*/
+static void kvvfsDecodeJournal(
+ KVVfsFile *pFile, /* Store decoding in pFile->aJrnl */
+ const char *zTxt, /* Text encoding. Zero-terminated */
+ int nTxt /* Bytes in zTxt, excluding zero terminator */
+){
+ unsigned int n = 0;
+ int c, i, mult;
+ i = 0;
+ mult = 1;
+ while( (c = zTxt[i++])>='a' && c<='z' ){
+ n += (zTxt[i] - 'a')*mult;
+ mult *= 26;
+ }
+ sqlite3_free(pFile->aJrnl);
+ pFile->aJrnl = sqlite3_malloc64( n );
+ if( pFile->aJrnl==0 ){
+ pFile->nJrnl = 0;
+ return;
+ }
+ pFile->nJrnl = n;
+ n = kvvfsDecode(zTxt+i, pFile->aJrnl, pFile->nJrnl);
+ if( n<pFile->nJrnl ){
+ sqlite3_free(pFile->aJrnl);
+ pFile->aJrnl = 0;
+ pFile->nJrnl = 0;
+ }
+}
+
+/*
+** Read or write the "sz" element, containing the database file size.
+*/
+static sqlite3_int64 kvvfsReadFileSize(KVVfsFile *pFile){
+ char zData[50];
+ zData[0] = 0;
+ sqlite3KvvfsMethods.xRead(pFile->zClass, "sz", zData, sizeof(zData)-1);
+ return strtoll(zData, 0, 0);
+}
+static int kvvfsWriteFileSize(KVVfsFile *pFile, sqlite3_int64 sz){
+ char zData[50];
+ sqlite3_snprintf(sizeof(zData), zData, "%lld", sz);
+ return sqlite3KvvfsMethods.xWrite(pFile->zClass, "sz", zData);
+}
+
+/****** sqlite3_io_methods methods ******************************************/
+
+/*
+** Close an kvvfs-file.
+*/
+static int kvvfsClose(sqlite3_file *pProtoFile){
+ KVVfsFile *pFile = (KVVfsFile *)pProtoFile;
+
+ SQLITE_KV_LOG(("xClose %s %s\n", pFile->zClass,
+ pFile->isJournal ? "journal" : "db"));
+ sqlite3_free(pFile->aJrnl);
+ return SQLITE_OK;
+}
+
+/*
+** Read from the -journal file.
+*/
+static int kvvfsReadJrnl(
+ sqlite3_file *pProtoFile,
+ void *zBuf,
+ int iAmt,
+ sqlite_int64 iOfst
+){
+ KVVfsFile *pFile = (KVVfsFile*)pProtoFile;
+ assert( pFile->isJournal );
+ SQLITE_KV_LOG(("xRead('%s-journal',%d,%lld)\n", pFile->zClass, iAmt, iOfst));
+ if( pFile->aJrnl==0 ){
+ int szTxt = kvstorageRead(pFile->zClass, "jrnl", 0, 0);
+ char *aTxt;
+ if( szTxt<=4 ){
+ return SQLITE_IOERR;
+ }
+ aTxt = sqlite3_malloc64( szTxt+1 );
+ if( aTxt==0 ) return SQLITE_NOMEM;
+ kvstorageRead(pFile->zClass, "jrnl", aTxt, szTxt+1);
+ kvvfsDecodeJournal(pFile, aTxt, szTxt);
+ sqlite3_free(aTxt);
+ if( pFile->aJrnl==0 ) return SQLITE_IOERR;
+ }
+ if( iOfst+iAmt>pFile->nJrnl ){
+ return SQLITE_IOERR_SHORT_READ;
+ }
+ memcpy(zBuf, pFile->aJrnl+iOfst, iAmt);
+ return SQLITE_OK;
+}
+
+/*
+** Read from the database file.
+*/
+static int kvvfsReadDb(
+ sqlite3_file *pProtoFile,
+ void *zBuf,
+ int iAmt,
+ sqlite_int64 iOfst
+){
+ KVVfsFile *pFile = (KVVfsFile*)pProtoFile;
+ unsigned int pgno;
+ int got, n;
+ char zKey[30];
+ char aData[133073];
+ assert( iOfst>=0 );
+ assert( iAmt>=0 );
+ SQLITE_KV_LOG(("xRead('%s-db',%d,%lld)\n", pFile->zClass, iAmt, iOfst));
+ if( iOfst+iAmt>=512 ){
+ if( (iOfst % iAmt)!=0 ){
+ return SQLITE_IOERR_READ;
+ }
+ if( (iAmt & (iAmt-1))!=0 || iAmt<512 || iAmt>65536 ){
+ return SQLITE_IOERR_READ;
+ }
+ pFile->szPage = iAmt;
+ pgno = 1 + iOfst/iAmt;
+ }else{
+ pgno = 1;
+ }
+ sqlite3_snprintf(sizeof(zKey), zKey, "%u", pgno);
+ got = sqlite3KvvfsMethods.xRead(pFile->zClass, zKey, aData, sizeof(aData)-1);
+ if( got<0 ){
+ n = 0;
+ }else{
+ aData[got] = 0;
+ if( iOfst+iAmt<512 ){
+ int k = iOfst+iAmt;
+ aData[k*2] = 0;
+ n = kvvfsDecode(aData, &aData[2000], sizeof(aData)-2000);
+ if( n>=iOfst+iAmt ){
+ memcpy(zBuf, &aData[2000+iOfst], iAmt);
+ n = iAmt;
+ }else{
+ n = 0;
+ }
+ }else{
+ n = kvvfsDecode(aData, zBuf, iAmt);
+ }
+ }
+ if( n<iAmt ){
+ memset(zBuf+n, 0, iAmt-n);
+ return SQLITE_IOERR_SHORT_READ;
+ }
+ return SQLITE_OK;
+}
+
+
+/*
+** Write into the -journal file.
+*/
+static int kvvfsWriteJrnl(
+ sqlite3_file *pProtoFile,
+ const void *zBuf,
+ int iAmt,
+ sqlite_int64 iOfst
+){
+ KVVfsFile *pFile = (KVVfsFile*)pProtoFile;
+ sqlite3_int64 iEnd = iOfst+iAmt;
+ SQLITE_KV_LOG(("xWrite('%s-journal',%d,%lld)\n", pFile->zClass, iAmt, iOfst));
+ if( iEnd>=0x10000000 ) return SQLITE_FULL;
+ if( pFile->aJrnl==0 || pFile->nJrnl<iEnd ){
+ char *aNew = sqlite3_realloc(pFile->aJrnl, iEnd);
+ if( aNew==0 ){
+ return SQLITE_IOERR_NOMEM;
+ }
+ pFile->aJrnl = aNew;
+ if( pFile->nJrnl<iOfst ){
+ memset(pFile->aJrnl+pFile->nJrnl, 0, iOfst-pFile->nJrnl);
+ }
+ pFile->nJrnl = iEnd;
+ }
+ memcpy(pFile->aJrnl+iOfst, zBuf, iAmt);
+ return SQLITE_OK;
+}
+
+/*
+** Write into the database file.
+*/
+static int kvvfsWriteDb(
+ sqlite3_file *pProtoFile,
+ const void *zBuf,
+ int iAmt,
+ sqlite_int64 iOfst
+){
+ KVVfsFile *pFile = (KVVfsFile*)pProtoFile;
+ unsigned int pgno;
+ char zKey[30];
+ char aData[131073];
+ SQLITE_KV_LOG(("xWrite('%s-db',%d,%lld)\n", pFile->zClass, iAmt, iOfst));
+ assert( iAmt>=512 && iAmt<=65536 );
+ assert( (iAmt & (iAmt-1))==0 );
+ assert( pFile->szPage<0 || pFile->szPage==iAmt );
+ pFile->szPage = iAmt;
+ pgno = 1 + iOfst/iAmt;
+ sqlite3_snprintf(sizeof(zKey), zKey, "%u", pgno);
+ kvvfsEncode(zBuf, iAmt, aData);
+ if( sqlite3KvvfsMethods.xWrite(pFile->zClass, zKey, aData) ){
+ return SQLITE_IOERR;
+ }
+ if( iOfst+iAmt > pFile->szDb ){
+ pFile->szDb = iOfst + iAmt;
+ }
+ return SQLITE_OK;
+}
+
+/*
+** Truncate an kvvfs-file.
+*/
+static int kvvfsTruncateJrnl(sqlite3_file *pProtoFile, sqlite_int64 size){
+ KVVfsFile *pFile = (KVVfsFile *)pProtoFile;
+ SQLITE_KV_LOG(("xTruncate('%s-journal',%lld)\n", pFile->zClass, size));
+ assert( size==0 );
+ sqlite3KvvfsMethods.xDelete(pFile->zClass, "jrnl");
+ sqlite3_free(pFile->aJrnl);
+ pFile->aJrnl = 0;
+ pFile->nJrnl = 0;
+ return SQLITE_OK;
+}
+static int kvvfsTruncateDb(sqlite3_file *pProtoFile, sqlite_int64 size){
+ KVVfsFile *pFile = (KVVfsFile *)pProtoFile;
+ if( pFile->szDb>size
+ && pFile->szPage>0
+ && (size % pFile->szPage)==0
+ ){
+ char zKey[50];
+ unsigned int pgno, pgnoMax;
+ SQLITE_KV_LOG(("xTruncate('%s-db',%lld)\n", pFile->zClass, size));
+ pgno = 1 + size/pFile->szPage;
+ pgnoMax = 2 + pFile->szDb/pFile->szPage;
+ while( pgno<=pgnoMax ){
+ sqlite3_snprintf(sizeof(zKey), zKey, "%u", pgno);
+ sqlite3KvvfsMethods.xDelete(pFile->zClass, zKey);
+ pgno++;
+ }
+ pFile->szDb = size;
+ return kvvfsWriteFileSize(pFile, size) ? SQLITE_IOERR : SQLITE_OK;
+ }
+ return SQLITE_IOERR;
+}
+
+/*
+** Sync an kvvfs-file.
+*/
+static int kvvfsSyncJrnl(sqlite3_file *pProtoFile, int flags){
+ int i, n;
+ KVVfsFile *pFile = (KVVfsFile *)pProtoFile;
+ char *zOut;
+ SQLITE_KV_LOG(("xSync('%s-journal')\n", pFile->zClass));
+ if( pFile->nJrnl<=0 ){
+ return kvvfsTruncateJrnl(pProtoFile, 0);
+ }
+ zOut = sqlite3_malloc64( pFile->nJrnl*2 + 50 );
+ if( zOut==0 ){
+ return SQLITE_IOERR_NOMEM;
+ }
+ n = pFile->nJrnl;
+ i = 0;
+ do{
+ zOut[i++] = 'a' + (n%26);
+ n /= 26;
+ }while( n>0 );
+ zOut[i++] = ' ';
+ kvvfsEncode(pFile->aJrnl, pFile->nJrnl, &zOut[i]);
+ i = sqlite3KvvfsMethods.xWrite(pFile->zClass, "jrnl", zOut);
+ sqlite3_free(zOut);
+ return i ? SQLITE_IOERR : SQLITE_OK;
+}
+static int kvvfsSyncDb(sqlite3_file *pProtoFile, int flags){
+ return SQLITE_OK;
+}
+
+/*
+** Return the current file-size of an kvvfs-file.
+*/
+static int kvvfsFileSizeJrnl(sqlite3_file *pProtoFile, sqlite_int64 *pSize){
+ KVVfsFile *pFile = (KVVfsFile *)pProtoFile;
+ SQLITE_KV_LOG(("xFileSize('%s-journal')\n", pFile->zClass));
+ *pSize = pFile->nJrnl;
+ return SQLITE_OK;
+}
+static int kvvfsFileSizeDb(sqlite3_file *pProtoFile, sqlite_int64 *pSize){
+ KVVfsFile *pFile = (KVVfsFile *)pProtoFile;
+ SQLITE_KV_LOG(("xFileSize('%s-db')\n", pFile->zClass));
+ if( pFile->szDb>=0 ){
+ *pSize = pFile->szDb;
+ }else{
+ *pSize = kvvfsReadFileSize(pFile);
+ }
+ return SQLITE_OK;
+}
+
+/*
+** Lock an kvvfs-file.
+*/
+static int kvvfsLock(sqlite3_file *pProtoFile, int eLock){
+ KVVfsFile *pFile = (KVVfsFile *)pProtoFile;
+ assert( !pFile->isJournal );
+ SQLITE_KV_LOG(("xLock(%s,%d)\n", pFile->zClass, eLock));
+
+ if( eLock!=SQLITE_LOCK_NONE ){
+ pFile->szDb = kvvfsReadFileSize(pFile);
+ }
+ return SQLITE_OK;
+}
+
+/*
+** Unlock an kvvfs-file.
+*/
+static int kvvfsUnlock(sqlite3_file *pProtoFile, int eLock){
+ KVVfsFile *pFile = (KVVfsFile *)pProtoFile;
+ assert( !pFile->isJournal );
+ SQLITE_KV_LOG(("xUnlock(%s,%d)\n", pFile->zClass, eLock));
+ if( eLock==SQLITE_LOCK_NONE ){
+ pFile->szDb = -1;
+ }
+ return SQLITE_OK;
+}
+
+/*
+** Check if another file-handle holds a RESERVED lock on an kvvfs-file.
+*/
+static int kvvfsCheckReservedLock(sqlite3_file *pProtoFile, int *pResOut){
+ SQLITE_KV_LOG(("xCheckReservedLock\n"));
+ *pResOut = 0;
+ return SQLITE_OK;
+}
+
+/*
+** File control method. For custom operations on an kvvfs-file.
+*/
+static int kvvfsFileControlJrnl(sqlite3_file *pProtoFile, int op, void *pArg){
+ SQLITE_KV_LOG(("xFileControl(%d) on journal\n", op));
+ return SQLITE_NOTFOUND;
+}
+static int kvvfsFileControlDb(sqlite3_file *pProtoFile, int op, void *pArg){
+ SQLITE_KV_LOG(("xFileControl(%d) on database\n", op));
+ if( op==SQLITE_FCNTL_SYNC ){
+ KVVfsFile *pFile = (KVVfsFile *)pProtoFile;
+ int rc = SQLITE_OK;
+ SQLITE_KV_LOG(("xSync('%s-db')\n", pFile->zClass));
+ if( pFile->szDb>0 && 0!=kvvfsWriteFileSize(pFile, pFile->szDb) ){
+ rc = SQLITE_IOERR;
+ }
+ return rc;
+ }
+ return SQLITE_NOTFOUND;
+}
+
+/*
+** Return the sector-size in bytes for an kvvfs-file.
+*/
+static int kvvfsSectorSize(sqlite3_file *pFile){
+ return 512;
+}
+
+/*
+** Return the device characteristic flags supported by an kvvfs-file.
+*/
+static int kvvfsDeviceCharacteristics(sqlite3_file *pProtoFile){
+ return 0;
+}
+
+/****** sqlite3_vfs methods *************************************************/
+
+/*
+** Open an kvvfs file handle.
+*/
+static int kvvfsOpen(
+ sqlite3_vfs *pProtoVfs,
+ const char *zName,
+ sqlite3_file *pProtoFile,
+ int flags,
+ int *pOutFlags
+){
+ KVVfsFile *pFile = (KVVfsFile*)pProtoFile;
+ if( zName==0 ) zName = "";
+ SQLITE_KV_LOG(("xOpen(\"%s\")\n", zName));
+ if( strcmp(zName, "local")==0
+ || strcmp(zName, "session")==0
+ ){
+ pFile->isJournal = 0;
+ pFile->base.pMethods = &kvvfs_db_io_methods;
+ }else
+ if( strcmp(zName, "local-journal")==0
+ || strcmp(zName, "session-journal")==0
+ ){
+ pFile->isJournal = 1;
+ pFile->base.pMethods = &kvvfs_jrnl_io_methods;
+ }else{
+ return SQLITE_CANTOPEN;
+ }
+ if( zName[0]=='s' ){
+ pFile->zClass = "session";
+ }else{
+ pFile->zClass = "local";
+ }
+ pFile->aJrnl = 0;
+ pFile->nJrnl = 0;
+ pFile->szPage = -1;
+ pFile->szDb = -1;
+ return SQLITE_OK;
+}
+
+/*
+** Delete the file located at zPath. If the dirSync argument is true,
+** ensure the file-system modifications are synced to disk before
+** returning.
+*/
+static int kvvfsDelete(sqlite3_vfs *pVfs, const char *zPath, int dirSync){
+ if( strcmp(zPath, "local-journal")==0 ){
+ sqlite3KvvfsMethods.xDelete("local", "jrnl");
+ }else
+ if( strcmp(zPath, "session-journal")==0 ){
+ sqlite3KvvfsMethods.xDelete("session", "jrnl");
+ }
+ return SQLITE_OK;
+}
+
+/*
+** Test for access permissions. Return true if the requested permission
+** is available, or false otherwise.
+*/
+static int kvvfsAccess(
+ sqlite3_vfs *pProtoVfs,
+ const char *zPath,
+ int flags,
+ int *pResOut
+){
+ SQLITE_KV_LOG(("xAccess(\"%s\")\n", zPath));
+ if( strcmp(zPath, "local-journal")==0 ){
+ *pResOut = sqlite3KvvfsMethods.xRead("local", "jrnl", 0, 0)>0;
+ }else
+ if( strcmp(zPath, "session-journal")==0 ){
+ *pResOut = sqlite3KvvfsMethods.xRead("session", "jrnl", 0, 0)>0;
+ }else
+ if( strcmp(zPath, "local")==0 ){
+ *pResOut = sqlite3KvvfsMethods.xRead("local", "sz", 0, 0)>0;
+ }else
+ if( strcmp(zPath, "session")==0 ){
+ *pResOut = sqlite3KvvfsMethods.xRead("session", "sz", 0, 0)>0;
+ }else
+ {
+ *pResOut = 0;
+ }
+ SQLITE_KV_LOG(("xAccess returns %d\n",*pResOut));
+ return SQLITE_OK;
+}
+
+/*
+** Populate buffer zOut with the full canonical pathname corresponding
+** to the pathname in zPath. zOut is guaranteed to point to a buffer
+** of at least (INST_MAX_PATHNAME+1) bytes.
+*/
+static int kvvfsFullPathname(
+ sqlite3_vfs *pVfs,
+ const char *zPath,
+ int nOut,
+ char *zOut
+){
+ size_t nPath;
+#ifdef SQLITE_OS_KV_ALWAYS_LOCAL
+ zPath = "local";
+#endif
+ nPath = strlen(zPath);
+ SQLITE_KV_LOG(("xFullPathname(\"%s\")\n", zPath));
+ if( nOut<nPath+1 ) nPath = nOut - 1;
+ memcpy(zOut, zPath, nPath);
+ zOut[nPath] = 0;
+ return SQLITE_OK;
+}
+
+/*
+** Open the dynamic library located at zPath and return a handle.
+*/
+static void *kvvfsDlOpen(sqlite3_vfs *pVfs, const char *zPath){
+ return 0;
+}
+
+/*
+** Populate the buffer pointed to by zBufOut with nByte bytes of
+** random data.
+*/
+static int kvvfsRandomness(sqlite3_vfs *pVfs, int nByte, char *zBufOut){
+ memset(zBufOut, 0, nByte);
+ return nByte;
+}
+
+/*
+** Sleep for nMicro microseconds. Return the number of microseconds
+** actually slept.
+*/
+static int kvvfsSleep(sqlite3_vfs *pVfs, int nMicro){
+ return SQLITE_OK;
+}
+
+/*
+** Return the current time as a Julian Day number in *pTimeOut.
+*/
+static int kvvfsCurrentTime(sqlite3_vfs *pVfs, double *pTimeOut){
+ sqlite3_int64 i = 0;
+ int rc;
+ rc = kvvfsCurrentTimeInt64(0, &i);
+ *pTimeOut = i/86400000.0;
+ return rc;
+}
+#include <sys/time.h>
+static int kvvfsCurrentTimeInt64(sqlite3_vfs *pVfs, sqlite3_int64 *pTimeOut){
+ static const sqlite3_int64 unixEpoch = 24405875*(sqlite3_int64)8640000;
+ struct timeval sNow;
+ (void)gettimeofday(&sNow, 0); /* Cannot fail given valid arguments */
+ *pTimeOut = unixEpoch + 1000*(sqlite3_int64)sNow.tv_sec + sNow.tv_usec/1000;
+ return SQLITE_OK;
+}
+#endif /* SQLITE_OS_KV || SQLITE_OS_UNIX */
+
+#if SQLITE_OS_KV
+/*
+** This routine is called initialize the KV-vfs as the default VFS.
+*/
+SQLITE_API int sqlite3_os_init(void){
+ return sqlite3_vfs_register(&sqlite3OsKvvfsObject, 1);
+}
+SQLITE_API int sqlite3_os_end(void){
+ return SQLITE_OK;
+}
+#endif /* SQLITE_OS_KV */
+
+#if SQLITE_OS_UNIX && defined(SQLITE_OS_KV_OPTIONAL)
+SQLITE_PRIVATE int sqlite3KvvfsInit(void){
+ return sqlite3_vfs_register(&sqlite3OsKvvfsObject, 0);
+}
+#endif
+
+/************** End of os_kv.c ***********************************************/
/************** Begin file os_unix.c *****************************************/
/*
** 2004 May 22
@@ -34876,13 +36757,13 @@ SQLITE_PRIVATE const char *sqlite3OpcodeName(int i){
/*
** standard include files.
*/
-#include <sys/types.h>
-#include <sys/stat.h>
+#include <sys/types.h> /* amalgamator: keep */
+#include <sys/stat.h> /* amalgamator: keep */
#include <fcntl.h>
#include <sys/ioctl.h>
-#include <unistd.h>
+#include <unistd.h> /* amalgamator: keep */
/* #include <time.h> */
-#include <sys/time.h>
+#include <sys/time.h> /* amalgamator: keep */
#include <errno.h>
#if !defined(SQLITE_OMIT_WAL) || SQLITE_MAX_MMAP_SIZE>0
# include <sys/mman.h>
@@ -35475,6 +37356,9 @@ static int robust_open(const char *z, int f, mode_t m){
break;
}
if( fd>=SQLITE_MINIMUM_FILE_DESCRIPTOR ) break;
+ if( (f & (O_EXCL|O_CREAT))==(O_EXCL|O_CREAT) ){
+ (void)osUnlink(z);
+ }
osClose(fd);
sqlite3_log(SQLITE_WARNING,
"attempt to open \"%s\" as file descriptor %d", z, fd);
@@ -40644,6 +42528,7 @@ static const char *unixTempFileDir(void){
static int unixGetTempname(int nBuf, char *zBuf){
const char *zDir;
int iLimit = 0;
+ int rc = SQLITE_OK;
/* It's odd to simulate an io-error here, but really this is just
** using the io-error infrastructure to test that SQLite handles this
@@ -40652,18 +42537,26 @@ static int unixGetTempname(int nBuf, char *zBuf){
zBuf[0] = 0;
SimulateIOError( return SQLITE_IOERR );
+ sqlite3_mutex_enter(sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_TEMPDIR));
zDir = unixTempFileDir();
- if( zDir==0 ) return SQLITE_IOERR_GETTEMPPATH;
- do{
- u64 r;
- sqlite3_randomness(sizeof(r), &r);
- assert( nBuf>2 );
- zBuf[nBuf-2] = 0;
- sqlite3_snprintf(nBuf, zBuf, "%s/"SQLITE_TEMP_FILE_PREFIX"%llx%c",
- zDir, r, 0);
- if( zBuf[nBuf-2]!=0 || (iLimit++)>10 ) return SQLITE_ERROR;
- }while( osAccess(zBuf,0)==0 );
- return SQLITE_OK;
+ if( zDir==0 ){
+ rc = SQLITE_IOERR_GETTEMPPATH;
+ }else{
+ do{
+ u64 r;
+ sqlite3_randomness(sizeof(r), &r);
+ assert( nBuf>2 );
+ zBuf[nBuf-2] = 0;
+ sqlite3_snprintf(nBuf, zBuf, "%s/"SQLITE_TEMP_FILE_PREFIX"%llx%c",
+ zDir, r, 0);
+ if( zBuf[nBuf-2]!=0 || (iLimit++)>10 ){
+ rc = SQLITE_ERROR;
+ break;
+ }
+ }while( osAccess(zBuf,0)==0 );
+ }
+ sqlite3_mutex_leave(sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_TEMPDIR));
+ return rc;
}
#if SQLITE_ENABLE_LOCKING_STYLE && defined(__APPLE__)
@@ -40806,20 +42699,23 @@ static int findCreateFileMode(
**
** where NN is a decimal number. The NN naming schemes are
** used by the test_multiplex.c module.
+ **
+ ** In normal operation, the journal file name will always contain
+ ** a '-' character. However in 8+3 filename mode, or if a corrupt
+ ** rollback journal specifies a super-journal with a goofy name, then
+ ** the '-' might be missing or the '-' might be the first character in
+ ** the filename. In that case, just return SQLITE_OK with *pMode==0.
*/
nDb = sqlite3Strlen30(zPath) - 1;
- while( zPath[nDb]!='-' ){
- /* In normal operation, the journal file name will always contain
- ** a '-' character. However in 8+3 filename mode, or if a corrupt
- ** rollback journal specifies a super-journal with a goofy name, then
- ** the '-' might be missing. */
- if( nDb==0 || zPath[nDb]=='.' ) return SQLITE_OK;
+ while( nDb>0 && zPath[nDb]!='.' ){
+ if( zPath[nDb]=='-' ){
+ memcpy(zDb, zPath, nDb);
+ zDb[nDb] = '\0';
+ rc = getFileMode(zDb, pMode, pUid, pGid);
+ break;
+ }
nDb--;
}
- memcpy(zDb, zPath, nDb);
- zDb[nDb] = '\0';
-
- rc = getFileMode(zDb, pMode, pUid, pGid);
}else if( flags & SQLITE_OPEN_DELETEONCLOSE ){
*pMode = 0600;
}else if( flags & SQLITE_OPEN_URI ){
@@ -41209,86 +43105,99 @@ static int unixAccess(
}
/*
-** If the last component of the pathname in z[0]..z[j-1] is something
-** other than ".." then back it out and return true. If the last
-** component is empty or if it is ".." then return false.
+** A pathname under construction
*/
-static int unixBackupDir(const char *z, int *pJ){
- int j = *pJ;
- int i;
- if( j<=0 ) return 0;
- for(i=j-1; i>0 && z[i-1]!='/'; i--){}
- if( i==0 ) return 0;
- if( z[i]=='.' && i==j-2 && z[i+1]=='.' ) return 0;
- *pJ = i-1;
- return 1;
-}
+typedef struct DbPath DbPath;
+struct DbPath {
+ int rc; /* Non-zero following any error */
+ int nSymlink; /* Number of symlinks resolved */
+ char *zOut; /* Write the pathname here */
+ int nOut; /* Bytes of space available to zOut[] */
+ int nUsed; /* Bytes of zOut[] currently being used */
+};
+
+/* Forward reference */
+static void appendAllPathElements(DbPath*,const char*);
/*
-** Convert a relative pathname into a full pathname. Also
-** simplify the pathname as follows:
-**
-** Remove all instances of /./
-** Remove all isntances of /X/../ for any X
+** Append a single path element to the DbPath under construction
*/
-static int mkFullPathname(
- const char *zPath, /* Input path */
- char *zOut, /* Output buffer */
- int nOut /* Allocated size of buffer zOut */
+static void appendOnePathElement(
+ DbPath *pPath, /* Path under construction, to which to append zName */
+ const char *zName, /* Name to append to pPath. Not zero-terminated */
+ int nName /* Number of significant bytes in zName */
){
- int nPath = sqlite3Strlen30(zPath);
- int iOff = 0;
- int i, j;
- if( zPath[0]!='/' ){
- if( osGetcwd(zOut, nOut-2)==0 ){
- return unixLogError(SQLITE_CANTOPEN_BKPT, "getcwd", zPath);
+ assert( nName>0 );
+ assert( zName!=0 );
+ if( zName[0]=='.' ){
+ if( nName==1 ) return;
+ if( zName[1]=='.' && nName==2 ){
+ if( pPath->nUsed<=1 ){
+ pPath->rc = SQLITE_ERROR;
+ return;
+ }
+ assert( pPath->zOut[0]=='/' );
+ while( pPath->zOut[--pPath->nUsed]!='/' ){}
+ return;
}
- iOff = sqlite3Strlen30(zOut);
- zOut[iOff++] = '/';
}
- if( (iOff+nPath+1)>nOut ){
- /* SQLite assumes that xFullPathname() nul-terminates the output buffer
- ** even if it returns an error. */
- zOut[iOff] = '\0';
- return SQLITE_CANTOPEN_BKPT;
- }
- sqlite3_snprintf(nOut-iOff, &zOut[iOff], "%s", zPath);
-
- /* Remove duplicate '/' characters. Except, two // at the beginning
- ** of a pathname is allowed since this is important on windows. */
- for(i=j=1; zOut[i]; i++){
- zOut[j++] = zOut[i];
- while( zOut[i]=='/' && zOut[i+1]=='/' ) i++;
+ if( pPath->nUsed + nName + 2 >= pPath->nOut ){
+ pPath->rc = SQLITE_ERROR;
+ return;
}
- zOut[j] = 0;
-
- assert( zOut[0]=='/' );
- for(i=j=0; zOut[i]; i++){
- if( zOut[i]=='/' ){
- /* Skip over internal "/." directory components */
- if( zOut[i+1]=='.' && zOut[i+2]=='/' ){
- i += 1;
- continue;
+ pPath->zOut[pPath->nUsed++] = '/';
+ memcpy(&pPath->zOut[pPath->nUsed], zName, nName);
+ pPath->nUsed += nName;
+#if defined(HAVE_READLINK) && defined(HAVE_LSTAT)
+ if( pPath->rc==SQLITE_OK ){
+ const char *zIn;
+ struct stat buf;
+ pPath->zOut[pPath->nUsed] = 0;
+ zIn = pPath->zOut;
+ if( osLstat(zIn, &buf)!=0 ){
+ if( errno!=ENOENT ){
+ pPath->rc = unixLogError(SQLITE_CANTOPEN_BKPT, "lstat", zIn);
}
-
- /* If this is a "/.." directory component then back out the
- ** previous term of the directory if it is something other than "..".
- */
- if( zOut[i+1]=='.'
- && zOut[i+2]=='.'
- && zOut[i+3]=='/'
- && unixBackupDir(zOut, &j)
- ){
- i += 2;
- continue;
+ }else if( S_ISLNK(buf.st_mode) ){
+ ssize_t got;
+ char zLnk[SQLITE_MAX_PATHLEN+2];
+ if( pPath->nSymlink++ > SQLITE_MAX_SYMLINK ){
+ pPath->rc = SQLITE_CANTOPEN_BKPT;
+ return;
}
+ got = osReadlink(zIn, zLnk, sizeof(zLnk)-2);
+ if( got<=0 || got>=(ssize_t)sizeof(zLnk)-2 ){
+ pPath->rc = unixLogError(SQLITE_CANTOPEN_BKPT, "readlink", zIn);
+ return;
+ }
+ zLnk[got] = 0;
+ if( zLnk[0]=='/' ){
+ pPath->nUsed = 0;
+ }else{
+ pPath->nUsed -= nName + 1;
+ }
+ appendAllPathElements(pPath, zLnk);
}
- if( ALWAYS(j>=0) ) zOut[j] = zOut[i];
- j++;
}
- if( NEVER(j==0) ) zOut[j++] = '/';
- zOut[j] = 0;
- return SQLITE_OK;
+#endif
+}
+
+/*
+** Append all path elements in zPath to the DbPath under construction.
+*/
+static void appendAllPathElements(
+ DbPath *pPath, /* Path under construction, to which to append zName */
+ const char *zPath /* Path to append to pPath. Is zero-terminated */
+){
+ int i = 0;
+ int j = 0;
+ do{
+ while( zPath[i] && zPath[i]!='/' ){ i++; }
+ if( i>j ){
+ appendOnePathElement(pPath, &zPath[j], i-j);
+ }
+ j = i+1;
+ }while( zPath[i++] );
}
/*
@@ -41306,86 +43215,27 @@ static int unixFullPathname(
int nOut, /* Size of output buffer in bytes */
char *zOut /* Output buffer */
){
-#if !defined(HAVE_READLINK) || !defined(HAVE_LSTAT)
- return mkFullPathname(zPath, zOut, nOut);
-#else
- int rc = SQLITE_OK;
- int nByte;
- int nLink = 0; /* Number of symbolic links followed so far */
- const char *zIn = zPath; /* Input path for each iteration of loop */
- char *zDel = 0;
-
- assert( pVfs->mxPathname==MAX_PATHNAME );
+ DbPath path;
UNUSED_PARAMETER(pVfs);
-
- /* It's odd to simulate an io-error here, but really this is just
- ** using the io-error infrastructure to test that SQLite handles this
- ** function failing. This function could fail if, for example, the
- ** current working directory has been unlinked.
- */
- SimulateIOError( return SQLITE_ERROR );
-
- do {
-
- /* Call stat() on path zIn. Set bLink to true if the path is a symbolic
- ** link, or false otherwise. */
- int bLink = 0;
- struct stat buf;
- if( osLstat(zIn, &buf)!=0 ){
- if( errno!=ENOENT ){
- rc = unixLogError(SQLITE_CANTOPEN_BKPT, "lstat", zIn);
- }
- }else{
- bLink = S_ISLNK(buf.st_mode);
- }
-
- if( bLink ){
- nLink++;
- if( zDel==0 ){
- zDel = sqlite3_malloc(nOut);
- if( zDel==0 ) rc = SQLITE_NOMEM_BKPT;
- }else if( nLink>=SQLITE_MAX_SYMLINKS ){
- rc = SQLITE_CANTOPEN_BKPT;
- }
-
- if( rc==SQLITE_OK ){
- nByte = osReadlink(zIn, zDel, nOut-1);
- if( nByte<0 ){
- rc = unixLogError(SQLITE_CANTOPEN_BKPT, "readlink", zIn);
- }else{
- if( zDel[0]!='/' ){
- int n;
- for(n = sqlite3Strlen30(zIn); n>0 && zIn[n-1]!='/'; n--);
- if( nByte+n+1>nOut ){
- rc = SQLITE_CANTOPEN_BKPT;
- }else{
- memmove(&zDel[n], zDel, nByte+1);
- memcpy(zDel, zIn, n);
- nByte += n;
- }
- }
- zDel[nByte] = '\0';
- }
- }
-
- zIn = zDel;
- }
-
- assert( rc!=SQLITE_OK || zIn!=zOut || zIn[0]=='/' );
- if( rc==SQLITE_OK && zIn!=zOut ){
- rc = mkFullPathname(zIn, zOut, nOut);
+ path.rc = 0;
+ path.nUsed = 0;
+ path.nSymlink = 0;
+ path.nOut = nOut;
+ path.zOut = zOut;
+ if( zPath[0]!='/' ){
+ char zPwd[SQLITE_MAX_PATHLEN+2];
+ if( osGetcwd(zPwd, sizeof(zPwd)-2)==0 ){
+ return unixLogError(SQLITE_CANTOPEN_BKPT, "getcwd", zPath);
}
- if( bLink==0 ) break;
- zIn = zOut;
- }while( rc==SQLITE_OK );
-
- sqlite3_free(zDel);
- if( rc==SQLITE_OK && nLink ) rc = SQLITE_OK_SYMLINK;
- return rc;
-#endif /* HAVE_READLINK && HAVE_LSTAT */
+ appendAllPathElements(&path, zPwd);
+ }
+ appendAllPathElements(&path, zPath);
+ zOut[path.nUsed] = 0;
+ if( path.rc || path.nUsed<2 ) return SQLITE_CANTOPEN_BKPT;
+ if( path.nSymlink ) return SQLITE_OK_SYMLINK;
+ return SQLITE_OK;
}
-
#ifndef SQLITE_OMIT_LOAD_EXTENSION
/*
** Interfaces for opening a shared library, finding entry points
@@ -42881,8 +44731,16 @@ SQLITE_API int sqlite3_os_init(void){
/* Register all VFSes defined in the aVfs[] array */
for(i=0; i<(sizeof(aVfs)/sizeof(sqlite3_vfs)); i++){
+#ifdef SQLITE_DEFAULT_UNIX_VFS
+ sqlite3_vfs_register(&aVfs[i],
+ 0==strcmp(aVfs[i].zName,SQLITE_DEFAULT_UNIX_VFS));
+#else
sqlite3_vfs_register(&aVfs[i], i==0);
+#endif
}
+#ifdef SQLITE_OS_KV_OPTIONAL
+ sqlite3KvvfsInit();
+#endif
unixBigLock = sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_VFS1);
#ifndef SQLITE_OMIT_WAL
@@ -44845,10 +46703,12 @@ SQLITE_API int sqlite3_win32_set_directory8(
const char *zValue /* New value for directory being set or reset */
){
char **ppDirectory = 0;
+ int rc;
#ifndef SQLITE_OMIT_AUTOINIT
- int rc = sqlite3_initialize();
+ rc = sqlite3_initialize();
if( rc ) return rc;
#endif
+ sqlite3_mutex_enter(sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_TEMPDIR));
if( type==SQLITE_WIN32_DATA_DIRECTORY_TYPE ){
ppDirectory = &sqlite3_data_directory;
}else if( type==SQLITE_WIN32_TEMP_DIRECTORY_TYPE ){
@@ -44863,14 +46723,19 @@ SQLITE_API int sqlite3_win32_set_directory8(
if( zValue && zValue[0] ){
zCopy = sqlite3_mprintf("%s", zValue);
if ( zCopy==0 ){
- return SQLITE_NOMEM_BKPT;
+ rc = SQLITE_NOMEM_BKPT;
+ goto set_directory8_done;
}
}
sqlite3_free(*ppDirectory);
*ppDirectory = zCopy;
- return SQLITE_OK;
+ rc = SQLITE_OK;
+ }else{
+ rc = SQLITE_ERROR;
}
- return SQLITE_ERROR;
+set_directory8_done:
+ sqlite3_mutex_leave(sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_TEMPDIR));
+ return rc;
}
/*
@@ -47645,6 +49510,19 @@ static int winMakeEndInDirSep(int nBuf, char *zBuf){
}
/*
+** If sqlite3_temp_directory is defined, take the mutex and return true.
+**
+** If sqlite3_temp_directory is NULL (undefined), omit the mutex and
+** return false.
+*/
+static int winTempDirDefined(void){
+ sqlite3_mutex_enter(sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_TEMPDIR));
+ if( sqlite3_temp_directory!=0 ) return 1;
+ sqlite3_mutex_leave(sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_TEMPDIR));
+ return 0;
+}
+
+/*
** Create a temporary file name and store the resulting pointer into pzBuf.
** The pointer returned in pzBuf must be freed via sqlite3_free().
*/
@@ -47680,20 +49558,23 @@ static int winGetTempname(sqlite3_vfs *pVfs, char **pzBuf){
*/
nDir = nMax - (nPre + 15);
assert( nDir>0 );
- if( sqlite3_temp_directory ){
+ if( winTempDirDefined() ){
int nDirLen = sqlite3Strlen30(sqlite3_temp_directory);
if( nDirLen>0 ){
if( !winIsDirSep(sqlite3_temp_directory[nDirLen-1]) ){
nDirLen++;
}
if( nDirLen>nDir ){
+ sqlite3_mutex_leave(sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_TEMPDIR));
sqlite3_free(zBuf);
OSTRACE(("TEMP-FILENAME rc=SQLITE_ERROR\n"));
return winLogError(SQLITE_ERROR, 0, "winGetTempname1", 0);
}
sqlite3_snprintf(nMax, zBuf, "%s", sqlite3_temp_directory);
}
+ sqlite3_mutex_leave(sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_TEMPDIR));
}
+
#if defined(__CYGWIN__)
else{
static const char *azDirs[] = {
@@ -48482,7 +50363,7 @@ static BOOL winIsVerbatimPathname(
** pathname into zOut[]. zOut[] will be at least pVfs->mxPathname
** bytes in size.
*/
-static int winFullPathname(
+static int winFullPathnameNoMutex(
sqlite3_vfs *pVfs, /* Pointer to vfs object */
const char *zRelative, /* Possibly relative input path */
int nFull, /* Size of output buffer in bytes */
@@ -48661,6 +50542,20 @@ static int winFullPathname(
}
#endif
}
+static int winFullPathname(
+ sqlite3_vfs *pVfs, /* Pointer to vfs object */
+ const char *zRelative, /* Possibly relative input path */
+ int nFull, /* Size of output buffer in bytes */
+ char *zFull /* Output buffer */
+){
+ int rc;
+ MUTEX_LOGIC( sqlite3_mutex *pMutex; )
+ MUTEX_LOGIC( pMutex = sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_TEMPDIR); )
+ sqlite3_mutex_enter(pMutex);
+ rc = winFullPathnameNoMutex(pVfs, zRelative, nFull, zFull);
+ sqlite3_mutex_leave(pMutex);
+ return rc;
+}
#ifndef SQLITE_OMIT_LOAD_EXTENSION
/*
@@ -49197,6 +51092,7 @@ static int memdbTruncate(sqlite3_file*, sqlite3_int64 size);
static int memdbSync(sqlite3_file*, int flags);
static int memdbFileSize(sqlite3_file*, sqlite3_int64 *pSize);
static int memdbLock(sqlite3_file*, int);
+static int memdbUnlock(sqlite3_file*, int);
/* static int memdbCheckReservedLock(sqlite3_file*, int *pResOut);// not used */
static int memdbFileControl(sqlite3_file*, int op, void *pArg);
/* static int memdbSectorSize(sqlite3_file*); // not used */
@@ -49255,7 +51151,7 @@ static const sqlite3_io_methods memdb_io_methods = {
memdbSync, /* xSync */
memdbFileSize, /* xFileSize */
memdbLock, /* xLock */
- memdbLock, /* xUnlock - same as xLock in this case */
+ memdbUnlock, /* xUnlock */
0, /* memdbCheckReservedLock, */ /* xCheckReservedLock */
memdbFileControl, /* xFileControl */
0, /* memdbSectorSize,*/ /* xSectorSize */
@@ -49456,39 +51352,81 @@ static int memdbLock(sqlite3_file *pFile, int eLock){
MemFile *pThis = (MemFile*)pFile;
MemStore *p = pThis->pStore;
int rc = SQLITE_OK;
- if( eLock==pThis->eLock ) return SQLITE_OK;
+ if( eLock<=pThis->eLock ) return SQLITE_OK;
memdbEnter(p);
- if( eLock>SQLITE_LOCK_SHARED ){
- if( p->mFlags & SQLITE_DESERIALIZE_READONLY ){
- rc = SQLITE_READONLY;
- }else if( pThis->eLock<=SQLITE_LOCK_SHARED ){
- if( p->nWrLock ){
- rc = SQLITE_BUSY;
- }else{
- p->nWrLock = 1;
+
+ assert( p->nWrLock==0 || p->nWrLock==1 );
+ assert( pThis->eLock<=SQLITE_LOCK_SHARED || p->nWrLock==1 );
+ assert( pThis->eLock==SQLITE_LOCK_NONE || p->nRdLock>=1 );
+
+ if( eLock>SQLITE_LOCK_SHARED && (p->mFlags & SQLITE_DESERIALIZE_READONLY) ){
+ rc = SQLITE_READONLY;
+ }else{
+ switch( eLock ){
+ case SQLITE_LOCK_SHARED: {
+ assert( pThis->eLock==SQLITE_LOCK_NONE );
+ if( p->nWrLock>0 ){
+ rc = SQLITE_BUSY;
+ }else{
+ p->nRdLock++;
+ }
+ break;
+ };
+
+ case SQLITE_LOCK_RESERVED:
+ case SQLITE_LOCK_PENDING: {
+ assert( pThis->eLock>=SQLITE_LOCK_SHARED );
+ if( ALWAYS(pThis->eLock==SQLITE_LOCK_SHARED) ){
+ if( p->nWrLock>0 ){
+ rc = SQLITE_BUSY;
+ }else{
+ p->nWrLock = 1;
+ }
+ }
+ break;
+ }
+
+ default: {
+ assert( eLock==SQLITE_LOCK_EXCLUSIVE );
+ assert( pThis->eLock>=SQLITE_LOCK_SHARED );
+ if( p->nRdLock>1 ){
+ rc = SQLITE_BUSY;
+ }else if( pThis->eLock==SQLITE_LOCK_SHARED ){
+ p->nWrLock = 1;
+ }
+ break;
}
}
- }else if( eLock==SQLITE_LOCK_SHARED ){
- if( pThis->eLock > SQLITE_LOCK_SHARED ){
- assert( p->nWrLock==1 );
- p->nWrLock = 0;
- }else if( p->nWrLock ){
- rc = SQLITE_BUSY;
- }else{
- p->nRdLock++;
+ }
+ if( rc==SQLITE_OK ) pThis->eLock = eLock;
+ memdbLeave(p);
+ return rc;
+}
+
+/*
+** Unlock an memdb-file.
+*/
+static int memdbUnlock(sqlite3_file *pFile, int eLock){
+ MemFile *pThis = (MemFile*)pFile;
+ MemStore *p = pThis->pStore;
+ if( eLock>=pThis->eLock ) return SQLITE_OK;
+ memdbEnter(p);
+
+ assert( eLock==SQLITE_LOCK_SHARED || eLock==SQLITE_LOCK_NONE );
+ if( eLock==SQLITE_LOCK_SHARED ){
+ if( ALWAYS(pThis->eLock>SQLITE_LOCK_SHARED) ){
+ p->nWrLock--;
}
}else{
- assert( eLock==SQLITE_LOCK_NONE );
if( pThis->eLock>SQLITE_LOCK_SHARED ){
- assert( p->nWrLock==1 );
- p->nWrLock = 0;
+ p->nWrLock--;
}
- assert( p->nRdLock>0 );
p->nRdLock--;
}
- if( rc==SQLITE_OK ) pThis->eLock = eLock;
+
+ pThis->eLock = eLock;
memdbLeave(p);
- return rc;
+ return SQLITE_OK;
}
#if 0
@@ -49598,7 +51536,7 @@ static int memdbOpen(
memset(pFile, 0, sizeof(*pFile));
szName = sqlite3Strlen30(zName);
- if( szName>1 && zName[0]=='/' ){
+ if( szName>1 && (zName[0]=='/' || zName[0]=='\\') ){
int i;
#ifndef SQLITE_MUTEX_OMIT
sqlite3_mutex *pVfsMutex = sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_VFS1);
@@ -49946,6 +51884,13 @@ end_deserialize:
}
/*
+** Return true if the VFS is the memvfs.
+*/
+//SQLITE_PRIVATE int sqlite3IsMemdb(const sqlite3_vfs *pVfs){
+// return pVfs==&memdb_vfs;
+//}
+
+/*
** This routine is called when the extension is loaded.
** Register the new VFS.
*/
@@ -50449,12 +52394,20 @@ struct PCache {
int sqlite3PcacheTrace = 2; /* 0: off 1: simple 2: cache dumps */
int sqlite3PcacheMxDump = 9999; /* Max cache entries for pcacheDump() */
# define pcacheTrace(X) if(sqlite3PcacheTrace){sqlite3DebugPrintf X;}
- void pcacheDump(PCache *pCache){
- int N;
- int i, j;
- sqlite3_pcache_page *pLower;
+ static void pcachePageTrace(int i, sqlite3_pcache_page *pLower){
PgHdr *pPg;
unsigned char *a;
+ int j;
+ pPg = (PgHdr*)pLower->pExtra;
+ printf("%3d: nRef %2d flgs %02x data ", i, pPg->nRef, pPg->flags);
+ a = (unsigned char *)pLower->pBuf;
+ for(j=0; j<12; j++) printf("%02x", a[j]);
+ printf(" ptr %p\n", pPg);
+ }
+ static void pcacheDump(PCache *pCache){
+ int N;
+ int i;
+ sqlite3_pcache_page *pLower;
if( sqlite3PcacheTrace<2 ) return;
if( pCache->pCache==0 ) return;
@@ -50463,22 +52416,33 @@ struct PCache {
for(i=1; i<=N; i++){
pLower = sqlite3GlobalConfig.pcache2.xFetch(pCache->pCache, i, 0);
if( pLower==0 ) continue;
- pPg = (PgHdr*)pLower->pExtra;
- printf("%3d: nRef %2d flgs %02x data ", i, pPg->nRef, pPg->flags);
- a = (unsigned char *)pLower->pBuf;
- for(j=0; j<12; j++) printf("%02x", a[j]);
- printf("\n");
- if( pPg->pPage==0 ){
+ pcachePageTrace(i, pLower);
+ if( ((PgHdr*)pLower)->pPage==0 ){
sqlite3GlobalConfig.pcache2.xUnpin(pCache->pCache, pLower, 0);
}
}
}
- #else
+#else
# define pcacheTrace(X)
+# define pcachePageTrace(PGNO, X)
# define pcacheDump(X)
#endif
/*
+** Return 1 if pPg is on the dirty list for pCache. Return 0 if not.
+** This routine runs inside of assert() statements only.
+*/
+#ifdef SQLITE_DEBUG
+static int pageOnDirtyList(PCache *pCache, PgHdr *pPg){
+ PgHdr *p;
+ for(p=pCache->pDirty; p; p=p->pDirtyNext){
+ if( p==pPg ) return 1;
+ }
+ return 0;
+}
+#endif
+
+/*
** Check invariants on a PgHdr entry. Return true if everything is OK.
** Return false if any invariant is violated.
**
@@ -50496,8 +52460,13 @@ SQLITE_PRIVATE int sqlite3PcachePageSanity(PgHdr *pPg){
assert( pCache!=0 ); /* Every page has an associated PCache */
if( pPg->flags & PGHDR_CLEAN ){
assert( (pPg->flags & PGHDR_DIRTY)==0 );/* Cannot be both CLEAN and DIRTY */
- assert( pCache->pDirty!=pPg ); /* CLEAN pages not on dirty list */
- assert( pCache->pDirtyTail!=pPg );
+ assert( !pageOnDirtyList(pCache, pPg) );/* CLEAN pages not on dirty list */
+ }else{
+ assert( (pPg->flags & PGHDR_DIRTY)!=0 );/* If not CLEAN must be DIRTY */
+ assert( pPg->pDirtyNext==0 || pPg->pDirtyNext->pDirtyPrev==pPg );
+ assert( pPg->pDirtyPrev==0 || pPg->pDirtyPrev->pDirtyNext==pPg );
+ assert( pPg->pDirtyPrev!=0 || pCache->pDirty==pPg );
+ assert( pageOnDirtyList(pCache, pPg) );
}
/* WRITEABLE pages must also be DIRTY */
if( pPg->flags & PGHDR_WRITEABLE ){
@@ -50771,8 +52740,9 @@ SQLITE_PRIVATE sqlite3_pcache_page *sqlite3PcacheFetch(
assert( createFlag==0 || pCache->eCreate==eCreate );
assert( createFlag==0 || eCreate==1+(!pCache->bPurgeable||!pCache->pDirty) );
pRes = sqlite3GlobalConfig.pcache2.xFetch(pCache->pCache, pgno, eCreate);
- pcacheTrace(("%p.FETCH %d%s (result: %p)\n",pCache,pgno,
+ pcacheTrace(("%p.FETCH %d%s (result: %p) ",pCache,pgno,
createFlag?" create":"",pRes));
+ pcachePageTrace(pgno, pRes);
return pRes;
}
@@ -50900,6 +52870,7 @@ SQLITE_PRIVATE void SQLITE_NOINLINE sqlite3PcacheRelease(PgHdr *p){
pcacheUnpin(p);
}else{
pcacheManageDirtyList(p, PCACHE_DIRTYLIST_FRONT);
+ assert( sqlite3PcachePageSanity(p) );
}
}
}
@@ -50934,8 +52905,7 @@ SQLITE_PRIVATE void sqlite3PcacheDrop(PgHdr *p){
** make it so.
*/
SQLITE_PRIVATE void sqlite3PcacheMakeDirty(PgHdr *p){
- assert( p->nRef>0 || p->pCache->bPurgeable==0 );
- testcase( p->nRef==0 );
+ assert( p->nRef>0 );
assert( sqlite3PcachePageSanity(p) );
if( p->flags & (PGHDR_CLEAN|PGHDR_DONT_WRITE) ){ /*OPTIMIZATION-IF-FALSE*/
p->flags &= ~PGHDR_DONT_WRITE;
@@ -50944,6 +52914,7 @@ SQLITE_PRIVATE void sqlite3PcacheMakeDirty(PgHdr *p){
pcacheTrace(("%p.DIRTY %d\n",p->pCache,p->pgno));
assert( (p->flags & (PGHDR_DIRTY|PGHDR_CLEAN))==PGHDR_DIRTY );
pcacheManageDirtyList(p, PCACHE_DIRTYLIST_ADD);
+ assert( sqlite3PcachePageSanity(p) );
}
assert( sqlite3PcachePageSanity(p) );
}
@@ -51006,14 +52977,24 @@ SQLITE_PRIVATE void sqlite3PcacheClearSyncFlags(PCache *pCache){
*/
SQLITE_PRIVATE void sqlite3PcacheMove(PgHdr *p, Pgno newPgno){
PCache *pCache = p->pCache;
+ sqlite3_pcache_page *pOther;
assert( p->nRef>0 );
assert( newPgno>0 );
assert( sqlite3PcachePageSanity(p) );
pcacheTrace(("%p.MOVE %d -> %d\n",pCache,p->pgno,newPgno));
+ pOther = sqlite3GlobalConfig.pcache2.xFetch(pCache->pCache, newPgno, 0);
+ if( pOther ){
+ PgHdr *pXPage = (PgHdr*)pOther->pExtra;
+ assert( pXPage->nRef==0 );
+ pXPage->nRef++;
+ pCache->nRefSum++;
+ sqlite3PcacheDrop(pXPage);
+ }
sqlite3GlobalConfig.pcache2.xRekey(pCache->pCache, p->pPage, p->pgno,newPgno);
p->pgno = newPgno;
if( (p->flags&PGHDR_DIRTY) && (p->flags&PGHDR_NEED_SYNC) ){
pcacheManageDirtyList(p, PCACHE_DIRTYLIST_FRONT);
+ assert( sqlite3PcachePageSanity(p) );
}
}
@@ -51311,12 +53292,13 @@ SQLITE_PRIVATE void sqlite3PcacheIterateDirty(PCache *pCache, void (*xIter)(PgHd
** size can vary according to architecture, compile-time options, and
** SQLite library version number.
**
-** If SQLITE_PCACHE_SEPARATE_HEADER is defined, then the extension is obtained
-** using a separate memory allocation from the database page content. This
-** seeks to overcome the "clownshoe" problem (also called "internal
-** fragmentation" in academic literature) of allocating a few bytes more
-** than a power of two with the memory allocator rounding up to the next
-** power of two, and leaving the rounded-up space unused.
+** Historical note: It used to be that if the SQLITE_PCACHE_SEPARATE_HEADER
+** was defined, then the page content would be held in a separate memory
+** allocation from the PgHdr1. This was intended to avoid clownshoe memory
+** allocations. However, the btree layer needs a small (16-byte) overrun
+** area after the page content buffer. The header serves as that overrun
+** area. Therefore SQLITE_PCACHE_SEPARATE_HEADER was discontinued to avoid
+** any possibility of a memory error.
**
** This module tracks pointers to PgHdr1 objects. Only pcache.c communicates
** with this module. Information is passed back and forth as PgHdr1 pointers.
@@ -51361,30 +53343,40 @@ typedef struct PGroup PGroup;
/*
** Each cache entry is represented by an instance of the following
-** structure. Unless SQLITE_PCACHE_SEPARATE_HEADER is defined, a buffer of
-** PgHdr1.pCache->szPage bytes is allocated directly before this structure
-** in memory.
+** structure. A buffer of PgHdr1.pCache->szPage bytes is allocated
+** directly before this structure and is used to cache the page content.
**
-** Note: Variables isBulkLocal and isAnchor were once type "u8". That works,
+** When reading a corrupt database file, it is possible that SQLite might
+** read a few bytes (no more than 16 bytes) past the end of the page buffer.
+** It will only read past the end of the page buffer, never write. This
+** object is positioned immediately after the page buffer to serve as an
+** overrun area, so that overreads are harmless.
+**
+** Variables isBulkLocal and isAnchor were once type "u8". That works,
** but causes a 2-byte gap in the structure for most architectures (since
** pointers must be either 4 or 8-byte aligned). As this structure is located
** in memory directly after the associated page data, if the database is
** corrupt, code at the b-tree layer may overread the page buffer and
** read part of this structure before the corruption is detected. This
** can cause a valgrind error if the unitialized gap is accessed. Using u16
-** ensures there is no such gap, and therefore no bytes of unitialized memory
-** in the structure.
+** ensures there is no such gap, and therefore no bytes of uninitialized
+** memory in the structure.
+**
+** The pLruNext and pLruPrev pointers form a double-linked circular list
+** of all pages that are unpinned. The PGroup.lru element (which should be
+** the only element on the list with PgHdr1.isAnchor set to 1) forms the
+** beginning and the end of the list.
*/
struct PgHdr1 {
- sqlite3_pcache_page page; /* Base class. Must be first. pBuf & pExtra */
- unsigned int iKey; /* Key value (page number) */
- u16 isBulkLocal; /* This page from bulk local storage */
- u16 isAnchor; /* This is the PGroup.lru element */
- PgHdr1 *pNext; /* Next in hash table chain */
- PCache1 *pCache; /* Cache that currently owns this page */
- PgHdr1 *pLruNext; /* Next in LRU list of unpinned pages */
- PgHdr1 *pLruPrev; /* Previous in LRU list of unpinned pages */
- /* NB: pLruPrev is only valid if pLruNext!=0 */
+ sqlite3_pcache_page page; /* Base class. Must be first. pBuf & pExtra */
+ unsigned int iKey; /* Key value (page number) */
+ u16 isBulkLocal; /* This page from bulk local storage */
+ u16 isAnchor; /* This is the PGroup.lru element */
+ PgHdr1 *pNext; /* Next in hash table chain */
+ PCache1 *pCache; /* Cache that currently owns this page */
+ PgHdr1 *pLruNext; /* Next in circular LRU list of unpinned pages */
+ PgHdr1 *pLruPrev; /* Previous in LRU list of unpinned pages */
+ /* NB: pLruPrev is only valid if pLruNext!=0 */
};
/*
@@ -51710,25 +53702,13 @@ static PgHdr1 *pcache1AllocPage(PCache1 *pCache, int benignMalloc){
pcache1LeaveMutex(pCache->pGroup);
#endif
if( benignMalloc ){ sqlite3BeginBenignMalloc(); }
-#ifdef SQLITE_PCACHE_SEPARATE_HEADER
- pPg = pcache1Alloc(pCache->szPage);
- p = sqlite3Malloc(sizeof(PgHdr1) + pCache->szExtra);
- if( !pPg || !p ){
- pcache1Free(pPg);
- sqlite3_free(p);
- pPg = 0;
- }
-#else
pPg = pcache1Alloc(pCache->szAlloc);
-#endif
if( benignMalloc ){ sqlite3EndBenignMalloc(); }
#ifdef SQLITE_ENABLE_MEMORY_MANAGEMENT
pcache1EnterMutex(pCache->pGroup);
#endif
if( pPg==0 ) return 0;
-#ifndef SQLITE_PCACHE_SEPARATE_HEADER
p = (PgHdr1 *)&((u8 *)pPg)[pCache->szPage];
-#endif
p->page.pBuf = pPg;
p->page.pExtra = &p[1];
p->isBulkLocal = 0;
@@ -51752,9 +53732,6 @@ static void pcache1FreePage(PgHdr1 *p){
pCache->pFree = p;
}else{
pcache1Free(p->page.pBuf);
-#ifdef SQLITE_PCACHE_SEPARATE_HEADER
- sqlite3_free(p);
-#endif
}
(*pCache->pnPurgeable)--;
}
@@ -52395,23 +54372,26 @@ static void pcache1Rekey(
PCache1 *pCache = (PCache1 *)p;
PgHdr1 *pPage = (PgHdr1 *)pPg;
PgHdr1 **pp;
- unsigned int h;
+ unsigned int hOld, hNew;
assert( pPage->iKey==iOld );
assert( pPage->pCache==pCache );
+ assert( iOld!=iNew ); /* The page number really is changing */
pcache1EnterMutex(pCache->pGroup);
- h = iOld%pCache->nHash;
- pp = &pCache->apHash[h];
+ assert( pcache1FetchNoMutex(p, iOld, 0)==pPage ); /* pPg really is iOld */
+ hOld = iOld%pCache->nHash;
+ pp = &pCache->apHash[hOld];
while( (*pp)!=pPage ){
pp = &(*pp)->pNext;
}
*pp = pPage->pNext;
- h = iNew%pCache->nHash;
+ assert( pcache1FetchNoMutex(p, iNew, 0)==0 ); /* iNew not in cache */
+ hNew = iNew%pCache->nHash;
pPage->iKey = iNew;
- pPage->pNext = pCache->apHash[h];
- pCache->apHash[h] = pPage;
+ pPage->pNext = pCache->apHash[hNew];
+ pCache->apHash[hNew] = pPage;
if( iNew>pCache->iMaxKey ){
pCache->iMaxKey = iNew;
}
@@ -52518,9 +54498,6 @@ SQLITE_PRIVATE int sqlite3PcacheReleaseMemory(int nReq){
&& p->isAnchor==0
){
nFree += pcache1MemSize(p->page.pBuf);
-#ifdef SQLITE_PCACHE_SEPARATE_HEADER
- nFree += sqlite3MemSize(p);
-#endif
assert( PAGE_IS_UNPINNED(p) );
pcache1PinPage(p);
pcache1RemoveFromHash(p, 1);
@@ -53904,6 +55881,7 @@ struct Pager {
u32 vfsFlags; /* Flags for sqlite3_vfs.xOpen() */
u32 sectorSize; /* Assumed sector size during rollback */
Pgno mxPgno; /* Maximum allowed size of the database */
+ Pgno lckPgno; /* Page number for the locking page */
i64 pageSize; /* Number of bytes in a page */
i64 journalSizeLimit; /* Size limit for persistent journal files */
char *zFilename; /* Name of the database file */
@@ -54890,7 +56868,7 @@ static int readJournalHdr(
** journal file descriptor is advanced to the next sector boundary before
** anything is written. The format is:
**
-** + 4 bytes: PAGER_MJ_PGNO.
+** + 4 bytes: PAGER_SJ_PGNO.
** + N bytes: super-journal filename in utf-8.
** + 4 bytes: N (length of super-journal name in bytes, no nul-terminator).
** + 4 bytes: super-journal name checksum.
@@ -54938,7 +56916,7 @@ static int writeSuperJournal(Pager *pPager, const char *zSuper){
/* Write the super-journal data to the end of the journal file. If
** an error occurs, return the error code to the caller.
*/
- if( (0 != (rc = write32bits(pPager->jfd, iHdrOff, PAGER_MJ_PGNO(pPager))))
+ if( (0 != (rc = write32bits(pPager->jfd, iHdrOff, PAGER_SJ_PGNO(pPager))))
|| (0 != (rc = sqlite3OsWrite(pPager->jfd, zSuper, nSuper, iHdrOff+4)))
|| (0 != (rc = write32bits(pPager->jfd, iHdrOff+4+nSuper, nSuper)))
|| (0 != (rc = write32bits(pPager->jfd, iHdrOff+4+nSuper+4, cksum)))
@@ -55448,7 +57426,7 @@ static u32 pager_cksum(Pager *pPager, const u8 *aData){
** corrupted, SQLITE_DONE is returned. Data is considered corrupted in
** two circumstances:
**
-** * If the record page-number is illegal (0 or PAGER_MJ_PGNO), or
+** * If the record page-number is illegal (0 or PAGER_SJ_PGNO), or
** * If the record is being rolled back from the main journal file
** and the checksum field does not match the record content.
**
@@ -55508,7 +57486,7 @@ static int pager_playback_one_page(
** it could cause invalid data to be written into the journal. We need to
** detect this invalid data (with high probability) and ignore it.
*/
- if( pgno==0 || pgno==PAGER_MJ_PGNO(pPager) ){
+ if( pgno==0 || pgno==PAGER_SJ_PGNO(pPager) ){
assert( !isSavepnt );
return SQLITE_DONE;
}
@@ -55845,6 +57823,7 @@ static int pager_truncate(Pager *pPager, Pgno nPage){
memset(pTmp, 0, szPage);
testcase( (newSize-szPage) == currentSize );
testcase( (newSize-szPage) > currentSize );
+ sqlite3OsFileControlHint(pPager->fd, SQLITE_FCNTL_SIZE_HINT, &newSize);
rc = sqlite3OsWrite(pPager->fd, pTmp, szPage, newSize-szPage);
}
if( rc==SQLITE_OK ){
@@ -56966,6 +58945,7 @@ SQLITE_PRIVATE int sqlite3PagerSetPagesize(Pager *pPager, u32 *pPageSize, int nR
pPager->pTmpSpace = pNew;
pPager->dbSize = (Pgno)((nByte+pageSize-1)/pageSize);
pPager->pageSize = pageSize;
+ pPager->lckPgno = (Pgno)(PENDING_BYTE/pageSize) + 1;
}else{
sqlite3PageFree(pNew);
}
@@ -58735,7 +60715,7 @@ static int getPageNormal(
if( pPg->pPager && !noContent ){
/* In this case the pcache already contains an initialized copy of
** the page. Return without further ado. */
- assert( pgno!=PAGER_MJ_PGNO(pPager) );
+ assert( pgno!=PAGER_SJ_PGNO(pPager) );
pPager->aStat[PAGER_STAT_HIT]++;
return SQLITE_OK;
@@ -58746,7 +60726,7 @@ static int getPageNormal(
** (*) obsolete. Was: maximum page number is 2^31
** (2) Never try to fetch the locking page
*/
- if( pgno==PAGER_MJ_PGNO(pPager) ){
+ if( pgno==PAGER_SJ_PGNO(pPager) ){
rc = SQLITE_CORRUPT_BKPT;
goto pager_acquire_err;
}
@@ -59006,6 +60986,7 @@ static int pager_open_journal(Pager *pPager){
if( pPager->tempFile ){
flags |= (SQLITE_OPEN_DELETEONCLOSE|SQLITE_OPEN_TEMP_JOURNAL);
+ flags |= SQLITE_OPEN_EXCLUSIVE;
nSpill = sqlite3Config.nStmtSpill;
}else{
flags |= SQLITE_OPEN_MAIN_JOURNAL;
@@ -59041,6 +61022,7 @@ static int pager_open_journal(Pager *pPager){
if( rc!=SQLITE_OK ){
sqlite3BitvecDestroy(pPager->pInJournal);
pPager->pInJournal = 0;
+ pPager->journalOff = 0;
}else{
assert( pPager->eState==PAGER_WRITER_LOCKED );
pPager->eState = PAGER_WRITER_CACHEMOD;
@@ -59145,7 +61127,7 @@ static SQLITE_NOINLINE int pagerAddPageToRollbackJournal(PgHdr *pPg){
/* We should never write to the journal file the page that
** contains the database locks. The following assert verifies
** that we do not. */
- assert( pPg->pgno!=PAGER_MJ_PGNO(pPager) );
+ assert( pPg->pgno!=PAGER_SJ_PGNO(pPager) );
assert( pPager->journalHdr<=pPager->journalOff );
pData2 = pPg->pData;
@@ -59324,7 +61306,7 @@ static SQLITE_NOINLINE int pagerWriteLargeSector(PgHdr *pPg){
Pgno pg = pg1+ii;
PgHdr *pPage;
if( pg==pPg->pgno || !sqlite3BitvecTest(pPager->pInJournal, pg) ){
- if( pg!=PAGER_MJ_PGNO(pPager) ){
+ if( pg!=PAGER_SJ_PGNO(pPager) ){
rc = sqlite3PagerGet(pPager, pg, &pPage, 0);
if( rc==SQLITE_OK ){
rc = pager_write(pPage);
@@ -59802,7 +61784,7 @@ SQLITE_PRIVATE int sqlite3PagerCommitPhaseOne(
** last page is never written out to disk, leaving the database file
** undersized. Fix this now if it is the case. */
if( pPager->dbSize>pPager->dbFileSize ){
- Pgno nNew = pPager->dbSize - (pPager->dbSize==PAGER_MJ_PGNO(pPager));
+ Pgno nNew = pPager->dbSize - (pPager->dbSize==PAGER_SJ_PGNO(pPager));
assert( pPager->eState==PAGER_WRITER_DBMOD );
rc = pager_truncate(pPager, nNew);
if( rc!=SQLITE_OK ) goto commit_phase_one_exit;
@@ -65431,7 +67413,7 @@ struct MemPage {
u8 *aData; /* Pointer to disk image of the page data */
u8 *aDataEnd; /* One byte past the end of the entire page - not just
** the usable space, the entire page. Used to prevent
- ** corruption-induced of buffer overflow. */
+ ** corruption-induced buffer overflow. */
u8 *aCellIdx; /* The cell index area */
u8 *aDataOfst; /* Same as aData for leaves. aData+4 for interior */
DbPage *pDbPage; /* Pager page handle */
@@ -65736,7 +67718,7 @@ struct BtCursor {
/*
** The database page the PENDING_BYTE occupies. This page is never used.
*/
-# define PENDING_BYTE_PAGE(pBt) PAGER_MJ_PGNO(pBt)
+#define PENDING_BYTE_PAGE(pBt) ((Pgno)((PENDING_BYTE/((pBt)->pageSize))+1))
/*
** These macros define the location of the pointer-map entry for a
@@ -66104,6 +68086,7 @@ SQLITE_PRIVATE int sqlite3BtreeHoldsAllMutexes(sqlite3 *db){
SQLITE_PRIVATE int sqlite3SchemaMutexHeld(sqlite3 *db, int iDb, Schema *pSchema){
Btree *p;
assert( db!=0 );
+ if( db->pVfs==0 && db->nDb==0 ) return 1;
if( pSchema ) iDb = sqlite3SchemaToIndex(db, pSchema);
assert( iDb>=0 && iDb<db->nDb );
if( !sqlite3_mutex_held(db->mutex) ) return 0;
@@ -66377,7 +68360,7 @@ static int hasSharedCacheTableLock(
int bSeen = 0;
for(p=sqliteHashFirst(&pSchema->idxHash); p; p=sqliteHashNext(p)){
Index *pIdx = (Index *)sqliteHashData(p);
- if( pIdx->tnum==(int)iRoot ){
+ if( pIdx->tnum==iRoot ){
if( bSeen ){
/* Two or more indexes share the same root page. There must
** be imposter tables. So just return true. The assert is not
@@ -66970,7 +68953,7 @@ SQLITE_PRIVATE void sqlite3BtreeClearCursor(BtCursor *pCur){
/*
** In this version of BtreeMoveto, pKey is a packed index record
** such as is generated by the OP_MakeRecord opcode. Unpack the
-** record and then call BtreeMovetoUnpacked() to do the work.
+** record and then call sqlite3BtreeIndexMoveto() to do the work.
*/
static int btreeMoveto(
BtCursor *pCur, /* Cursor open on the btree to be searched */
@@ -67490,6 +69473,7 @@ static void btreeParseCell(
** the space used by the cell pointer.
**
** cellSizePtrNoPayload() => table internal nodes
+** cellSizePtrTableLeaf() => table leaf nodes
** cellSizePtr() => all index nodes & table leaf nodes
*/
static u16 cellSizePtr(MemPage *pPage, u8 *pCell){
@@ -67515,13 +69499,6 @@ static u16 cellSizePtr(MemPage *pPage, u8 *pCell){
}while( *(pIter)>=0x80 && pIter<pEnd );
}
pIter++;
- if( pPage->intKey ){
- /* pIter now points at the 64-bit integer key value, a variable length
- ** integer. The following block moves pIter to point at the first byte
- ** past the end of the key value. */
- pEnd = &pIter[9];
- while( (*pIter++)&0x80 && pIter<pEnd );
- }
testcase( nSize==pPage->maxLocal );
testcase( nSize==(u32)pPage->maxLocal+1 );
if( nSize<=pPage->maxLocal ){
@@ -67561,6 +69538,58 @@ static u16 cellSizePtrNoPayload(MemPage *pPage, u8 *pCell){
assert( debuginfo.nSize==(u16)(pIter - pCell) || CORRUPT_DB );
return (u16)(pIter - pCell);
}
+static u16 cellSizePtrTableLeaf(MemPage *pPage, u8 *pCell){
+ u8 *pIter = pCell; /* For looping over bytes of pCell */
+ u8 *pEnd; /* End mark for a varint */
+ u32 nSize; /* Size value to return */
+
+#ifdef SQLITE_DEBUG
+ /* The value returned by this function should always be the same as
+ ** the (CellInfo.nSize) value found by doing a full parse of the
+ ** cell. If SQLITE_DEBUG is defined, an assert() at the bottom of
+ ** this function verifies that this invariant is not violated. */
+ CellInfo debuginfo;
+ pPage->xParseCell(pPage, pCell, &debuginfo);
+#endif
+
+ nSize = *pIter;
+ if( nSize>=0x80 ){
+ pEnd = &pIter[8];
+ nSize &= 0x7f;
+ do{
+ nSize = (nSize<<7) | (*++pIter & 0x7f);
+ }while( *(pIter)>=0x80 && pIter<pEnd );
+ }
+ pIter++;
+ /* pIter now points at the 64-bit integer key value, a variable length
+ ** integer. The following block moves pIter to point at the first byte
+ ** past the end of the key value. */
+ if( (*pIter++)&0x80
+ && (*pIter++)&0x80
+ && (*pIter++)&0x80
+ && (*pIter++)&0x80
+ && (*pIter++)&0x80
+ && (*pIter++)&0x80
+ && (*pIter++)&0x80
+ && (*pIter++)&0x80 ){ pIter++; }
+ testcase( nSize==pPage->maxLocal );
+ testcase( nSize==(u32)pPage->maxLocal+1 );
+ if( nSize<=pPage->maxLocal ){
+ nSize += (u32)(pIter - pCell);
+ if( nSize<4 ) nSize = 4;
+ }else{
+ int minLocal = pPage->minLocal;
+ nSize = minLocal + (nSize - minLocal) % (pPage->pBt->usableSize - 4);
+ testcase( nSize==pPage->maxLocal );
+ testcase( nSize==(u32)pPage->maxLocal+1 );
+ if( nSize>pPage->maxLocal ){
+ nSize = minLocal;
+ }
+ nSize += 4 + (u16)(pIter - pCell);
+ }
+ assert( nSize==debuginfo.nSize || CORRUPT_DB );
+ return (u16)nSize;
+}
#ifdef SQLITE_DEBUG
@@ -67574,7 +69603,7 @@ static u16 cellSize(MemPage *pPage, int iCell){
#ifndef SQLITE_OMIT_AUTOVACUUM
/*
** The cell pCell is currently part of page pSrc but will ultimately be part
-** of pPage. (pSrc and pPager are often the same.) If pCell contains a
+** of pPage. (pSrc and pPage are often the same.) If pCell contains a
** pointer to an overflow page, insert an entry into the pointer-map for
** the overflow page that will be valid after pCell has been moved to pPage.
*/
@@ -67630,8 +69659,7 @@ static int defragmentPage(MemPage *pPage, int nMaxFrag){
assert( pPage->pBt->usableSize <= SQLITE_MAX_PAGE_SIZE );
assert( pPage->nOverflow==0 );
assert( sqlite3_mutex_held(pPage->pBt->mutex) );
- temp = 0;
- src = data = pPage->aData;
+ data = pPage->aData;
hdr = pPage->hdrOffset;
cellOffset = pPage->cellOffset;
nCell = pPage->nCell;
@@ -67665,7 +69693,7 @@ static int defragmentPage(MemPage *pPage, int nMaxFrag){
if( iFree2+sz2 > usableSize ) return SQLITE_CORRUPT_PAGE(pPage);
memmove(&data[iFree+sz+sz2], &data[iFree+sz], iFree2-(iFree+sz));
sz += sz2;
- }else if( NEVER(iFree+sz>usableSize) ){
+ }else if( iFree+sz>usableSize ){
return SQLITE_CORRUPT_PAGE(pPage);
}
@@ -67685,39 +69713,38 @@ static int defragmentPage(MemPage *pPage, int nMaxFrag){
cbrk = usableSize;
iCellLast = usableSize - 4;
iCellStart = get2byte(&data[hdr+5]);
- for(i=0; i<nCell; i++){
- u8 *pAddr; /* The i-th cell pointer */
- pAddr = &data[cellOffset + i*2];
- pc = get2byte(pAddr);
- testcase( pc==iCellFirst );
- testcase( pc==iCellLast );
- /* These conditions have already been verified in btreeInitPage()
- ** if PRAGMA cell_size_check=ON.
- */
- if( pc<iCellStart || pc>iCellLast ){
- return SQLITE_CORRUPT_PAGE(pPage);
- }
- assert( pc>=iCellStart && pc<=iCellLast );
- size = pPage->xCellSize(pPage, &src[pc]);
- cbrk -= size;
- if( cbrk<iCellStart || pc+size>usableSize ){
- return SQLITE_CORRUPT_PAGE(pPage);
- }
- assert( cbrk+size<=usableSize && cbrk>=iCellStart );
- testcase( cbrk+size==usableSize );
- testcase( pc+size==usableSize );
- put2byte(pAddr, cbrk);
- if( temp==0 ){
- if( cbrk==pc ) continue;
- temp = sqlite3PagerTempSpace(pPage->pBt->pPager);
- memcpy(&temp[iCellStart], &data[iCellStart], usableSize - iCellStart);
- src = temp;
+ if( nCell>0 ){
+ temp = sqlite3PagerTempSpace(pPage->pBt->pPager);
+ memcpy(&temp[iCellStart], &data[iCellStart], usableSize - iCellStart);
+ src = temp;
+ for(i=0; i<nCell; i++){
+ u8 *pAddr; /* The i-th cell pointer */
+ pAddr = &data[cellOffset + i*2];
+ pc = get2byte(pAddr);
+ testcase( pc==iCellFirst );
+ testcase( pc==iCellLast );
+ /* These conditions have already been verified in btreeInitPage()
+ ** if PRAGMA cell_size_check=ON.
+ */
+ if( pc<iCellStart || pc>iCellLast ){
+ return SQLITE_CORRUPT_PAGE(pPage);
+ }
+ assert( pc>=iCellStart && pc<=iCellLast );
+ size = pPage->xCellSize(pPage, &src[pc]);
+ cbrk -= size;
+ if( cbrk<iCellStart || pc+size>usableSize ){
+ return SQLITE_CORRUPT_PAGE(pPage);
+ }
+ assert( cbrk+size<=usableSize && cbrk>=iCellStart );
+ testcase( cbrk+size==usableSize );
+ testcase( pc+size==usableSize );
+ put2byte(pAddr, cbrk);
+ memcpy(&data[cbrk], &src[pc], size);
}
- memcpy(&data[cbrk], &src[pc], size);
}
data[hdr+7] = 0;
- defragment_out:
+defragment_out:
assert( pPage->nFree>=0 );
if( data[hdr+7]+cbrk-iCellFirst!=pPage->nFree ){
return SQLITE_CORRUPT_PAGE(pPage);
@@ -67749,7 +69776,8 @@ static u8 *pageFindSlot(MemPage *pPg, int nByte, int *pRc){
const int hdr = pPg->hdrOffset; /* Offset to page header */
u8 * const aData = pPg->aData; /* Page data */
int iAddr = hdr + 1; /* Address of ptr to pc */
- int pc = get2byte(&aData[iAddr]); /* Address of a free slot */
+ u8 *pTmp = &aData[iAddr]; /* Temporary ptr into aData[] */
+ int pc = get2byte(pTmp); /* Address of a free slot */
int x; /* Excess size of the slot */
int maxPC = pPg->pBt->usableSize - nByte; /* Max address for a usable slot */
int size; /* Size of the free slot */
@@ -67759,7 +69787,8 @@ static u8 *pageFindSlot(MemPage *pPg, int nByte, int *pRc){
/* EVIDENCE-OF: R-22710-53328 The third and fourth bytes of each
** freeblock form a big-endian integer which is the size of the freeblock
** in bytes, including the 4-byte header. */
- size = get2byte(&aData[pc+2]);
+ pTmp = &aData[pc+2];
+ size = get2byte(pTmp);
if( (x = size - nByte)>=0 ){
testcase( x==4 );
testcase( x==3 );
@@ -67772,7 +69801,6 @@ static u8 *pageFindSlot(MemPage *pPg, int nByte, int *pRc){
** fragmented bytes within the page. */
memcpy(&aData[iAddr], &aData[pc], 2);
aData[hdr+7] += (u8)x;
- testcase( pc+x>maxPC );
return &aData[pc];
}else if( x+pc > maxPC ){
/* This slot extends off the end of the usable part of the page */
@@ -67786,10 +69814,11 @@ static u8 *pageFindSlot(MemPage *pPg, int nByte, int *pRc){
return &aData[pc + x];
}
iAddr = pc;
- pc = get2byte(&aData[pc]);
- if( pc<=iAddr+size ){
+ pTmp = &aData[pc];
+ pc = get2byte(pTmp);
+ if( pc<=iAddr ){
if( pc ){
- /* The next slot in the chain is not past the end of the current slot */
+ /* The next slot in the chain comes before the current slot */
*pRc = SQLITE_CORRUPT_PAGE(pPg);
}
return 0;
@@ -67820,6 +69849,7 @@ static int allocateSpace(MemPage *pPage, int nByte, int *pIdx){
u8 * const data = pPage->aData; /* Local cache of pPage->aData */
int top; /* First byte of cell content area */
int rc = SQLITE_OK; /* Integer return code */
+ u8 *pTmp; /* Temp ptr into data[] */
int gap; /* First byte of gap between cell pointers and cell content */
assert( sqlite3PagerIswriteable(pPage->pDbPage) );
@@ -67838,7 +69868,8 @@ static int allocateSpace(MemPage *pPage, int nByte, int *pIdx){
** then the cell content offset of an empty page wants to be 65536.
** However, that integer is too large to be stored in a 2-byte unsigned
** integer, so a value of 0 is used in its place. */
- top = get2byte(&data[hdr+5]);
+ pTmp = &data[hdr+5];
+ top = get2byte(pTmp);
assert( top<=(int)pPage->pBt->usableSize ); /* by btreeComputeFreeSpace() */
if( gap>top ){
if( top==0 && pPage->pBt->usableSize==65536 ){
@@ -67920,6 +69951,7 @@ static int freeSpace(MemPage *pPage, u16 iStart, u16 iSize){
u16 x; /* Offset to cell content area */
u32 iEnd = iStart + iSize; /* First byte past the iStart buffer */
unsigned char *data = pPage->aData; /* Page content */
+ u8 *pTmp; /* Temporary ptr into data[] */
assert( pPage->pBt!=0 );
assert( sqlite3PagerIswriteable(pPage->pDbPage) );
@@ -67938,7 +69970,7 @@ static int freeSpace(MemPage *pPage, u16 iStart, u16 iSize){
iFreeBlk = 0; /* Shortcut for the case when the freelist is empty */
}else{
while( (iFreeBlk = get2byte(&data[iPtr]))<iStart ){
- if( iFreeBlk<iPtr+4 ){
+ if( iFreeBlk<=iPtr ){
if( iFreeBlk==0 ) break; /* TH3: corrupt082.100 */
return SQLITE_CORRUPT_PAGE(pPage);
}
@@ -67982,7 +70014,8 @@ static int freeSpace(MemPage *pPage, u16 iStart, u16 iSize){
if( nFrag>data[hdr+7] ) return SQLITE_CORRUPT_PAGE(pPage);
data[hdr+7] -= nFrag;
}
- x = get2byte(&data[hdr+5]);
+ pTmp = &data[hdr+5];
+ x = get2byte(pTmp);
if( iStart<=x ){
/* The new freeblock is at the beginning of the cell content area,
** so just extend the cell content area rather than create another
@@ -68026,7 +70059,6 @@ static int decodeFlags(MemPage *pPage, int flagByte){
pPage->leaf = (u8)(flagByte>>3); assert( PTF_LEAF == 1<<3 );
flagByte &= ~PTF_LEAF;
pPage->childPtrSize = 4-4*pPage->leaf;
- pPage->xCellSize = cellSizePtr;
pBt = pPage->pBt;
if( flagByte==(PTF_LEAFDATA | PTF_INTKEY) ){
/* EVIDENCE-OF: R-07291-35328 A value of 5 (0x05) means the page is an
@@ -68038,6 +70070,7 @@ static int decodeFlags(MemPage *pPage, int flagByte){
pPage->intKey = 1;
if( pPage->leaf ){
pPage->intKeyLeaf = 1;
+ pPage->xCellSize = cellSizePtrTableLeaf;
pPage->xParseCell = btreeParseCellPtr;
}else{
pPage->intKeyLeaf = 0;
@@ -68055,12 +70088,17 @@ static int decodeFlags(MemPage *pPage, int flagByte){
assert( (PTF_ZERODATA|PTF_LEAF)==10 );
pPage->intKey = 0;
pPage->intKeyLeaf = 0;
+ pPage->xCellSize = cellSizePtr;
pPage->xParseCell = btreeParseCellPtrIndex;
pPage->maxLocal = pBt->maxLocal;
pPage->minLocal = pBt->minLocal;
}else{
/* EVIDENCE-OF: R-47608-56469 Any other value for the b-tree page type is
** an error. */
+ pPage->intKey = 0;
+ pPage->intKeyLeaf = 0;
+ pPage->xCellSize = cellSizePtr;
+ pPage->xParseCell = btreeParseCellPtrIndex;
return SQLITE_CORRUPT_PAGE(pPage);
}
pPage->max1bytePayload = pBt->max1bytePayload;
@@ -68414,7 +70452,7 @@ getAndInitPage_error1:
pCur->pPage = pCur->apPage[pCur->iPage];
}
testcase( pgno==0 );
- assert( pgno!=0 || rc==SQLITE_CORRUPT );
+ assert( pgno!=0 || rc!=SQLITE_OK );
return rc;
}
@@ -69850,6 +71888,9 @@ static int modifyPagePointer(MemPage *pPage, Pgno iFrom, Pgno iTo, u8 eType){
}
}
}else{
+ if( pCell+4 > pPage->aData+pPage->pBt->usableSize ){
+ return SQLITE_CORRUPT_PAGE(pPage);
+ }
if( get4byte(pCell)==iFrom ){
put4byte(pCell, iTo);
break;
@@ -70036,12 +72077,17 @@ static int incrVacuumStep(BtShared *pBt, Pgno nFin, Pgno iLastPg, int bCommit){
}
do {
MemPage *pFreePg;
+ Pgno dbSize = btreePagecount(pBt);
rc = allocateBtreePage(pBt, &pFreePg, &iFreePg, iNear, eMode);
if( rc!=SQLITE_OK ){
releasePage(pLastPg);
return rc;
}
releasePage(pFreePg);
+ if( iFreePg>dbSize ){
+ releasePage(pLastPg);
+ return SQLITE_CORRUPT_BKPT;
+ }
}while( bCommit && iFreePg>nFin );
assert( iFreePg<iLastPg );
@@ -71351,8 +73397,6 @@ SQLITE_PRIVATE const void *sqlite3BtreePayloadFetch(BtCursor *pCur, u32 *pAmt){
** vice-versa).
*/
static int moveToChild(BtCursor *pCur, u32 newPgno){
- BtShared *pBt = pCur->pBt;
-
assert( cursorOwnsBtShared(pCur) );
assert( pCur->eState==CURSOR_VALID );
assert( pCur->iPage<BTCURSOR_MAX_DEPTH );
@@ -71366,7 +73410,8 @@ static int moveToChild(BtCursor *pCur, u32 newPgno){
pCur->apPage[pCur->iPage] = pCur->pPage;
pCur->ix = 0;
pCur->iPage++;
- return getAndInitPage(pBt, newPgno, &pCur->pPage, pCur, pCur->curPagerFlags);
+ return getAndInitPage(pCur->pBt, newPgno, &pCur->pPage, pCur,
+ pCur->curPagerFlags);
}
#ifdef SQLITE_DEBUG
@@ -71472,7 +73517,7 @@ static int moveToRoot(BtCursor *pCur){
}
sqlite3BtreeClearCursor(pCur);
}
- rc = getAndInitPage(pCur->pBtree->pBt, pCur->pgnoRoot, &pCur->pPage,
+ rc = getAndInitPage(pCur->pBt, pCur->pgnoRoot, &pCur->pPage,
0, pCur->curPagerFlags);
if( rc!=SQLITE_OK ){
pCur->eState = CURSOR_INVALID;
@@ -71803,6 +73848,69 @@ moveto_table_finish:
return rc;
}
+/*
+** Compare the "idx"-th cell on the page the cursor pCur is currently
+** pointing to to pIdxKey using xRecordCompare. Return negative or
+** zero if the cell is less than or equal pIdxKey. Return positive
+** if unknown.
+**
+** Return value negative: Cell at pCur[idx] less than pIdxKey
+**
+** Return value is zero: Cell at pCur[idx] equals pIdxKey
+**
+** Return value positive: Nothing is known about the relationship
+** of the cell at pCur[idx] and pIdxKey.
+**
+** This routine is part of an optimization. It is always safe to return
+** a positive value as that will cause the optimization to be skipped.
+*/
+static int indexCellCompare(
+ BtCursor *pCur,
+ int idx,
+ UnpackedRecord *pIdxKey,
+ RecordCompare xRecordCompare
+){
+ MemPage *pPage = pCur->pPage;
+ int c;
+ int nCell; /* Size of the pCell cell in bytes */
+ u8 *pCell = findCellPastPtr(pPage, idx);
+
+ nCell = pCell[0];
+ if( nCell<=pPage->max1bytePayload ){
+ /* This branch runs if the record-size field of the cell is a
+ ** single byte varint and the record fits entirely on the main
+ ** b-tree page. */
+ testcase( pCell+nCell+1==pPage->aDataEnd );
+ c = xRecordCompare(nCell, (void*)&pCell[1], pIdxKey);
+ }else if( !(pCell[1] & 0x80)
+ && (nCell = ((nCell&0x7f)<<7) + pCell[1])<=pPage->maxLocal
+ ){
+ /* The record-size field is a 2 byte varint and the record
+ ** fits entirely on the main b-tree page. */
+ testcase( pCell+nCell+2==pPage->aDataEnd );
+ c = xRecordCompare(nCell, (void*)&pCell[2], pIdxKey);
+ }else{
+ /* If the record extends into overflow pages, do not attempt
+ ** the optimization. */
+ c = 99;
+ }
+ return c;
+}
+
+/*
+** Return true (non-zero) if pCur is current pointing to the last
+** page of a table.
+*/
+static int cursorOnLastPage(BtCursor *pCur){
+ int i;
+ assert( pCur->eState==CURSOR_VALID );
+ for(i=0; i<pCur->iPage; i++){
+ MemPage *pPage = pCur->apPage[i];
+ if( pCur->aiIdx[i]<pPage->nCell ) return 0;
+ }
+ return 1;
+}
+
/* Move the cursor so that it points to an entry in an index table
** near the key pIdxKey. Return a success code.
**
@@ -71853,6 +73961,43 @@ SQLITE_PRIVATE int sqlite3BtreeIndexMoveto(
|| pIdxKey->default_rc==-1
);
+
+ /* Check to see if we can skip a lot of work. Two cases:
+ **
+ ** (1) If the cursor is already pointing to the very last cell
+ ** in the table and the pIdxKey search key is greater than or
+ ** equal to that last cell, then no movement is required.
+ **
+ ** (2) If the cursor is on the last page of the table and the first
+ ** cell on that last page is less than or equal to the pIdxKey
+ ** search key, then we can start the search on the current page
+ ** without needing to go back to root.
+ */
+ if( pCur->eState==CURSOR_VALID
+ && pCur->pPage->leaf
+ && cursorOnLastPage(pCur)
+ ){
+ int c;
+ if( pCur->ix==pCur->pPage->nCell-1
+ && (c = indexCellCompare(pCur, pCur->ix, pIdxKey, xRecordCompare))<=0
+ && pIdxKey->errCode==SQLITE_OK
+ ){
+ *pRes = c;
+ return SQLITE_OK; /* Cursor already pointing at the correct spot */
+ }
+ if( pCur->iPage>0
+ && indexCellCompare(pCur, 0, pIdxKey, xRecordCompare)<=0
+ && pIdxKey->errCode==SQLITE_OK
+ ){
+ pCur->curFlags &= ~BTCF_ValidOvfl;
+ if( !pCur->pPage->isInit ){
+ return SQLITE_CORRUPT_BKPT;
+ }
+ goto bypass_moveto_root; /* Start search on the current page */
+ }
+ pIdxKey->errCode = SQLITE_OK;
+ }
+
rc = moveToRoot(pCur);
if( rc ){
if( rc==SQLITE_EMPTY ){
@@ -71862,12 +74007,14 @@ SQLITE_PRIVATE int sqlite3BtreeIndexMoveto(
}
return rc;
}
+
+bypass_moveto_root:
assert( pCur->pPage );
assert( pCur->pPage->isInit );
assert( pCur->eState==CURSOR_VALID );
assert( pCur->pPage->nCell > 0 );
- assert( pCur->iPage==0 || pCur->apPage[0]->intKey==pCur->curIntKey );
- assert( pCur->curIntKey || pIdxKey );
+ assert( pCur->curIntKey==0 );
+ assert( pIdxKey!=0 );
for(;;){
int lwr, upr, idx, c;
Pgno chldPg;
@@ -71881,7 +74028,7 @@ SQLITE_PRIVATE int sqlite3BtreeIndexMoveto(
** be the right kind (index or table) of b-tree page. Otherwise
** a moveToChild() or moveToRoot() call would have detected corruption. */
assert( pPage->nCell>0 );
- assert( pPage->intKey==(pIdxKey==0) );
+ assert( pPage->intKey==0 );
lwr = 0;
upr = pPage->nCell-1;
idx = upr>>1; /* idx = (lwr+upr)/2; */
@@ -72078,14 +74225,7 @@ static SQLITE_NOINLINE int btreeNext(BtCursor *pCur){
pPage = pCur->pPage;
idx = ++pCur->ix;
- if( !pPage->isInit || sqlite3FaultSim(412) ){
- /* The only known way for this to happen is for there to be a
- ** recursive SQL function that does a DELETE operation as part of a
- ** SELECT which deletes content out from under an active cursor
- ** in a corrupt database file where the table being DELETE-ed from
- ** has pages in common with the table being queried. See TH3
- ** module cov1/btree78.test testcase 220 (2018-06-08) for an
- ** example. */
+ if( NEVER(!pPage->isInit) || sqlite3FaultSim(412) ){
return SQLITE_CORRUPT_BKPT;
}
@@ -72261,8 +74401,8 @@ static int allocateBtreePage(
assert( eMode==BTALLOC_ANY || (nearby>0 && IfNotOmitAV(pBt->autoVacuum)) );
pPage1 = pBt->pPage1;
mxPage = btreePagecount(pBt);
- /* EVIDENCE-OF: R-05119-02637 The 4-byte big-endian integer at offset 36
- ** stores stores the total number of pages on the freelist. */
+ /* EVIDENCE-OF: R-21003-45125 The 4-byte big-endian integer at offset 36
+ ** stores the total number of pages on the freelist. */
n = get4byte(&pPage1->aData[36]);
testcase( n==mxPage-1 );
if( n>=mxPage ){
@@ -73011,12 +75151,6 @@ static void dropCell(MemPage *pPage, int idx, int sz, int *pRC){
assert( pPage->pBt->usableSize > (u32)(ptr-data) );
pc = get2byte(ptr);
hdr = pPage->hdrOffset;
-#if 0 /* Not required. Omit for efficiency */
- if( pc<hdr+pPage->nCell*2 ){
- *pRC = SQLITE_CORRUPT_BKPT;
- return;
- }
-#endif
testcase( pc==(u32)get2byte(&data[hdr+5]) );
testcase( pc+sz==pPage->pBt->usableSize );
if( pc+sz > pPage->pBt->usableSize ){
@@ -73900,8 +76034,6 @@ static int balance_nonroot(
Pgno pgno; /* Temp var to store a page number in */
u8 abDone[NB+2]; /* True after i'th new page is populated */
Pgno aPgno[NB+2]; /* Page numbers of new pages before shuffling */
- Pgno aPgOrder[NB+2]; /* Copy of aPgno[] used for sorting pages */
- u16 aPgFlags[NB+2]; /* flags field of new pages before shuffling */
CellArray b; /* Parsed information on cells being balanced */
memset(abDone, 0, sizeof(abDone));
@@ -74325,42 +76457,39 @@ static int balance_nonroot(
** of the table is closer to a linear scan through the file. That in turn
** helps the operating system to deliver pages from the disk more rapidly.
**
- ** An O(n^2) insertion sort algorithm is used, but since n is never more
- ** than (NB+2) (a small constant), that should not be a problem.
+ ** An O(N*N) sort algorithm is used, but since N is never more than NB+2
+ ** (5), that is not a performance concern.
**
** When NB==3, this one optimization makes the database about 25% faster
** for large insertions and deletions.
*/
for(i=0; i<nNew; i++){
- aPgOrder[i] = aPgno[i] = apNew[i]->pgno;
- aPgFlags[i] = apNew[i]->pDbPage->flags;
- for(j=0; j<i; j++){
- if( NEVER(aPgno[j]==aPgno[i]) ){
- /* This branch is taken if the set of sibling pages somehow contains
- ** duplicate entries. This can happen if the database is corrupt.
- ** It would be simpler to detect this as part of the loop below, but
- ** we do the detection here in order to avoid populating the pager
- ** cache with two separate objects associated with the same
- ** page number. */
- assert( CORRUPT_DB );
- rc = SQLITE_CORRUPT_BKPT;
- goto balance_cleanup;
- }
- }
+ aPgno[i] = apNew[i]->pgno;
+ assert( apNew[i]->pDbPage->flags & PGHDR_WRITEABLE );
+ assert( apNew[i]->pDbPage->flags & PGHDR_DIRTY );
}
- for(i=0; i<nNew; i++){
- int iBest = 0; /* aPgno[] index of page number to use */
- for(j=1; j<nNew; j++){
- if( aPgOrder[j]<aPgOrder[iBest] ) iBest = j;
+ for(i=0; i<nNew-1; i++){
+ int iB = i;
+ for(j=i+1; j<nNew; j++){
+ if( apNew[j]->pgno < apNew[iB]->pgno ) iB = j;
}
- pgno = aPgOrder[iBest];
- aPgOrder[iBest] = 0xffffffff;
- if( iBest!=i ){
- if( iBest>i ){
- sqlite3PagerRekey(apNew[iBest]->pDbPage, pBt->nPage+iBest+1, 0);
- }
- sqlite3PagerRekey(apNew[i]->pDbPage, pgno, aPgFlags[iBest]);
- apNew[i]->pgno = pgno;
+
+ /* If apNew[i] has a page number that is bigger than any of the
+ ** subsequence apNew[i] entries, then swap apNew[i] with the subsequent
+ ** entry that has the smallest page number (which we know to be
+ ** entry apNew[iB]).
+ */
+ if( iB!=i ){
+ Pgno pgnoA = apNew[i]->pgno;
+ Pgno pgnoB = apNew[iB]->pgno;
+ Pgno pgnoTemp = (PENDING_BYTE/pBt->pageSize)+1;
+ u16 fgA = apNew[i]->pDbPage->flags;
+ u16 fgB = apNew[iB]->pDbPage->flags;
+ sqlite3PagerRekey(apNew[i]->pDbPage, pgnoTemp, fgB);
+ sqlite3PagerRekey(apNew[iB]->pDbPage, pgnoA, fgA);
+ sqlite3PagerRekey(apNew[i]->pDbPage, pgnoB, fgB);
+ apNew[i]->pgno = pgnoB;
+ apNew[iB]->pgno = pgnoA;
}
}
@@ -74748,7 +76877,6 @@ static int anotherValidCursor(BtCursor *pCur){
*/
static int balance(BtCursor *pCur){
int rc = SQLITE_OK;
- const int nMin = pCur->pBt->usableSize * 2 / 3;
u8 aBalanceQuickSpace[13];
u8 *pFree = 0;
@@ -74760,7 +76888,11 @@ static int balance(BtCursor *pCur){
MemPage *pPage = pCur->pPage;
if( NEVER(pPage->nFree<0) && btreeComputeFreeSpace(pPage) ) break;
- if( pPage->nOverflow==0 && pPage->nFree<=nMin ){
+ if( pPage->nOverflow==0 && pPage->nFree*3<=(int)pCur->pBt->usableSize*2 ){
+ /* No rebalance required as long as:
+ ** (1) There are no overflow cells
+ ** (2) The amount of free space on the page is less than 2/3rds of
+ ** the total usable space on the page. */
break;
}else if( (iPage = pCur->iPage)==0 ){
if( pPage->nOverflow && (rc = anotherValidCursor(pCur))==SQLITE_OK ){
@@ -74783,6 +76915,11 @@ static int balance(BtCursor *pCur){
}else{
break;
}
+ }else if( sqlite3PagerPageRefcount(pPage->pDbPage)>1 ){
+ /* The page being written is not a root page, and there is currently
+ ** more than one reference to it. This only happens if the page is one
+ ** of its own ancestor pages. Corruption. */
+ rc = SQLITE_CORRUPT_BKPT;
}else{
MemPage * const pParent = pCur->apPage[iPage-1];
int const iIdx = pCur->aiIdx[iPage-1];
@@ -74980,7 +77117,7 @@ static int btreeOverwriteCell(BtCursor *pCur, const BtreePayload *pX){
** pX.pData,nData,nZero fields must be zero.
**
** If the seekResult parameter is non-zero, then a successful call to
-** MovetoUnpacked() to seek cursor pCur to (pKey,nKey) has already
+** sqlite3BtreeIndexMoveto() to seek cursor pCur to (pKey,nKey) has already
** been performed. In other words, if seekResult!=0 then the cursor
** is currently pointing to a cell that will be adjacent to the cell
** to be inserted. If seekResult<0 then pCur points to a cell that is
@@ -74998,7 +77135,7 @@ SQLITE_PRIVATE int sqlite3BtreeInsert(
BtCursor *pCur, /* Insert data into the table of this cursor */
const BtreePayload *pX, /* Content of the row to be inserted */
int flags, /* True if this is likely an append */
- int seekResult /* Result of prior MovetoUnpacked() call */
+ int seekResult /* Result of prior IndexMoveto() call */
){
int rc;
int loc = seekResult; /* -1: before desired location +1: after */
@@ -75037,7 +77174,12 @@ SQLITE_PRIVATE int sqlite3BtreeInsert(
}
}
+ /* Ensure that the cursor is not in the CURSOR_FAULT state and that it
+ ** points to a valid cell.
+ */
if( pCur->eState>=CURSOR_REQUIRESEEK ){
+ testcase( pCur->eState==CURSOR_REQUIRESEEK );
+ testcase( pCur->eState==CURSOR_FAULT );
rc = moveToRoot(pCur);
if( rc && rc!=SQLITE_EMPTY ) return rc;
}
@@ -75149,7 +77291,8 @@ SQLITE_PRIVATE int sqlite3BtreeInsert(
assert( pPage->intKey || pX->nKey>=0 || (flags & BTREE_PREFORMAT) );
assert( pPage->leaf || !pPage->intKey );
if( pPage->nFree<0 ){
- if( pCur->eState>CURSOR_INVALID ){
+ if( NEVER(pCur->eState>CURSOR_INVALID) ){
+ /* ^^^^^--- due to the moveToRoot() call above */
rc = SQLITE_CORRUPT_BKPT;
}else{
rc = btreeComputeFreeSpace(pPage);
@@ -75160,7 +77303,7 @@ SQLITE_PRIVATE int sqlite3BtreeInsert(
TRACE(("INSERT: table=%d nkey=%lld ndata=%d page=%d %s\n",
pCur->pgnoRoot, pX->nKey, pX->nData, pPage->pgno,
loc==0 ? "overwrite" : "new entry"));
- assert( pPage->isInit );
+ assert( pPage->isInit || CORRUPT_DB );
newCell = pBt->pTmpSpace;
assert( newCell!=0 );
if( flags & BTREE_PREFORMAT ){
@@ -75311,7 +77454,11 @@ SQLITE_PRIVATE int sqlite3BtreeTransferRow(BtCursor *pDest, BtCursor *pSrc, i64
u32 nRem; /* Bytes of data still to copy */
getCellInfo(pSrc);
- aOut += putVarint32(aOut, pSrc->info.nPayload);
+ if( pSrc->info.nPayload<0x80 ){
+ *(aOut++) = pSrc->info.nPayload;
+ }else{
+ aOut += sqlite3PutVarint(aOut, pSrc->info.nPayload);
+ }
if( pDest->pKeyInfo==0 ) aOut += putVarint(aOut, iKey);
nIn = pSrc->info.nLocal;
aIn = pSrc->info.pPayload;
@@ -75471,7 +77618,8 @@ SQLITE_PRIVATE int sqlite3BtreeDelete(BtCursor *pCur, u8 flags){
bPreserve = (flags & BTREE_SAVEPOSITION)!=0;
if( bPreserve ){
if( !pPage->leaf
- || (pPage->nFree+cellSizePtr(pPage,pCell)+2)>(int)(pBt->usableSize*2/3)
+ || (pPage->nFree+pPage->xCellSize(pPage,pCell)+2) >
+ (int)(pBt->usableSize*2/3)
|| pPage->nCell==1 /* See dbfuzz001.test for a test case */
){
/* A b-tree rebalance will be required after deleting this entry.
@@ -75567,7 +77715,15 @@ SQLITE_PRIVATE int sqlite3BtreeDelete(BtCursor *pCur, u8 flags){
** been corrected, so be it. Otherwise, after balancing the leaf node,
** walk the cursor up the tree to the internal node and balance it as
** well. */
- rc = balance(pCur);
+ assert( pCur->pPage->nOverflow==0 );
+ assert( pCur->pPage->nFree>=0 );
+ if( pCur->pPage->nFree*3<=(int)pCur->pBt->usableSize*2 ){
+ /* Optimization: If the free space is less than 2/3rds of the page,
+ ** then balance() will always be a no-op. No need to invoke it. */
+ rc = SQLITE_OK;
+ }else{
+ rc = balance(pCur);
+ }
if( rc==SQLITE_OK && pCur->iPage>iCellDepth ){
releasePageNotNull(pCur->pPage);
pCur->iPage--;
@@ -77062,6 +79218,17 @@ SQLITE_PRIVATE int sqlite3BtreeIsReadonly(Btree *p){
*/
SQLITE_PRIVATE int sqlite3HeaderSizeBtree(void){ return ROUND8(sizeof(MemPage)); }
+/*
+** If no transaction is active and the database is not a temp-db, clear
+** the in-memory pager cache.
+*/
+SQLITE_PRIVATE void sqlite3BtreeClearCache(Btree *p){
+ BtShared *pBt = p->pBt;
+ if( pBt->inTransaction==TRANS_NONE ){
+ sqlite3PagerClearCache(pBt->pPager);
+ }
+}
+
#if !defined(SQLITE_OMIT_SHARED_CACHE)
/*
** Return true if the Btree passed as the only argument is sharable.
@@ -78315,9 +80482,10 @@ SQLITE_PRIVATE int sqlite3VdbeMemFinalize(Mem *pMem, FuncDef *pFunc){
Mem t;
assert( pFunc!=0 );
assert( pMem!=0 );
+ assert( pMem->db!=0 );
assert( pFunc->xFinalize!=0 );
assert( (pMem->flags & MEM_Null)!=0 || pFunc==pMem->u.pDef );
- assert( pMem->db==0 || sqlite3_mutex_held(pMem->db->mutex) );
+ assert( sqlite3_mutex_held(pMem->db->mutex) );
memset(&ctx, 0, sizeof(ctx));
memset(&t, 0, sizeof(t));
t.flags = MEM_Null;
@@ -78325,6 +80493,7 @@ SQLITE_PRIVATE int sqlite3VdbeMemFinalize(Mem *pMem, FuncDef *pFunc){
ctx.pOut = &t;
ctx.pMem = pMem;
ctx.pFunc = pFunc;
+ ctx.enc = ENC(t.db);
pFunc->xFinalize(&ctx); /* IMP: R-24505-23230 */
assert( (pMem->flags & MEM_Dyn)==0 );
if( pMem->szMalloc>0 ) sqlite3DbFreeNN(pMem->db, pMem->zMalloc);
@@ -78346,12 +80515,14 @@ SQLITE_PRIVATE int sqlite3VdbeMemAggValue(Mem *pAccum, Mem *pOut, FuncDef *pFunc
assert( pFunc!=0 );
assert( pFunc->xValue!=0 );
assert( (pAccum->flags & MEM_Null)!=0 || pFunc==pAccum->u.pDef );
- assert( pAccum->db==0 || sqlite3_mutex_held(pAccum->db->mutex) );
+ assert( pAccum->db!=0 );
+ assert( sqlite3_mutex_held(pAccum->db->mutex) );
memset(&ctx, 0, sizeof(ctx));
sqlite3VdbeMemSetNull(pOut);
ctx.pOut = pOut;
ctx.pMem = pAccum;
ctx.pFunc = pFunc;
+ ctx.enc = ENC(pAccum->db);
pFunc->xValue(&ctx);
return ctx.isError;
}
@@ -78417,6 +80588,14 @@ SQLITE_PRIVATE void sqlite3VdbeMemRelease(Mem *p){
}
}
+/* Like sqlite3VdbeMemRelease() but faster for cases where we
+** know in advance that the Mem is not MEM_Dyn or MEM_Agg.
+*/
+SQLITE_PRIVATE void sqlite3VdbeMemReleaseMalloc(Mem *p){
+ assert( !VdbeMemDynamic(p) );
+ if( p->szMalloc ) vdbeMemClear(p);
+}
+
/*
** Convert a 64-bit IEEE double into a 64-bit signed integer.
** If the double is out of range of a 64-bit signed integer then
@@ -78595,6 +80774,16 @@ SQLITE_PRIVATE int sqlite3RealSameAsInt(double r1, sqlite3_int64 i){
&& i >= -2251799813685248LL && i < 2251799813685248LL);
}
+/* Convert a floating point value to its closest integer. Do so in
+** a way that avoids 'outside the range of representable values' warnings
+** from UBSAN.
+*/
+SQLITE_PRIVATE i64 sqlite3RealToI64(double r){
+ if( r<=(double)SMALLEST_INT64 ) return SMALLEST_INT64;
+ if( r>=(double)LARGEST_INT64) return LARGEST_INT64;
+ return (i64)r;
+}
+
/*
** Convert pMem so that it has type MEM_Real or MEM_Int.
** Invalidate any prior representations.
@@ -78616,7 +80805,7 @@ SQLITE_PRIVATE int sqlite3VdbeMemNumerify(Mem *pMem){
assert( pMem->db==0 || sqlite3_mutex_held(pMem->db->mutex) );
rc = sqlite3AtoF(pMem->z, &pMem->u.r, pMem->n, pMem->enc);
if( ((rc==0 || rc==1) && sqlite3Atoi64(pMem->z, &ix, pMem->n, pMem->enc)<=1)
- || sqlite3RealSameAsInt(pMem->u.r, (ix = (i64)pMem->u.r))
+ || sqlite3RealSameAsInt(pMem->u.r, (ix = sqlite3RealToI64(pMem->u.r)))
){
pMem->u.i = ix;
MemSetTypeFlag(pMem, MEM_Int);
@@ -78668,6 +80857,7 @@ SQLITE_PRIVATE int sqlite3VdbeMemCast(Mem *pMem, u8 aff, u8 encoding){
sqlite3ValueApplyAffinity(pMem, SQLITE_AFF_TEXT, encoding);
assert( pMem->flags & MEM_Str || pMem->db->mallocFailed );
pMem->flags &= ~(MEM_Int|MEM_Real|MEM_IntReal|MEM_Blob|MEM_Zero);
+ if( encoding!=SQLITE_UTF8 ) pMem->n &= ~1;
return sqlite3VdbeChangeEncoding(pMem, encoding);
}
}
@@ -78961,6 +81151,13 @@ SQLITE_PRIVATE void sqlite3VdbeMemMove(Mem *pTo, Mem *pFrom){
** stored without allocating memory, then it is. If a memory allocation
** is required to store the string, then value of pMem is unchanged. In
** either case, SQLITE_TOOBIG is returned.
+**
+** The "enc" parameter is the text encoding for the string, or zero
+** to store a blob.
+**
+** If n is negative, then the string consists of all bytes up to but
+** excluding the first zero character. The n parameter must be
+** non-negative for blobs.
*/
SQLITE_PRIVATE int sqlite3VdbeMemSetStr(
Mem *pMem, /* Memory cell to set to string value */
@@ -78971,11 +81168,12 @@ SQLITE_PRIVATE int sqlite3VdbeMemSetStr(
){
i64 nByte = n; /* New value for pMem->n */
int iLimit; /* Maximum allowed string or blob size */
- u16 flags = 0; /* New value for pMem->flags */
+ u16 flags; /* New value for pMem->flags */
assert( pMem!=0 );
assert( pMem->db==0 || sqlite3_mutex_held(pMem->db->mutex) );
assert( !sqlite3VdbeMemIsRowSet(pMem) );
+ assert( enc!=0 || n>=0 );
/* If z is a NULL pointer, set pMem to contain an SQL NULL. */
if( !z ){
@@ -78988,7 +81186,6 @@ SQLITE_PRIVATE int sqlite3VdbeMemSetStr(
}else{
iLimit = SQLITE_MAX_LENGTH;
}
- flags = (enc==0?MEM_Blob:MEM_Str);
if( nByte<0 ){
assert( enc!=0 );
if( enc==SQLITE_UTF8 ){
@@ -78996,7 +81193,23 @@ SQLITE_PRIVATE int sqlite3VdbeMemSetStr(
}else{
for(nByte=0; nByte<=iLimit && (z[nByte] | z[nByte+1]); nByte+=2){}
}
- flags |= MEM_Term;
+ flags= MEM_Str|MEM_Term;
+ }else if( enc==0 ){
+ flags = MEM_Blob;
+ enc = SQLITE_UTF8;
+ }else{
+ flags = MEM_Str;
+ }
+ if( nByte>iLimit ){
+ if( xDel && xDel!=SQLITE_TRANSIENT ){
+ if( xDel==SQLITE_DYNAMIC ){
+ sqlite3DbFree(pMem->db, (void*)z);
+ }else{
+ xDel((void*)z);
+ }
+ }
+ sqlite3VdbeMemSetNull(pMem);
+ return sqlite3ErrorToParser(pMem->db, SQLITE_TOOBIG);
}
/* The following block sets the new values of Mem.z and Mem.xDel. It
@@ -79008,9 +81221,6 @@ SQLITE_PRIVATE int sqlite3VdbeMemSetStr(
if( flags&MEM_Term ){
nAlloc += (enc==SQLITE_UTF8?1:2);
}
- if( nByte>iLimit ){
- return sqlite3ErrorToParser(pMem->db, SQLITE_TOOBIG);
- }
testcase( nAlloc==0 );
testcase( nAlloc==31 );
testcase( nAlloc==32 );
@@ -79032,16 +81242,7 @@ SQLITE_PRIVATE int sqlite3VdbeMemSetStr(
pMem->n = (int)(nByte & 0x7fffffff);
pMem->flags = flags;
- if( enc ){
- pMem->enc = enc;
-#ifdef SQLITE_ENABLE_SESSION
- }else if( pMem->db==0 ){
- pMem->enc = SQLITE_UTF8;
-#endif
- }else{
- assert( pMem->db!=0 );
- pMem->enc = ENC(pMem->db);
- }
+ pMem->enc = enc;
#ifndef SQLITE_OMIT_UTF16
if( enc>SQLITE_UTF8 && sqlite3VdbeMemHandleBom(pMem) ){
@@ -79049,9 +81250,6 @@ SQLITE_PRIVATE int sqlite3VdbeMemSetStr(
}
#endif
- if( nByte>iLimit ){
- return sqlite3ErrorToParser(pMem->db, SQLITE_TOOBIG);
- }
return SQLITE_OK;
}
@@ -79329,10 +81527,12 @@ static int valueFromFunction(
goto value_from_function_out;
}
- assert( pCtx->pParse->rc==SQLITE_OK );
+ testcase( pCtx->pParse->rc==SQLITE_ERROR );
+ testcase( pCtx->pParse->rc==SQLITE_OK );
memset(&ctx, 0, sizeof(ctx));
ctx.pOut = pVal;
ctx.pFunc = pFunc;
+ ctx.enc = ENC(db);
pFunc->xSFunc(&ctx, nVal, apVal);
if( ctx.isError ){
rc = ctx.isError;
@@ -79408,8 +81608,8 @@ static int valueFromExpr(
rc = valueFromExpr(db, pExpr->pLeft, enc, aff, ppVal, pCtx);
testcase( rc!=SQLITE_OK );
if( *ppVal ){
- sqlite3VdbeMemCast(*ppVal, aff, SQLITE_UTF8);
- sqlite3ValueApplyAffinity(*ppVal, affinity, SQLITE_UTF8);
+ sqlite3VdbeMemCast(*ppVal, aff, enc);
+ sqlite3ValueApplyAffinity(*ppVal, affinity, enc);
}
return rc;
}
@@ -79793,6 +81993,9 @@ SQLITE_PRIVATE int sqlite3ValueBytes(sqlite3_value *pVal, u8 enc){
if( (p->flags & MEM_Str)!=0 && pVal->enc==enc ){
return p->n;
}
+ if( (p->flags & MEM_Str)!=0 && enc!=SQLITE_UTF8 && pVal->enc!=SQLITE_UTF8 ){
+ return p->n;
+ }
if( (p->flags & MEM_Blob)!=0 ){
if( p->flags & MEM_Zero ){
return p->n + p->u.nZero;
@@ -79838,12 +82041,12 @@ SQLITE_PRIVATE Vdbe *sqlite3VdbeCreate(Parse *pParse){
memset(&p->aOp, 0, sizeof(Vdbe)-offsetof(Vdbe,aOp));
p->db = db;
if( db->pVdbe ){
- db->pVdbe->pPrev = p;
+ db->pVdbe->ppVPrev = &p->pVNext;
}
- p->pNext = db->pVdbe;
- p->pPrev = 0;
+ p->pVNext = db->pVdbe;
+ p->ppVPrev = &db->pVdbe;
db->pVdbe = p;
- p->iVdbeMagic = VDBE_MAGIC_INIT;
+ assert( p->eVdbeState==VDBE_INIT_STATE );
p->pParse = pParse;
pParse->pVdbe = p;
assert( pParse->aLabel==0 );
@@ -79923,21 +82126,28 @@ SQLITE_PRIVATE int sqlite3VdbeUsesDoubleQuotedString(
#endif
/*
-** Swap all content between two VDBE structures.
+** Swap byte-code between two VDBE structures.
+**
+** This happens after pB was previously run and returned
+** SQLITE_SCHEMA. The statement was then reprepared in pA.
+** This routine transfers the new bytecode in pA over to pB
+** so that pB can be run again. The old pB byte code is
+** moved back to pA so that it will be cleaned up when pA is
+** finalized.
*/
SQLITE_PRIVATE void sqlite3VdbeSwap(Vdbe *pA, Vdbe *pB){
- Vdbe tmp, *pTmp;
+ Vdbe tmp, *pTmp, **ppTmp;
char *zTmp;
assert( pA->db==pB->db );
tmp = *pA;
*pA = *pB;
*pB = tmp;
- pTmp = pA->pNext;
- pA->pNext = pB->pNext;
- pB->pNext = pTmp;
- pTmp = pA->pPrev;
- pA->pPrev = pB->pPrev;
- pB->pPrev = pTmp;
+ pTmp = pA->pVNext;
+ pA->pVNext = pB->pVNext;
+ pB->pVNext = pTmp;
+ ppTmp = pA->ppVPrev;
+ pA->ppVPrev = pB->ppVPrev;
+ pB->ppVPrev = ppTmp;
zTmp = pA->zSql;
pA->zSql = pB->zSql;
pB->zSql = zTmp;
@@ -79988,7 +82198,7 @@ static int growOpArray(Vdbe *v, int nOp){
return SQLITE_NOMEM;
}
- assert( nOp<=(1024/sizeof(Op)) );
+ assert( nOp<=(int)(1024/sizeof(Op)) );
assert( nNew>=(v->nOpAlloc+nOp) );
pNew = sqlite3DbRealloc(p->db, v->aOp, nNew*sizeof(Op));
if( pNew ){
@@ -80044,7 +82254,7 @@ SQLITE_PRIVATE int sqlite3VdbeAddOp3(Vdbe *p, int op, int p1, int p2, int p3){
VdbeOp *pOp;
i = p->nOp;
- assert( p->iVdbeMagic==VDBE_MAGIC_INIT );
+ assert( p->eVdbeState==VDBE_INIT_STATE );
assert( op>=0 && op<0xff );
if( p->nOpAlloc<=i ){
return growOp3(p, op, p1, p2, p3);
@@ -80189,6 +82399,7 @@ SQLITE_PRIVATE int sqlite3VdbeAddFunctionCall(
addr = sqlite3VdbeAddOp4(v, eCallCtx ? OP_PureFunc : OP_Function,
p1, p2, p3, (char*)pCtx, P4_FUNCCTX);
sqlite3VdbeChangeP5(v, eCallCtx & NC_SelfRef);
+ sqlite3MayAbort(pParse);
return addr;
}
@@ -80257,7 +82468,7 @@ SQLITE_PRIVATE void sqlite3VdbeExplain(Parse *pParse, u8 bPush, const char *zFmt
iThis = v->nOp;
sqlite3VdbeAddOp4(v, OP_Explain, iThis, pParse->addrExplain, 0,
zMsg, P4_DYNAMIC);
- sqlite3ExplainBreakpoint(bPush?"PUSH":"", sqlite3VdbeGetOp(v,-1)->p4.z);
+ sqlite3ExplainBreakpoint(bPush?"PUSH":"", sqlite3VdbeGetLastOp(v)->p4.z);
if( bPush){
pParse->addrExplain = iThis;
}
@@ -80376,7 +82587,7 @@ static SQLITE_NOINLINE void resizeResolveLabel(Parse *p, Vdbe *v, int j){
SQLITE_PRIVATE void sqlite3VdbeResolveLabel(Vdbe *v, int x){
Parse *p = v->pParse;
int j = ADDR(x);
- assert( v->iVdbeMagic==VDBE_MAGIC_INIT );
+ assert( v->eVdbeState==VDBE_INIT_STATE );
assert( j<-p->nLabel );
assert( j>=0 );
#ifdef SQLITE_DEBUG
@@ -80396,14 +82607,20 @@ SQLITE_PRIVATE void sqlite3VdbeResolveLabel(Vdbe *v, int x){
** Mark the VDBE as one that can only be run one time.
*/
SQLITE_PRIVATE void sqlite3VdbeRunOnlyOnce(Vdbe *p){
- p->runOnlyOnce = 1;
+ sqlite3VdbeAddOp2(p, OP_Expire, 1, 1);
}
/*
-** Mark the VDBE as one that can only be run multiple times.
+** Mark the VDBE as one that can be run multiple times.
*/
SQLITE_PRIVATE void sqlite3VdbeReusable(Vdbe *p){
- p->runOnlyOnce = 0;
+ int i;
+ for(i=1; ALWAYS(i<p->nOp); i++){
+ if( ALWAYS(p->aOp[i].opcode==OP_Expire) ){
+ p->aOp[1].opcode = OP_Noop;
+ break;
+ }
+ }
}
#ifdef SQLITE_DEBUG /* sqlite3AssertMayAbort() logic */
@@ -80507,6 +82724,8 @@ SQLITE_PRIVATE int sqlite3VdbeAssertMayAbort(Vdbe *v, int mayAbort){
int hasInitCoroutine = 0;
Op *pOp;
VdbeOpIter sIter;
+
+ if( v==0 ) return 0;
memset(&sIter, 0, sizeof(sIter));
sIter.v = v;
@@ -80516,6 +82735,7 @@ SQLITE_PRIVATE int sqlite3VdbeAssertMayAbort(Vdbe *v, int mayAbort){
|| opcode==OP_VDestroy
|| opcode==OP_VCreate
|| opcode==OP_ParseSchema
+ || opcode==OP_Function || opcode==OP_PureFunc
|| ((opcode==OP_Halt || opcode==OP_HaltIfNull)
&& ((pOp->p1)!=SQLITE_OK && pOp->p2==OE_Abort))
){
@@ -80590,7 +82810,7 @@ SQLITE_PRIVATE void sqlite3VdbeAssertAbortable(Vdbe *p){
** (3) Update the Vdbe.readOnly and Vdbe.bIsReader flags to accurately
** indicate what the prepared statement actually does.
**
-** (4) Initialize the p4.xAdvance pointer on opcodes that use it.
+** (4) (discontinued)
**
** (5) Reclaim the memory allocated for storing labels.
**
@@ -80606,8 +82826,8 @@ static void resolveP2Values(Vdbe *p, int *pMaxFuncArgs){
p->readOnly = 1;
p->bIsReader = 0;
pOp = &p->aOp[p->nOp-1];
- while(1){
-
+ assert( p->aOp[0].opcode==OP_Init );
+ while( 1 /* Loop termates when it reaches the OP_Init opcode */ ){
/* Only JUMP opcodes and the short list of special opcodes in the switch
** below need to be considered. The mkopcodeh.tcl generator script groups
** all these opcodes together near the front of the opcode list. Skip
@@ -80636,24 +82856,9 @@ static void resolveP2Values(Vdbe *p, int *pMaxFuncArgs){
p->bIsReader = 1;
break;
}
- case OP_Next:
- case OP_SorterNext: {
- pOp->p4.xAdvance = sqlite3BtreeNext;
- pOp->p4type = P4_ADVANCE;
- /* The code generator never codes any of these opcodes as a jump
- ** to a label. They are always coded as a jump backwards to a
- ** known address */
+ case OP_Init: {
assert( pOp->p2>=0 );
- break;
- }
- case OP_Prev: {
- pOp->p4.xAdvance = sqlite3BtreePrevious;
- pOp->p4type = P4_ADVANCE;
- /* The code generator never codes any of these opcodes as a jump
- ** to a label. They are always coded as a jump backwards to a
- ** known address */
- assert( pOp->p2>=0 );
- break;
+ goto resolve_p2_values_loop_exit;
}
#ifndef SQLITE_OMIT_VIRTUALTABLE
case OP_VUpdate: {
@@ -80687,21 +82892,108 @@ static void resolveP2Values(Vdbe *p, int *pMaxFuncArgs){
** have non-negative values for P2. */
assert( (sqlite3OpcodeProperty[pOp->opcode]&OPFLG_JUMP)==0 || pOp->p2>=0);
}
- if( pOp==p->aOp ) break;
+ assert( pOp>p->aOp );
pOp--;
}
- sqlite3DbFree(p->db, pParse->aLabel);
- pParse->aLabel = 0;
+resolve_p2_values_loop_exit:
+ if( aLabel ){
+ sqlite3DbNNFreeNN(p->db, pParse->aLabel);
+ pParse->aLabel = 0;
+ }
pParse->nLabel = 0;
*pMaxFuncArgs = nMaxArgs;
assert( p->bIsReader!=0 || DbMaskAllZero(p->btreeMask) );
}
+#ifdef SQLITE_DEBUG
+/*
+** Check to see if a subroutine contains a jump to a location outside of
+** the subroutine. If a jump outside the subroutine is detected, add code
+** that will cause the program to halt with an error message.
+**
+** The subroutine consists of opcodes between iFirst and iLast. Jumps to
+** locations within the subroutine are acceptable. iRetReg is a register
+** that contains the return address. Jumps to outside the range of iFirst
+** through iLast are also acceptable as long as the jump destination is
+** an OP_Return to iReturnAddr.
+**
+** A jump to an unresolved label means that the jump destination will be
+** beyond the current address. That is normally a jump to an early
+** termination and is consider acceptable.
+**
+** This routine only runs during debug builds. The purpose is (of course)
+** to detect invalid escapes out of a subroutine. The OP_Halt opcode
+** is generated rather than an assert() or other error, so that ".eqp full"
+** will still work to show the original bytecode, to aid in debugging.
+*/
+SQLITE_PRIVATE void sqlite3VdbeNoJumpsOutsideSubrtn(
+ Vdbe *v, /* The byte-code program under construction */
+ int iFirst, /* First opcode of the subroutine */
+ int iLast, /* Last opcode of the subroutine */
+ int iRetReg /* Subroutine return address register */
+){
+ VdbeOp *pOp;
+ Parse *pParse;
+ int i;
+ sqlite3_str *pErr = 0;
+ assert( v!=0 );
+ pParse = v->pParse;
+ assert( pParse!=0 );
+ if( pParse->nErr ) return;
+ assert( iLast>=iFirst );
+ assert( iLast<v->nOp );
+ pOp = &v->aOp[iFirst];
+ for(i=iFirst; i<=iLast; i++, pOp++){
+ if( (sqlite3OpcodeProperty[pOp->opcode] & OPFLG_JUMP)!=0 ){
+ int iDest = pOp->p2; /* Jump destination */
+ if( iDest==0 ) continue;
+ if( pOp->opcode==OP_Gosub ) continue;
+ if( iDest<0 ){
+ int j = ADDR(iDest);
+ assert( j>=0 );
+ if( j>=-pParse->nLabel || pParse->aLabel[j]<0 ){
+ continue;
+ }
+ iDest = pParse->aLabel[j];
+ }
+ if( iDest<iFirst || iDest>iLast ){
+ int j = iDest;
+ for(; j<v->nOp; j++){
+ VdbeOp *pX = &v->aOp[j];
+ if( pX->opcode==OP_Return ){
+ if( pX->p1==iRetReg ) break;
+ continue;
+ }
+ if( pX->opcode==OP_Noop ) continue;
+ if( pX->opcode==OP_Explain ) continue;
+ if( pErr==0 ){
+ pErr = sqlite3_str_new(0);
+ }else{
+ sqlite3_str_appendchar(pErr, 1, '\n');
+ }
+ sqlite3_str_appendf(pErr,
+ "Opcode at %d jumps to %d which is outside the "
+ "subroutine at %d..%d",
+ i, iDest, iFirst, iLast);
+ break;
+ }
+ }
+ }
+ }
+ if( pErr ){
+ char *zErr = sqlite3_str_finish(pErr);
+ sqlite3VdbeAddOp4(v, OP_Halt, SQLITE_INTERNAL, OE_Abort, 0, zErr, 0);
+ sqlite3_free(zErr);
+ sqlite3MayAbort(pParse);
+ }
+}
+#endif /* SQLITE_DEBUG */
+
/*
** Return the address of the next instruction to be inserted.
*/
SQLITE_PRIVATE int sqlite3VdbeCurrentAddr(Vdbe *p){
- assert( p->iVdbeMagic==VDBE_MAGIC_INIT );
+ assert( p->eVdbeState==VDBE_INIT_STATE );
return p->nOp;
}
@@ -80786,7 +83078,7 @@ SQLITE_PRIVATE VdbeOp *sqlite3VdbeAddOpList(
int i;
VdbeOp *pOut, *pFirst;
assert( nOp>0 );
- assert( p->iVdbeMagic==VDBE_MAGIC_INIT );
+ assert( p->eVdbeState==VDBE_INIT_STATE );
if( p->nOp + nOp > p->nOpAlloc && growOpArray(p, nOp) ){
return 0;
}
@@ -80854,15 +83146,19 @@ SQLITE_PRIVATE void sqlite3VdbeScanStatus(
** for a specific instruction.
*/
SQLITE_PRIVATE void sqlite3VdbeChangeOpcode(Vdbe *p, int addr, u8 iNewOpcode){
+ assert( addr>=0 );
sqlite3VdbeGetOp(p,addr)->opcode = iNewOpcode;
}
SQLITE_PRIVATE void sqlite3VdbeChangeP1(Vdbe *p, int addr, int val){
+ assert( addr>=0 );
sqlite3VdbeGetOp(p,addr)->p1 = val;
}
SQLITE_PRIVATE void sqlite3VdbeChangeP2(Vdbe *p, int addr, int val){
+ assert( addr>=0 || p->db->mallocFailed );
sqlite3VdbeGetOp(p,addr)->p2 = val;
}
SQLITE_PRIVATE void sqlite3VdbeChangeP3(Vdbe *p, int addr, int val){
+ assert( addr>=0 );
sqlite3VdbeGetOp(p,addr)->p3 = val;
}
SQLITE_PRIVATE void sqlite3VdbeChangeP5(Vdbe *p, u16 p5){
@@ -80871,6 +83167,18 @@ SQLITE_PRIVATE void sqlite3VdbeChangeP5(Vdbe *p, u16 p5){
}
/*
+** If the previous opcode is an OP_Column that delivers results
+** into register iDest, then add the OPFLAG_TYPEOFARG flag to that
+** opcode.
+*/
+SQLITE_PRIVATE void sqlite3VdbeTypeofColumn(Vdbe *p, int iDest){
+ VdbeOp *pOp = sqlite3VdbeGetLastOp(p);
+ if( pOp->p3==iDest && pOp->opcode==OP_Column ){
+ pOp->p5 |= OPFLAG_TYPEOFARG;
+ }
+}
+
+/*
** Change the P2 operand of instruction addr so that it points to
** the address of the next instruction to be coded.
*/
@@ -80898,7 +83206,7 @@ SQLITE_PRIVATE void sqlite3VdbeJumpHereOrPopInst(Vdbe *p, int addr){
|| p->aOp[addr].opcode==OP_FkIfZero );
assert( p->aOp[addr].p4type==0 );
#ifdef SQLITE_VDBE_COVERAGE
- sqlite3VdbeGetOp(p,-1)->iSrcLine = 0; /* Erase VdbeCoverage() macros */
+ sqlite3VdbeGetLastOp(p)->iSrcLine = 0; /* Erase VdbeCoverage() macros */
#endif
p->nOp--;
}else{
@@ -80912,8 +83220,9 @@ SQLITE_PRIVATE void sqlite3VdbeJumpHereOrPopInst(Vdbe *p, int addr){
** the FuncDef is not ephermal, then do nothing.
*/
static void freeEphemeralFunction(sqlite3 *db, FuncDef *pDef){
+ assert( db!=0 );
if( (pDef->funcFlags & SQLITE_FUNC_EPHEM)!=0 ){
- sqlite3DbFreeNN(db, pDef);
+ sqlite3DbNNFreeNN(db, pDef);
}
}
@@ -80922,11 +83231,12 @@ static void freeEphemeralFunction(sqlite3 *db, FuncDef *pDef){
*/
static SQLITE_NOINLINE void freeP4Mem(sqlite3 *db, Mem *p){
if( p->szMalloc ) sqlite3DbFree(db, p->zMalloc);
- sqlite3DbFreeNN(db, p);
+ sqlite3DbNNFreeNN(db, p);
}
static SQLITE_NOINLINE void freeP4FuncCtx(sqlite3 *db, sqlite3_context *p){
+ assert( db!=0 );
freeEphemeralFunction(db, p->pFunc);
- sqlite3DbFreeNN(db, p);
+ sqlite3DbNNFreeNN(db, p);
}
static void freeP4(sqlite3 *db, int p4type, void *p4){
assert( db );
@@ -80938,9 +83248,8 @@ static void freeP4(sqlite3 *db, int p4type, void *p4){
case P4_REAL:
case P4_INT64:
case P4_DYNAMIC:
- case P4_DYNBLOB:
case P4_INTARRAY: {
- sqlite3DbFree(db, p4);
+ if( p4 ) sqlite3DbNNFreeNN(db, p4);
break;
}
case P4_KEYINFO: {
@@ -80978,15 +83287,19 @@ static void freeP4(sqlite3 *db, int p4type, void *p4){
** nOp entries.
*/
static void vdbeFreeOpArray(sqlite3 *db, Op *aOp, int nOp){
+ assert( nOp>=0 );
+ assert( db!=0 );
if( aOp ){
- Op *pOp;
- for(pOp=&aOp[nOp-1]; pOp>=aOp; pOp--){
+ Op *pOp = &aOp[nOp-1];
+ while(1){ /* Exit via break */
if( pOp->p4type <= P4_FREE_IF_LE ) freeP4(db, pOp->p4type, pOp->p4.p);
#ifdef SQLITE_ENABLE_EXPLAIN_COMMENTS
sqlite3DbFree(db, pOp->zComment);
#endif
+ if( pOp==aOp ) break;
+ pOp--;
}
- sqlite3DbFreeNN(db, aOp);
+ sqlite3DbNNFreeNN(db, aOp);
}
}
@@ -81046,7 +83359,7 @@ SQLITE_PRIVATE void sqlite3VdbeReleaseRegisters(
u32 mask, /* Mask of registers to NOT release */
int bUndefine /* If true, mark registers as undefined */
){
- if( N==0 ) return;
+ if( N==0 || OptimizationDisabled(pParse->db, SQLITE_ReleaseReg) ) return;
assert( pParse->pVdbe );
assert( iFirst>=1 );
assert( iFirst+N-1<=pParse->nMem );
@@ -81110,7 +83423,7 @@ SQLITE_PRIVATE void sqlite3VdbeChangeP4(Vdbe *p, int addr, const char *zP4, int
sqlite3 *db;
assert( p!=0 );
db = p->db;
- assert( p->iVdbeMagic==VDBE_MAGIC_INIT );
+ assert( p->eVdbeState==VDBE_INIT_STATE );
assert( p->aOp!=0 || db->mallocFailed );
if( db->mallocFailed ){
if( n!=P4_VTAB ) freeP4(db, n, (void*)*(char**)&zP4);
@@ -81155,7 +83468,7 @@ SQLITE_PRIVATE void sqlite3VdbeAppendP4(Vdbe *p, void *pP4, int n){
if( p->db->mallocFailed ){
freeP4(p->db, n, pP4);
}else{
- assert( pP4!=0 );
+ assert( pP4!=0 || n==P4_DYNAMIC );
assert( p->nOp>0 );
pOp = &p->aOp[p->nOp-1];
assert( pOp->p4type==P4_NOTUSED );
@@ -81217,13 +83530,13 @@ SQLITE_PRIVATE void sqlite3VdbeNoopComment(Vdbe *p, const char *zFormat, ...){
** Set the value if the iSrcLine field for the previously coded instruction.
*/
SQLITE_PRIVATE void sqlite3VdbeSetLineNumber(Vdbe *v, int iLine){
- sqlite3VdbeGetOp(v,-1)->iSrcLine = iLine;
+ sqlite3VdbeGetLastOp(v)->iSrcLine = iLine;
}
#endif /* SQLITE_VDBE_COVERAGE */
/*
-** Return the opcode for a given address. If the address is -1, then
-** return the most recently inserted opcode.
+** Return the opcode for a given address. The address must be non-negative.
+** See sqlite3VdbeGetLastOp() to get the most recently added opcode.
**
** If a memory allocation error has occurred prior to the calling of this
** routine, then a pointer to a dummy VdbeOp will be returned. That opcode
@@ -81238,10 +83551,7 @@ SQLITE_PRIVATE VdbeOp *sqlite3VdbeGetOp(Vdbe *p, int addr){
/* C89 specifies that the constant "dummy" will be initialized to all
** zeros, which is correct. MSVC generates a warning, nevertheless. */
static VdbeOp dummy; /* Ignore the MSVC warning about no initializer */
- assert( p->iVdbeMagic==VDBE_MAGIC_INIT );
- if( addr<0 ){
- addr = p->nOp - 1;
- }
+ assert( p->eVdbeState==VDBE_INIT_STATE );
assert( (addr>=0 && addr<p->nOp) || p->db->mallocFailed );
if( p->db->mallocFailed ){
return (VdbeOp*)&dummy;
@@ -81250,6 +83560,12 @@ SQLITE_PRIVATE VdbeOp *sqlite3VdbeGetOp(Vdbe *p, int addr){
}
}
+/* Return the most recently added opcode
+*/
+VdbeOp * sqlite3VdbeGetLastOp(Vdbe *p){
+ return sqlite3VdbeGetOp(p, p->nOp - 1);
+}
+
#if defined(SQLITE_ENABLE_EXPLAIN_COMMENTS)
/*
** Return an integer value for one of the parameters to the opcode pOp
@@ -81305,8 +83621,11 @@ SQLITE_PRIVATE char *sqlite3VdbeDisplayComment(
if( c=='4' ){
sqlite3_str_appendall(&x, zP4);
}else if( c=='X' ){
- sqlite3_str_appendall(&x, pOp->zComment);
- seenCom = 1;
+ if( pOp->zComment && pOp->zComment[0] ){
+ sqlite3_str_appendall(&x, pOp->zComment);
+ seenCom = 1;
+ break;
+ }
}else{
int v1 = translateP(c, pOp);
int v2;
@@ -81535,10 +83854,6 @@ SQLITE_PRIVATE char *sqlite3VdbeDisplayP4(sqlite3 *db, Op *pOp){
zP4 = "program";
break;
}
- case P4_DYNBLOB:
- case P4_ADVANCE: {
- break;
- }
case P4_TABLE: {
zP4 = pOp->p4.pTab->zName;
break;
@@ -81670,21 +83985,40 @@ SQLITE_PRIVATE void sqlite3VdbePrintOp(FILE *pOut, int pc, VdbeOp *pOp){
/*
** Initialize an array of N Mem element.
+**
+** This is a high-runner, so only those fields that really do need to
+** be initialized are set. The Mem structure is organized so that
+** the fields that get initialized are nearby and hopefully on the same
+** cache line.
+**
+** Mem.flags = flags
+** Mem.db = db
+** Mem.szMalloc = 0
+**
+** All other fields of Mem can safely remain uninitialized for now. They
+** will be initialized before use.
*/
static void initMemArray(Mem *p, int N, sqlite3 *db, u16 flags){
- while( (N--)>0 ){
- p->db = db;
- p->flags = flags;
- p->szMalloc = 0;
+ if( N>0 ){
+ do{
+ p->flags = flags;
+ p->db = db;
+ p->szMalloc = 0;
#ifdef SQLITE_DEBUG
- p->pScopyFrom = 0;
+ p->pScopyFrom = 0;
#endif
- p++;
+ p++;
+ }while( (--N)>0 );
}
}
/*
-** Release an array of N Mem elements
+** Release auxiliary memory held in an array of N Mem elements.
+**
+** After this routine returns, all Mem elements in the array will still
+** be valid. Those Mem elements that were not holding auxiliary resources
+** will be unchanged. Mem elements which had something freed will be
+** set to MEM_Undefined.
*/
static void releaseMemArray(Mem *p, int N){
if( p && N ){
@@ -81717,12 +84051,17 @@ static void releaseMemArray(Mem *p, int N){
if( p->flags&(MEM_Agg|MEM_Dyn) ){
testcase( (p->flags & MEM_Dyn)!=0 && p->xDel==sqlite3VdbeFrameMemDel );
sqlite3VdbeMemRelease(p);
+ p->flags = MEM_Undefined;
}else if( p->szMalloc ){
- sqlite3DbFreeNN(db, p->zMalloc);
+ sqlite3DbNNFreeNN(db, p->zMalloc);
p->szMalloc = 0;
+ p->flags = MEM_Undefined;
}
-
- p->flags = MEM_Undefined;
+#ifdef SQLITE_DEBUG
+ else{
+ p->flags = MEM_Undefined;
+ }
+#endif
}while( (++p)<pEnd );
}
}
@@ -81881,7 +84220,7 @@ SQLITE_PRIVATE void sqlite3VdbeFrameDelete(VdbeFrame *p){
VdbeCursor **apCsr = (VdbeCursor **)&aMem[p->nChildMem];
assert( sqlite3VdbeFrameIsValid(p) );
for(i=0; i<p->nChildCsr; i++){
- sqlite3VdbeFreeCursor(p->v, apCsr[i]);
+ if( apCsr[i] ) sqlite3VdbeFreeCursorNN(p->v, apCsr[i]);
}
releaseMemArray(aMem, p->nChildMem);
sqlite3VdbeDeleteAuxData(p->v->db, &p->pAuxData, -1, 0);
@@ -81920,7 +84259,7 @@ SQLITE_PRIVATE int sqlite3VdbeList(
Op *pOp; /* Current opcode */
assert( p->explain );
- assert( p->iVdbeMagic==VDBE_MAGIC_RUN );
+ assert( p->eVdbeState==VDBE_RUN_STATE );
assert( p->rc==SQLITE_OK || p->rc==SQLITE_BUSY || p->rc==SQLITE_NOMEM );
/* Even though this opcode does not use dynamic strings for
@@ -82075,11 +84414,11 @@ struct ReusableSpace {
static void *allocSpace(
struct ReusableSpace *p, /* Bulk memory available for allocation */
void *pBuf, /* Pointer to a prior allocation */
- sqlite3_int64 nByte /* Bytes of memory needed */
+ sqlite3_int64 nByte /* Bytes of memory needed. */
){
assert( EIGHT_BYTE_ALIGNMENT(p->pSpace) );
if( pBuf==0 ){
- nByte = ROUND8(nByte);
+ nByte = ROUND8P(nByte);
if( nByte <= p->nFree ){
p->nFree -= nByte;
pBuf = &p->pSpace[p->nFree];
@@ -82100,14 +84439,15 @@ SQLITE_PRIVATE void sqlite3VdbeRewind(Vdbe *p){
int i;
#endif
assert( p!=0 );
- assert( p->iVdbeMagic==VDBE_MAGIC_INIT || p->iVdbeMagic==VDBE_MAGIC_RESET );
+ assert( p->eVdbeState==VDBE_INIT_STATE
+ || p->eVdbeState==VDBE_READY_STATE
+ || p->eVdbeState==VDBE_HALT_STATE );
/* There should be at least one opcode.
*/
assert( p->nOp>0 );
- /* Set the magic to VDBE_MAGIC_RUN sooner rather than later. */
- p->iVdbeMagic = VDBE_MAGIC_RUN;
+ p->eVdbeState = VDBE_READY_STATE;
#ifdef SQLITE_DEBUG
for(i=0; i<p->nMem; i++){
@@ -82163,7 +84503,7 @@ SQLITE_PRIVATE void sqlite3VdbeMakeReady(
assert( p!=0 );
assert( p->nOp>0 );
assert( pParse!=0 );
- assert( p->iVdbeMagic==VDBE_MAGIC_INIT );
+ assert( p->eVdbeState==VDBE_INIT_STATE );
assert( pParse==p->pParse );
p->pVList = pParse->pVList;
pParse->pVList = 0;
@@ -82186,7 +84526,7 @@ SQLITE_PRIVATE void sqlite3VdbeMakeReady(
** opcode array. This extra memory will be reallocated for other elements
** of the prepared statement.
*/
- n = ROUND8(sizeof(Op)*p->nOp); /* Bytes of opcode memory used */
+ n = ROUND8P(sizeof(Op)*p->nOp); /* Bytes of opcode memory used */
x.pSpace = &((u8*)p->aOp)[n]; /* Unused opcode memory */
assert( EIGHT_BYTE_ALIGNMENT(x.pSpace) );
x.nFree = ROUNDDOWN8(pParse->szOpAlloc - n); /* Bytes of unused memory */
@@ -82274,9 +84614,9 @@ SQLITE_PRIVATE void sqlite3VdbeMakeReady(
** happens to hold.
*/
SQLITE_PRIVATE void sqlite3VdbeFreeCursor(Vdbe *p, VdbeCursor *pCx){
- if( pCx==0 ){
- return;
- }
+ if( pCx ) sqlite3VdbeFreeCursorNN(p,pCx);
+}
+SQLITE_PRIVATE void sqlite3VdbeFreeCursorNN(Vdbe *p, VdbeCursor *pCx){
switch( pCx->eCurType ){
case CURTYPE_SORTER: {
sqlite3VdbeSorterClose(p->db, pCx);
@@ -82304,14 +84644,12 @@ SQLITE_PRIVATE void sqlite3VdbeFreeCursor(Vdbe *p, VdbeCursor *pCx){
** Close all cursors in the current frame.
*/
static void closeCursorsInFrame(Vdbe *p){
- if( p->apCsr ){
- int i;
- for(i=0; i<p->nCursor; i++){
- VdbeCursor *pC = p->apCsr[i];
- if( pC ){
- sqlite3VdbeFreeCursor(p, pC);
- p->apCsr[i] = 0;
- }
+ int i;
+ for(i=0; i<p->nCursor; i++){
+ VdbeCursor *pC = p->apCsr[i];
+ if( pC ){
+ sqlite3VdbeFreeCursorNN(p, pC);
+ p->apCsr[i] = 0;
}
}
}
@@ -82360,9 +84698,7 @@ static void closeAllCursors(Vdbe *p){
}
assert( p->nFrame==0 );
closeCursorsInFrame(p);
- if( p->aMem ){
- releaseMemArray(p->aMem, p->nMem);
- }
+ releaseMemArray(p->aMem, p->nMem);
while( p->pDelFrame ){
VdbeFrame *pDel = p->pDelFrame;
p->pDelFrame = pDel->pParent;
@@ -82709,7 +85045,7 @@ static void checkActiveVdbeCnt(sqlite3 *db){
if( p->readOnly==0 ) nWrite++;
if( p->bIsReader ) nRead++;
}
- p = p->pNext;
+ p = p->pVNext;
}
assert( cnt==db->nVdbeActive );
assert( nWrite==db->nVdbeWrite );
@@ -82802,7 +85138,8 @@ SQLITE_PRIVATE int sqlite3VdbeCheckFk(Vdbe *p, int deferred){
p->rc = SQLITE_CONSTRAINT_FOREIGNKEY;
p->errorAction = OE_Abort;
sqlite3VdbeError(p, "FOREIGN KEY constraint failed");
- return SQLITE_ERROR;
+ if( (p->prepFlags & SQLITE_PREPARE_SAVESQL)==0 ) return SQLITE_ERROR;
+ return SQLITE_CONSTRAINT_FOREIGNKEY;
}
return SQLITE_OK;
}
@@ -82841,9 +85178,7 @@ SQLITE_PRIVATE int sqlite3VdbeHalt(Vdbe *p){
** one, or the complete transaction if there is no statement transaction.
*/
- if( p->iVdbeMagic!=VDBE_MAGIC_RUN ){
- return SQLITE_OK;
- }
+ assert( p->eVdbeState==VDBE_RUN_STATE );
if( db->mallocFailed ){
p->rc = SQLITE_NOMEM_BKPT;
}
@@ -82852,7 +85187,7 @@ SQLITE_PRIVATE int sqlite3VdbeHalt(Vdbe *p){
/* No commit or rollback needed if the program never started or if the
** SQL statement does not read or write a database file. */
- if( p->pc>=0 && p->bIsReader ){
+ if( p->bIsReader ){
int mrc; /* Primary error code from p->rc */
int eStatementOp = 0;
int isSpecialError; /* Set to true if a 'special' error */
@@ -83000,15 +85335,13 @@ SQLITE_PRIVATE int sqlite3VdbeHalt(Vdbe *p){
}
/* We have successfully halted and closed the VM. Record this fact. */
- if( p->pc>=0 ){
- db->nVdbeActive--;
- if( !p->readOnly ) db->nVdbeWrite--;
- if( p->bIsReader ) db->nVdbeRead--;
- assert( db->nVdbeActive>=db->nVdbeRead );
- assert( db->nVdbeRead>=db->nVdbeWrite );
- assert( db->nVdbeWrite>=0 );
- }
- p->iVdbeMagic = VDBE_MAGIC_HALT;
+ db->nVdbeActive--;
+ if( !p->readOnly ) db->nVdbeWrite--;
+ if( p->bIsReader ) db->nVdbeRead--;
+ assert( db->nVdbeActive>=db->nVdbeRead );
+ assert( db->nVdbeRead>=db->nVdbeWrite );
+ assert( db->nVdbeWrite>=0 );
+ p->eVdbeState = VDBE_HALT_STATE;
checkActiveVdbeCnt(db);
if( db->mallocFailed ){
p->rc = SQLITE_NOMEM_BKPT;
@@ -83090,8 +85423,8 @@ static void vdbeInvokeSqllog(Vdbe *v){
** again.
**
** To look at it another way, this routine resets the state of the
-** virtual machine from VDBE_MAGIC_RUN or VDBE_MAGIC_HALT back to
-** VDBE_MAGIC_INIT.
+** virtual machine from VDBE_RUN_STATE or VDBE_HALT_STATE back to
+** VDBE_READY_STATE.
*/
SQLITE_PRIVATE int sqlite3VdbeReset(Vdbe *p){
#if defined(SQLITE_DEBUG) || defined(VDBE_PROFILE)
@@ -83105,7 +85438,7 @@ SQLITE_PRIVATE int sqlite3VdbeReset(Vdbe *p){
** error, then it might not have been halted properly. So halt
** it now.
*/
- sqlite3VdbeHalt(p);
+ if( p->eVdbeState==VDBE_RUN_STATE ) sqlite3VdbeHalt(p);
/* If the VDBE has been run even partially, then transfer the error code
** and error message from the VDBE into the main database structure. But
@@ -83119,13 +85452,6 @@ SQLITE_PRIVATE int sqlite3VdbeReset(Vdbe *p){
}else{
db->errCode = p->rc;
}
- if( p->runOnlyOnce ) p->expired = 1;
- }else if( p->rc && p->expired ){
- /* The expired flag was set on the VDBE before the first call
- ** to sqlite3_step(). For consistency (since sqlite3_step() was
- ** called), set the database error in this case as well.
- */
- sqlite3ErrorWithMsg(db, p->rc, p->zErrMsg ? "%s" : 0, p->zErrMsg);
}
/* Reset register contents and reclaim error message memory.
@@ -83182,7 +85508,6 @@ SQLITE_PRIVATE int sqlite3VdbeReset(Vdbe *p){
}
}
#endif
- p->iVdbeMagic = VDBE_MAGIC_RESET;
return p->rc & db->errMask;
}
@@ -83192,7 +85517,10 @@ SQLITE_PRIVATE int sqlite3VdbeReset(Vdbe *p){
*/
SQLITE_PRIVATE int sqlite3VdbeFinalize(Vdbe *p){
int rc = SQLITE_OK;
- if( p->iVdbeMagic==VDBE_MAGIC_RUN || p->iVdbeMagic==VDBE_MAGIC_HALT ){
+ assert( VDBE_RUN_STATE>VDBE_READY_STATE );
+ assert( VDBE_HALT_STATE>VDBE_READY_STATE );
+ assert( VDBE_INIT_STATE<VDBE_READY_STATE );
+ if( p->eVdbeState>=VDBE_READY_STATE ){
rc = sqlite3VdbeReset(p);
assert( (rc & p->db->errMask)==rc );
}
@@ -83244,23 +85572,26 @@ SQLITE_PRIVATE void sqlite3VdbeDeleteAuxData(sqlite3 *db, AuxData **pp, int iOp,
** VdbeDelete() also unlinks the Vdbe from the list of VMs associated with
** the database connection and frees the object itself.
*/
-SQLITE_PRIVATE void sqlite3VdbeClearObject(sqlite3 *db, Vdbe *p){
+static void sqlite3VdbeClearObject(sqlite3 *db, Vdbe *p){
SubProgram *pSub, *pNext;
+ assert( db!=0 );
assert( p->db==0 || p->db==db );
- releaseMemArray(p->aColName, p->nResColumn*COLNAME_N);
+ if( p->aColName ){
+ releaseMemArray(p->aColName, p->nResColumn*COLNAME_N);
+ sqlite3DbNNFreeNN(db, p->aColName);
+ }
for(pSub=p->pProgram; pSub; pSub=pNext){
pNext = pSub->pNext;
vdbeFreeOpArray(db, pSub->aOp, pSub->nOp);
sqlite3DbFree(db, pSub);
}
- if( p->iVdbeMagic!=VDBE_MAGIC_INIT ){
+ if( p->eVdbeState!=VDBE_INIT_STATE ){
releaseMemArray(p->aVar, p->nVar);
- sqlite3DbFree(db, p->pVList);
- sqlite3DbFree(db, p->pFree);
+ if( p->pVList ) sqlite3DbNNFreeNN(db, p->pVList);
+ if( p->pFree ) sqlite3DbNNFreeNN(db, p->pFree);
}
vdbeFreeOpArray(db, p->aOp, p->nOp);
- sqlite3DbFree(db, p->aColName);
- sqlite3DbFree(db, p->zSql);
+ if( p->zSql ) sqlite3DbNNFreeNN(db, p->zSql);
#ifdef SQLITE_ENABLE_NORMALIZE
sqlite3DbFree(db, p->zNormSql);
{
@@ -83290,20 +85621,17 @@ SQLITE_PRIVATE void sqlite3VdbeDelete(Vdbe *p){
assert( p!=0 );
db = p->db;
+ assert( db!=0 );
assert( sqlite3_mutex_held(db->mutex) );
sqlite3VdbeClearObject(db, p);
- if( p->pPrev ){
- p->pPrev->pNext = p->pNext;
- }else{
- assert( db->pVdbe==p );
- db->pVdbe = p->pNext;
- }
- if( p->pNext ){
- p->pNext->pPrev = p->pPrev;
+ if( db->pnBytesFreed==0 ){
+ assert( p->ppVPrev!=0 );
+ *p->ppVPrev = p->pVNext;
+ if( p->pVNext ){
+ p->pVNext->ppVPrev = p->ppVPrev;
+ }
}
- p->iVdbeMagic = VDBE_MAGIC_DEAD;
- p->db = 0;
- sqlite3DbFreeNN(db, p);
+ sqlite3DbNNFreeNN(db, p);
}
/*
@@ -83337,7 +85665,7 @@ SQLITE_PRIVATE int SQLITE_NOINLINE sqlite3VdbeFinishMoveto(VdbeCursor *p){
** is supposed to be pointing. If the row was deleted out from under the
** cursor, set the cursor to point to a NULL row.
*/
-static int SQLITE_NOINLINE handleMovedCursor(VdbeCursor *p){
+SQLITE_PRIVATE int SQLITE_NOINLINE sqlite3VdbeHandleMovedCursor(VdbeCursor *p){
int isDifferentRow, rc;
assert( p->eCurType==CURTYPE_BTREE );
assert( p->uc.pCursor!=0 );
@@ -83353,41 +85681,9 @@ static int SQLITE_NOINLINE handleMovedCursor(VdbeCursor *p){
** if need be. Return any I/O error from the restore operation.
*/
SQLITE_PRIVATE int sqlite3VdbeCursorRestore(VdbeCursor *p){
- assert( p->eCurType==CURTYPE_BTREE );
+ assert( p->eCurType==CURTYPE_BTREE || IsNullCursor(p) );
if( sqlite3BtreeCursorHasMoved(p->uc.pCursor) ){
- return handleMovedCursor(p);
- }
- return SQLITE_OK;
-}
-
-/*
-** Make sure the cursor p is ready to read or write the row to which it
-** was last positioned. Return an error code if an OOM fault or I/O error
-** prevents us from positioning the cursor to its correct position.
-**
-** If a MoveTo operation is pending on the given cursor, then do that
-** MoveTo now. If no move is pending, check to see if the row has been
-** deleted out from under the cursor and if it has, mark the row as
-** a NULL row.
-**
-** If the cursor is already pointing to the correct row and that row has
-** not been deleted out from under the cursor, then this routine is a no-op.
-*/
-SQLITE_PRIVATE int sqlite3VdbeCursorMoveto(VdbeCursor **pp, u32 *piCol){
- VdbeCursor *p = *pp;
- assert( p->eCurType==CURTYPE_BTREE || p->eCurType==CURTYPE_PSEUDO );
- if( p->deferredMoveto ){
- u32 iMap;
- assert( !p->isEphemeral );
- if( p->ub.aAltMap && (iMap = p->ub.aAltMap[1+*piCol])>0 && !p->nullRow ){
- *pp = p->pAltCursor;
- *piCol = iMap - 1;
- return SQLITE_OK;
- }
- return sqlite3VdbeFinishMoveto(p);
- }
- if( sqlite3BtreeCursorHasMoved(p->uc.pCursor) ){
- return handleMovedCursor(p);
+ return sqlite3VdbeHandleMovedCursor(p);
}
return SQLITE_OK;
}
@@ -83398,7 +85694,7 @@ SQLITE_PRIVATE int sqlite3VdbeCursorMoveto(VdbeCursor **pp, u32 *piCol){
** sqlite3VdbeSerialType()
** sqlite3VdbeSerialTypeLen()
** sqlite3VdbeSerialLen()
-** sqlite3VdbeSerialPut()
+** sqlite3VdbeSerialPut() <--- in-lined into OP_MakeRecord as of 2022-04-02
** sqlite3VdbeSerialGet()
**
** encapsulate the code that serializes values for storage in SQLite
@@ -83510,7 +85806,7 @@ SQLITE_PRIVATE u32 sqlite3VdbeSerialType(Mem *pMem, int file_format, u32 *pLen){
/*
** The sizes for serial types less than 128
*/
-static const u8 sqlite3SmallTypeSizes[] = {
+SQLITE_PRIVATE const u8 sqlite3SmallTypeSizes[128] = {
/* 0 1 2 3 4 5 6 7 8 9 */
/* 0 */ 0, 1, 2, 3, 4, 6, 8, 8, 0, 0,
/* 10 */ 0, 0, 0, 0, 1, 1, 2, 2, 3, 3,
@@ -83579,7 +85875,7 @@ SQLITE_PRIVATE u8 sqlite3VdbeOneByteSerialTypeLen(u8 serial_type){
** so we trust him.
*/
#ifdef SQLITE_MIXED_ENDIAN_64BIT_FLOAT
-static u64 floatSwap(u64 in){
+SQLITE_PRIVATE u64 sqlite3FloatSwap(u64 in){
union {
u64 r;
u32 i[2];
@@ -83592,59 +85888,8 @@ static u64 floatSwap(u64 in){
u.i[1] = t;
return u.r;
}
-# define swapMixedEndianFloat(X) X = floatSwap(X)
-#else
-# define swapMixedEndianFloat(X)
-#endif
-
-/*
-** Write the serialized data blob for the value stored in pMem into
-** buf. It is assumed that the caller has allocated sufficient space.
-** Return the number of bytes written.
-**
-** nBuf is the amount of space left in buf[]. The caller is responsible
-** for allocating enough space to buf[] to hold the entire field, exclusive
-** of the pMem->u.nZero bytes for a MEM_Zero value.
-**
-** Return the number of bytes actually written into buf[]. The number
-** of bytes in the zero-filled tail is included in the return value only
-** if those bytes were zeroed in buf[].
-*/
-SQLITE_PRIVATE u32 sqlite3VdbeSerialPut(u8 *buf, Mem *pMem, u32 serial_type){
- u32 len;
-
- /* Integer and Real */
- if( serial_type<=7 && serial_type>0 ){
- u64 v;
- u32 i;
- if( serial_type==7 ){
- assert( sizeof(v)==sizeof(pMem->u.r) );
- memcpy(&v, &pMem->u.r, sizeof(v));
- swapMixedEndianFloat(v);
- }else{
- v = pMem->u.i;
- }
- len = i = sqlite3SmallTypeSizes[serial_type];
- assert( i>0 );
- do{
- buf[--i] = (u8)(v&0xFF);
- v >>= 8;
- }while( i );
- return len;
- }
+#endif /* SQLITE_MIXED_ENDIAN_64BIT_FLOAT */
- /* String or blob */
- if( serial_type>=12 ){
- assert( pMem->n + ((pMem->flags & MEM_Zero)?pMem->u.nZero:0)
- == (int)sqlite3VdbeSerialTypeLen(serial_type) );
- len = pMem->n;
- if( len>0 ) memcpy(buf, pMem->z, len);
- return len;
- }
-
- /* NULL or constants 0 or 1 */
- return 0;
-}
/* Input "x" is a sequence of unsigned characters that represent a
** big-endian integer. Return the equivalent native integer
@@ -83810,10 +86055,10 @@ SQLITE_PRIVATE UnpackedRecord *sqlite3VdbeAllocUnpackedRecord(
){
UnpackedRecord *p; /* Unpacked record to return */
int nByte; /* Number of bytes required for *p */
- nByte = ROUND8(sizeof(UnpackedRecord)) + sizeof(Mem)*(pKeyInfo->nKeyField+1);
+ nByte = ROUND8P(sizeof(UnpackedRecord)) + sizeof(Mem)*(pKeyInfo->nKeyField+1);
p = (UnpackedRecord *)sqlite3DbMallocRaw(pKeyInfo->db, nByte);
if( !p ) return 0;
- p->aMem = (Mem*)&((char*)p)[ROUND8(sizeof(UnpackedRecord))];
+ p->aMem = (Mem*)&((char*)p)[ROUND8P(sizeof(UnpackedRecord))];
assert( pKeyInfo->aSortFlags!=0 );
p->pKeyInfo = pKeyInfo;
p->nField = pKeyInfo->nKeyField + 1;
@@ -84049,8 +86294,8 @@ static int vdbeCompareMemString(
}else{
rc = pColl->xCmp(pColl->pUser, c1.n, v1, c2.n, v2);
}
- sqlite3VdbeMemRelease(&c1);
- sqlite3VdbeMemRelease(&c2);
+ sqlite3VdbeMemReleaseMalloc(&c1);
+ sqlite3VdbeMemReleaseMalloc(&c2);
return rc;
}
}
@@ -84311,14 +86556,22 @@ SQLITE_PRIVATE int sqlite3VdbeRecordCompareWithSkip(
** two elements in the keys are equal. Fix the various stack variables so
** that this routine begins comparing at the second field. */
if( bSkip ){
- u32 s1;
- idx1 = 1 + getVarint32(&aKey1[1], s1);
+ u32 s1 = aKey1[1];
+ if( s1<0x80 ){
+ idx1 = 2;
+ }else{
+ idx1 = 1 + sqlite3GetVarint32(&aKey1[1], &s1);
+ }
szHdr1 = aKey1[0];
d1 = szHdr1 + sqlite3VdbeSerialTypeLen(s1);
i = 1;
pRhs++;
}else{
- idx1 = getVarint32(aKey1, szHdr1);
+ if( (szHdr1 = aKey1[0])<0x80 ){
+ idx1 = 1;
+ }else{
+ idx1 = sqlite3GetVarint32(aKey1, &szHdr1);
+ }
d1 = szHdr1;
i = 0;
}
@@ -84333,7 +86586,7 @@ SQLITE_PRIVATE int sqlite3VdbeRecordCompareWithSkip(
assert( pPKey2->pKeyInfo->aSortFlags!=0 );
assert( pPKey2->pKeyInfo->nKeyField>0 );
assert( idx1<=szHdr1 || CORRUPT_DB );
- do{
+ while( 1 /*exit-by-break*/ ){
u32 serial_type;
/* RHS is an integer */
@@ -84343,7 +86596,7 @@ SQLITE_PRIVATE int sqlite3VdbeRecordCompareWithSkip(
serial_type = aKey1[idx1];
testcase( serial_type==12 );
if( serial_type>=10 ){
- rc = +1;
+ rc = serial_type==10 ? -1 : +1;
}else if( serial_type==0 ){
rc = -1;
}else if( serial_type==7 ){
@@ -84368,7 +86621,7 @@ SQLITE_PRIVATE int sqlite3VdbeRecordCompareWithSkip(
** numbers). Types 10 and 11 are currently "reserved for future
** use", so it doesn't really matter what the results of comparing
** them to numberic values are. */
- rc = +1;
+ rc = serial_type==10 ? -1 : +1;
}else if( serial_type==0 ){
rc = -1;
}else{
@@ -84449,7 +86702,7 @@ SQLITE_PRIVATE int sqlite3VdbeRecordCompareWithSkip(
/* RHS is null */
else{
serial_type = aKey1[idx1];
- rc = (serial_type!=0);
+ rc = (serial_type!=0 && serial_type!=10);
}
if( rc!=0 ){
@@ -84471,8 +86724,13 @@ SQLITE_PRIVATE int sqlite3VdbeRecordCompareWithSkip(
if( i==pPKey2->nField ) break;
pRhs++;
d1 += sqlite3VdbeSerialTypeLen(serial_type);
+ if( d1>(unsigned)nKey1 ) break;
idx1 += sqlite3VarintLen(serial_type);
- }while( idx1<(unsigned)szHdr1 && d1<=(unsigned)nKey1 );
+ if( idx1>=(unsigned)szHdr1 ){
+ pPKey2->errCode = (u8)SQLITE_CORRUPT_BKPT;
+ return 0; /* Corrupt index */
+ }
+ }
/* No memory allocation is ever used on mem1. Prove this using
** the following assert(). If the assert() fails, it indicates a
@@ -84574,7 +86832,8 @@ static int vdbeRecordCompareInt(
return sqlite3VdbeRecordCompare(nKey1, pKey1, pPKey2);
}
- v = pPKey2->aMem[0].u.i;
+ assert( pPKey2->u.i == pPKey2->aMem[0].u.i );
+ v = pPKey2->u.i;
if( v>lhs ){
res = pPKey2->r1;
}else if( v<lhs ){
@@ -84609,12 +86868,18 @@ static int vdbeRecordCompareString(
int res;
assert( pPKey2->aMem[0].flags & MEM_Str );
+ assert( pPKey2->aMem[0].n == pPKey2->n );
+ assert( pPKey2->aMem[0].z == pPKey2->u.z );
vdbeAssertFieldCountWithinLimits(nKey1, pKey1, pPKey2->pKeyInfo);
- serial_type = (u8)(aKey1[1]);
- if( serial_type >= 0x80 ){
- sqlite3GetVarint32(&aKey1[1], (u32*)&serial_type);
- }
+ serial_type = (signed char)(aKey1[1]);
+
+vrcs_restart:
if( serial_type<12 ){
+ if( serial_type<0 ){
+ sqlite3GetVarint32(&aKey1[1], (u32*)&serial_type);
+ if( serial_type>=12 ) goto vrcs_restart;
+ assert( CORRUPT_DB );
+ }
res = pPKey2->r1; /* (pKey1/nKey1) is a number or a null */
}else if( !(serial_type & 0x01) ){
res = pPKey2->r2; /* (pKey1/nKey1) is a blob */
@@ -84628,15 +86893,15 @@ static int vdbeRecordCompareString(
pPKey2->errCode = (u8)SQLITE_CORRUPT_BKPT;
return 0; /* Corruption */
}
- nCmp = MIN( pPKey2->aMem[0].n, nStr );
- res = memcmp(&aKey1[szHdr], pPKey2->aMem[0].z, nCmp);
+ nCmp = MIN( pPKey2->n, nStr );
+ res = memcmp(&aKey1[szHdr], pPKey2->u.z, nCmp);
if( res>0 ){
res = pPKey2->r2;
}else if( res<0 ){
res = pPKey2->r1;
}else{
- res = nStr - pPKey2->aMem[0].n;
+ res = nStr - pPKey2->n;
if( res==0 ){
if( pPKey2->nField>1 ){
res = sqlite3VdbeRecordCompareWithSkip(nKey1, pKey1, pPKey2, 1);
@@ -84691,6 +86956,7 @@ SQLITE_PRIVATE RecordCompare sqlite3VdbeFindCompare(UnpackedRecord *p){
p->r2 = 1;
}
if( (flags & MEM_Int) ){
+ p->u.i = p->aMem[0].u.i;
return vdbeRecordCompareInt;
}
testcase( flags & MEM_Real );
@@ -84700,6 +86966,8 @@ SQLITE_PRIVATE RecordCompare sqlite3VdbeFindCompare(UnpackedRecord *p){
&& p->pKeyInfo->aColl[0]==0
){
assert( flags & MEM_Str );
+ p->u.z = p->aMem[0].z;
+ p->n = p->aMem[0].n;
return vdbeRecordCompareString;
}
}
@@ -84772,14 +87040,14 @@ SQLITE_PRIVATE int sqlite3VdbeIdxRowid(sqlite3 *db, BtCursor *pCur, i64 *rowid){
/* Fetch the integer off the end of the index record */
sqlite3VdbeSerialGet((u8*)&m.z[m.n-lenRowid], typeRowid, &v);
*rowid = v.u.i;
- sqlite3VdbeMemRelease(&m);
+ sqlite3VdbeMemReleaseMalloc(&m);
return SQLITE_OK;
/* Jump here if database corruption is detected after m has been
** allocated. Free the m object and return SQLITE_CORRUPT. */
idx_rowid_corruption:
testcase( m.szMalloc!=0 );
- sqlite3VdbeMemRelease(&m);
+ sqlite3VdbeMemReleaseMalloc(&m);
return SQLITE_CORRUPT_BKPT;
}
@@ -84821,7 +87089,7 @@ SQLITE_PRIVATE int sqlite3VdbeIdxKeyCompare(
return rc;
}
*res = sqlite3VdbeRecordCompareWithSkip(m.n, m.z, pUnpacked, 0);
- sqlite3VdbeMemRelease(&m);
+ sqlite3VdbeMemReleaseMalloc(&m);
return SQLITE_OK;
}
@@ -84863,7 +87131,7 @@ SQLITE_PRIVATE void sqlite3VdbeCountChanges(Vdbe *v){
*/
SQLITE_PRIVATE void sqlite3ExpirePreparedStatements(sqlite3 *db, int iCode){
Vdbe *p;
- for(p = db->pVdbe; p; p=p->pNext){
+ for(p = db->pVdbe; p; p=p->pVNext){
p->expired = iCode+1;
}
}
@@ -84984,13 +87252,14 @@ SQLITE_PRIVATE void sqlite3VtabImportErrmsg(Vdbe *p, sqlite3_vtab *pVtab){
** the vdbeUnpackRecord() function found in vdbeapi.c.
*/
static void vdbeFreeUnpacked(sqlite3 *db, int nField, UnpackedRecord *p){
+ assert( db!=0 );
if( p ){
int i;
for(i=0; i<nField; i++){
Mem *pMem = &p->aMem[i];
- if( pMem->zMalloc ) sqlite3VdbeMemRelease(pMem);
+ if( pMem->zMalloc ) sqlite3VdbeMemReleaseMalloc(pMem);
}
- sqlite3DbFreeNN(db, p);
+ sqlite3DbNNFreeNN(db, p);
}
}
#endif /* SQLITE_ENABLE_PREUPDATE_HOOK */
@@ -85061,7 +87330,7 @@ SQLITE_PRIVATE void sqlite3VdbePreUpdateHook(
for(i=0; i<pCsr->nField; i++){
sqlite3VdbeMemRelease(&preupdate.aNew[i]);
}
- sqlite3DbFreeNN(db, preupdate.aNew);
+ sqlite3DbNNFreeNN(db, preupdate.aNew);
}
}
#endif /* SQLITE_ENABLE_PREUPDATE_HOOK */
@@ -85178,7 +87447,9 @@ SQLITE_API int sqlite3_finalize(sqlite3_stmt *pStmt){
if( vdbeSafety(v) ) return SQLITE_MISUSE_BKPT;
sqlite3_mutex_enter(db->mutex);
checkProfileCallback(db, v);
- rc = sqlite3VdbeFinalize(v);
+ assert( v->eVdbeState>=VDBE_READY_STATE );
+ rc = sqlite3VdbeReset(v);
+ sqlite3VdbeDelete(v);
rc = sqlite3ApiExit(db, rc);
sqlite3LeaveMutexAndCloseZombie(db);
}
@@ -85386,6 +87657,9 @@ SQLITE_API int sqlite3_value_type(sqlite3_value* pVal){
#endif
return aType[pVal->flags&MEM_AffMask];
}
+SQLITE_API int sqlite3_value_encoding(sqlite3_value *pVal){
+ return pVal->enc;
+}
/* Return true if a parameter to xUpdate represents an unchanged column */
SQLITE_API int sqlite3_value_nochange(sqlite3_value *pVal){
@@ -85415,6 +87689,9 @@ SQLITE_API sqlite3_value *sqlite3_value_dup(const sqlite3_value *pOrig){
sqlite3ValueFree(pNew);
pNew = 0;
}
+ }else if( pNew->flags & MEM_Null ){
+ /* Do not duplicate pointer values */
+ pNew->flags &= ~(MEM_Term|MEM_Subtype);
}
return pNew;
}
@@ -85445,7 +87722,8 @@ static void setResultStrOrError(
u8 enc, /* Encoding of z. 0 for BLOBs */
void (*xDel)(void*) /* Destructor function */
){
- int rc = sqlite3VdbeMemSetStr(pCtx->pOut, z, n, enc, xDel);
+ Mem *pOut = pCtx->pOut;
+ int rc = sqlite3VdbeMemSetStr(pOut, z, n, enc, xDel);
if( rc ){
if( rc==SQLITE_TOOBIG ){
sqlite3_result_error_toobig(pCtx);
@@ -85455,6 +87733,11 @@ static void setResultStrOrError(
assert( rc==SQLITE_NOMEM );
sqlite3_result_error_nomem(pCtx);
}
+ return;
+ }
+ sqlite3VdbeChangeEncoding(pOut, pCtx->enc);
+ if( sqlite3VdbeMemTooBig(pOut) ){
+ sqlite3_result_error_toobig(pCtx);
}
}
static int invokeValueDestructor(
@@ -85598,17 +87881,22 @@ SQLITE_API void sqlite3_result_text16le(
}
#endif /* SQLITE_OMIT_UTF16 */
SQLITE_API void sqlite3_result_value(sqlite3_context *pCtx, sqlite3_value *pValue){
+ Mem *pOut = pCtx->pOut;
assert( sqlite3_mutex_held(pCtx->pOut->db->mutex) );
- sqlite3VdbeMemCopy(pCtx->pOut, pValue);
+ sqlite3VdbeMemCopy(pOut, pValue);
+ sqlite3VdbeChangeEncoding(pOut, pCtx->enc);
+ if( sqlite3VdbeMemTooBig(pOut) ){
+ sqlite3_result_error_toobig(pCtx);
+ }
}
SQLITE_API void sqlite3_result_zeroblob(sqlite3_context *pCtx, int n){
- assert( sqlite3_mutex_held(pCtx->pOut->db->mutex) );
- sqlite3VdbeMemSetZeroBlob(pCtx->pOut, n);
+ sqlite3_result_zeroblob64(pCtx, n>0 ? n : 0);
}
SQLITE_API int sqlite3_result_zeroblob64(sqlite3_context *pCtx, u64 n){
Mem *pOut = pCtx->pOut;
assert( sqlite3_mutex_held(pOut->db->mutex) );
if( n>(u64)pOut->db->aLimit[SQLITE_LIMIT_LENGTH] ){
+ sqlite3_result_error_toobig(pCtx);
return SQLITE_TOOBIG;
}
#ifndef SQLITE_OMIT_INCRBLOB
@@ -85624,8 +87912,8 @@ SQLITE_API void sqlite3_result_error_code(sqlite3_context *pCtx, int errCode){
if( pCtx->pVdbe ) pCtx->pVdbe->rcApp = errCode;
#endif
if( pCtx->pOut->flags & MEM_Null ){
- sqlite3VdbeMemSetStr(pCtx->pOut, sqlite3ErrStr(errCode), -1,
- SQLITE_UTF8, SQLITE_STATIC);
+ setResultStrOrError(pCtx, sqlite3ErrStr(errCode), -1, SQLITE_UTF8,
+ SQLITE_STATIC);
}
}
@@ -85699,80 +87987,83 @@ static int sqlite3Step(Vdbe *p){
int rc;
assert(p);
- if( p->iVdbeMagic!=VDBE_MAGIC_RUN ){
- /* We used to require that sqlite3_reset() be called before retrying
- ** sqlite3_step() after any error or after SQLITE_DONE. But beginning
- ** with version 3.7.0, we changed this so that sqlite3_reset() would
- ** be called automatically instead of throwing the SQLITE_MISUSE error.
- ** This "automatic-reset" change is not technically an incompatibility,
- ** since any application that receives an SQLITE_MISUSE is broken by
- ** definition.
- **
- ** Nevertheless, some published applications that were originally written
- ** for version 3.6.23 or earlier do in fact depend on SQLITE_MISUSE
- ** returns, and those were broken by the automatic-reset change. As a
- ** a work-around, the SQLITE_OMIT_AUTORESET compile-time restores the
- ** legacy behavior of returning SQLITE_MISUSE for cases where the
- ** previous sqlite3_step() returned something other than a SQLITE_LOCKED
- ** or SQLITE_BUSY error.
- */
-#ifdef SQLITE_OMIT_AUTORESET
- if( (rc = p->rc&0xff)==SQLITE_BUSY || rc==SQLITE_LOCKED ){
- sqlite3_reset((sqlite3_stmt*)p);
- }else{
- return SQLITE_MISUSE_BKPT;
- }
-#else
- sqlite3_reset((sqlite3_stmt*)p);
-#endif
- }
-
- /* Check that malloc() has not failed. If it has, return early. */
db = p->db;
- if( db->mallocFailed ){
- p->rc = SQLITE_NOMEM;
- return SQLITE_NOMEM_BKPT;
- }
+ if( p->eVdbeState!=VDBE_RUN_STATE ){
+ restart_step:
+ if( p->eVdbeState==VDBE_READY_STATE ){
+ if( p->expired ){
+ p->rc = SQLITE_SCHEMA;
+ rc = SQLITE_ERROR;
+ if( (p->prepFlags & SQLITE_PREPARE_SAVESQL)!=0 ){
+ /* If this statement was prepared using saved SQL and an
+ ** error has occurred, then return the error code in p->rc to the
+ ** caller. Set the error code in the database handle to the same
+ ** value.
+ */
+ rc = sqlite3VdbeTransferError(p);
+ }
+ goto end_of_step;
+ }
- if( p->pc<0 && p->expired ){
- p->rc = SQLITE_SCHEMA;
- rc = SQLITE_ERROR;
- if( (p->prepFlags & SQLITE_PREPARE_SAVESQL)!=0 ){
- /* If this statement was prepared using saved SQL and an
- ** error has occurred, then return the error code in p->rc to the
- ** caller. Set the error code in the database handle to the same value.
+ /* If there are no other statements currently running, then
+ ** reset the interrupt flag. This prevents a call to sqlite3_interrupt
+ ** from interrupting a statement that has not yet started.
*/
- rc = sqlite3VdbeTransferError(p);
- }
- goto end_of_step;
- }
- if( p->pc<0 ){
- /* If there are no other statements currently running, then
- ** reset the interrupt flag. This prevents a call to sqlite3_interrupt
- ** from interrupting a statement that has not yet started.
- */
- if( db->nVdbeActive==0 ){
- AtomicStore(&db->u1.isInterrupted, 0);
- }
+ if( db->nVdbeActive==0 ){
+ AtomicStore(&db->u1.isInterrupted, 0);
+ }
- assert( db->nVdbeWrite>0 || db->autoCommit==0
- || (db->nDeferredCons==0 && db->nDeferredImmCons==0)
- );
+ assert( db->nVdbeWrite>0 || db->autoCommit==0
+ || (db->nDeferredCons==0 && db->nDeferredImmCons==0)
+ );
#ifndef SQLITE_OMIT_TRACE
- if( (db->mTrace & (SQLITE_TRACE_PROFILE|SQLITE_TRACE_XPROFILE))!=0
- && !db->init.busy && p->zSql ){
- sqlite3OsCurrentTimeInt64(db->pVfs, &p->startTime);
- }else{
- assert( p->startTime==0 );
- }
+ if( (db->mTrace & (SQLITE_TRACE_PROFILE|SQLITE_TRACE_XPROFILE))!=0
+ && !db->init.busy && p->zSql ){
+ sqlite3OsCurrentTimeInt64(db->pVfs, &p->startTime);
+ }else{
+ assert( p->startTime==0 );
+ }
#endif
- db->nVdbeActive++;
- if( p->readOnly==0 ) db->nVdbeWrite++;
- if( p->bIsReader ) db->nVdbeRead++;
- p->pc = 0;
+ db->nVdbeActive++;
+ if( p->readOnly==0 ) db->nVdbeWrite++;
+ if( p->bIsReader ) db->nVdbeRead++;
+ p->pc = 0;
+ p->eVdbeState = VDBE_RUN_STATE;
+ }else
+
+ if( ALWAYS(p->eVdbeState==VDBE_HALT_STATE) ){
+ /* We used to require that sqlite3_reset() be called before retrying
+ ** sqlite3_step() after any error or after SQLITE_DONE. But beginning
+ ** with version 3.7.0, we changed this so that sqlite3_reset() would
+ ** be called automatically instead of throwing the SQLITE_MISUSE error.
+ ** This "automatic-reset" change is not technically an incompatibility,
+ ** since any application that receives an SQLITE_MISUSE is broken by
+ ** definition.
+ **
+ ** Nevertheless, some published applications that were originally written
+ ** for version 3.6.23 or earlier do in fact depend on SQLITE_MISUSE
+ ** returns, and those were broken by the automatic-reset change. As a
+ ** a work-around, the SQLITE_OMIT_AUTORESET compile-time restores the
+ ** legacy behavior of returning SQLITE_MISUSE for cases where the
+ ** previous sqlite3_step() returned something other than a SQLITE_LOCKED
+ ** or SQLITE_BUSY error.
+ */
+#ifdef SQLITE_OMIT_AUTORESET
+ if( (rc = p->rc&0xff)==SQLITE_BUSY || rc==SQLITE_LOCKED ){
+ sqlite3_reset((sqlite3_stmt*)p);
+ }else{
+ return SQLITE_MISUSE_BKPT;
+ }
+#else
+ sqlite3_reset((sqlite3_stmt*)p);
+#endif
+ assert( p->eVdbeState==VDBE_READY_STATE );
+ goto restart_step;
+ }
}
+
#ifdef SQLITE_DEBUG
p->rcApp = SQLITE_OK;
#endif
@@ -85787,7 +88078,12 @@ static int sqlite3Step(Vdbe *p){
db->nVdbeExec--;
}
- if( rc!=SQLITE_ROW ){
+ if( rc==SQLITE_ROW ){
+ assert( p->rc==SQLITE_OK );
+ assert( db->mallocFailed==0 );
+ db->errCode = SQLITE_ROW;
+ return SQLITE_ROW;
+ }else{
#ifndef SQLITE_OMIT_TRACE
/* If the statement completed successfully, invoke the profile callback */
checkProfileCallback(db, p);
@@ -85839,7 +88135,6 @@ SQLITE_API int sqlite3_step(sqlite3_stmt *pStmt){
}
db = v->db;
sqlite3_mutex_enter(db->mutex);
- v->doingRerun = 0;
while( (rc = sqlite3Step(v))==SQLITE_SCHEMA
&& cnt++ < SQLITE_MAX_SCHEMA_RETRY ){
int savedPc = v->pc;
@@ -85865,7 +88160,13 @@ SQLITE_API int sqlite3_step(sqlite3_stmt *pStmt){
break;
}
sqlite3_reset(pStmt);
- if( savedPc>=0 ) v->doingRerun = 1;
+ if( savedPc>=0 ){
+ /* Setting minWriteFileFormat to 254 is a signal to the OP_Init and
+ ** OP_Trace opcodes to *not* perform SQLITE_TRACE_STMT because it has
+ ** already been done once on a prior invocation that failed due to
+ ** SQLITE_SCHEMA. tag-20220401a */
+ v->minWriteFileFormat = 254;
+ }
assert( v->expired==0 );
}
sqlite3_mutex_leave(db->mutex);
@@ -86174,15 +88475,15 @@ static const Mem *columnNullValue(void){
#endif
= {
/* .u = */ {0},
+ /* .z = */ (char*)0,
+ /* .n = */ (int)0,
/* .flags = */ (u16)MEM_Null,
/* .enc = */ (u8)0,
/* .eSubtype = */ (u8)0,
- /* .n = */ (int)0,
- /* .z = */ (char*)0,
- /* .zMalloc = */ (char*)0,
+ /* .db = */ (sqlite3*)0,
/* .szMalloc = */ (int)0,
/* .uTemp = */ (u32)0,
- /* .db = */ (sqlite3*)0,
+ /* .zMalloc = */ (char*)0,
/* .xDel = */ (void(*)(void*))0,
#ifdef SQLITE_DEBUG
/* .pScopyFrom = */ (Mem*)0,
@@ -86473,25 +88774,24 @@ SQLITE_API const void *sqlite3_column_origin_name16(sqlite3_stmt *pStmt, int N){
** The error code stored in database p->db is overwritten with the return
** value in any case.
*/
-static int vdbeUnbind(Vdbe *p, int i){
+static int vdbeUnbind(Vdbe *p, unsigned int i){
Mem *pVar;
if( vdbeSafetyNotNull(p) ){
return SQLITE_MISUSE_BKPT;
}
sqlite3_mutex_enter(p->db->mutex);
- if( p->iVdbeMagic!=VDBE_MAGIC_RUN || p->pc>=0 ){
+ if( p->eVdbeState!=VDBE_READY_STATE ){
sqlite3Error(p->db, SQLITE_MISUSE);
sqlite3_mutex_leave(p->db->mutex);
sqlite3_log(SQLITE_MISUSE,
"bind on a busy prepared statement: [%s]", p->zSql);
return SQLITE_MISUSE_BKPT;
}
- if( i<1 || i>p->nVar ){
+ if( i>=(unsigned int)p->nVar ){
sqlite3Error(p->db, SQLITE_RANGE);
sqlite3_mutex_leave(p->db->mutex);
return SQLITE_RANGE;
}
- i--;
pVar = &p->aVar[i];
sqlite3VdbeMemRelease(pVar);
pVar->flags = MEM_Null;
@@ -86528,7 +88828,7 @@ static int bindText(
Mem *pVar;
int rc;
- rc = vdbeUnbind(p, i);
+ rc = vdbeUnbind(p, (u32)(i-1));
if( rc==SQLITE_OK ){
if( zData!=0 ){
pVar = &p->aVar[i-1];
@@ -86577,7 +88877,7 @@ SQLITE_API int sqlite3_bind_blob64(
SQLITE_API int sqlite3_bind_double(sqlite3_stmt *pStmt, int i, double rValue){
int rc;
Vdbe *p = (Vdbe *)pStmt;
- rc = vdbeUnbind(p, i);
+ rc = vdbeUnbind(p, (u32)(i-1));
if( rc==SQLITE_OK ){
sqlite3VdbeMemSetDouble(&p->aVar[i-1], rValue);
sqlite3_mutex_leave(p->db->mutex);
@@ -86590,7 +88890,7 @@ SQLITE_API int sqlite3_bind_int(sqlite3_stmt *p, int i, int iValue){
SQLITE_API int sqlite3_bind_int64(sqlite3_stmt *pStmt, int i, sqlite_int64 iValue){
int rc;
Vdbe *p = (Vdbe *)pStmt;
- rc = vdbeUnbind(p, i);
+ rc = vdbeUnbind(p, (u32)(i-1));
if( rc==SQLITE_OK ){
sqlite3VdbeMemSetInt64(&p->aVar[i-1], iValue);
sqlite3_mutex_leave(p->db->mutex);
@@ -86600,7 +88900,7 @@ SQLITE_API int sqlite3_bind_int64(sqlite3_stmt *pStmt, int i, sqlite_int64 iValu
SQLITE_API int sqlite3_bind_null(sqlite3_stmt *pStmt, int i){
int rc;
Vdbe *p = (Vdbe*)pStmt;
- rc = vdbeUnbind(p, i);
+ rc = vdbeUnbind(p, (u32)(i-1));
if( rc==SQLITE_OK ){
sqlite3_mutex_leave(p->db->mutex);
}
@@ -86615,7 +88915,7 @@ SQLITE_API int sqlite3_bind_pointer(
){
int rc;
Vdbe *p = (Vdbe*)pStmt;
- rc = vdbeUnbind(p, i);
+ rc = vdbeUnbind(p, (u32)(i-1));
if( rc==SQLITE_OK ){
sqlite3VdbeMemSetPointer(&p->aVar[i-1], pPtr, zPTtype, xDestructor);
sqlite3_mutex_leave(p->db->mutex);
@@ -86693,7 +88993,7 @@ SQLITE_API int sqlite3_bind_value(sqlite3_stmt *pStmt, int i, const sqlite3_valu
SQLITE_API int sqlite3_bind_zeroblob(sqlite3_stmt *pStmt, int i, int n){
int rc;
Vdbe *p = (Vdbe *)pStmt;
- rc = vdbeUnbind(p, i);
+ rc = vdbeUnbind(p, (u32)(i-1));
if( rc==SQLITE_OK ){
#ifndef SQLITE_OMIT_INCRBLOB
sqlite3VdbeMemSetZeroBlob(&p->aVar[i-1], n);
@@ -86832,7 +89132,7 @@ SQLITE_API int sqlite3_stmt_isexplain(sqlite3_stmt *pStmt){
*/
SQLITE_API int sqlite3_stmt_busy(sqlite3_stmt *pStmt){
Vdbe *v = (Vdbe*)pStmt;
- return v!=0 && v->iVdbeMagic==VDBE_MAGIC_RUN && v->pc>=0;
+ return v!=0 && v->eVdbeState==VDBE_RUN_STATE;
}
/*
@@ -86853,7 +89153,7 @@ SQLITE_API sqlite3_stmt *sqlite3_next_stmt(sqlite3 *pDb, sqlite3_stmt *pStmt){
if( pStmt==0 ){
pNext = (sqlite3_stmt*)pDb->pVdbe;
}else{
- pNext = (sqlite3_stmt*)((Vdbe*)pStmt)->pNext;
+ pNext = (sqlite3_stmt*)((Vdbe*)pStmt)->pVNext;
}
sqlite3_mutex_leave(pDb->mutex);
return pNext;
@@ -86878,9 +89178,11 @@ SQLITE_API int sqlite3_stmt_status(sqlite3_stmt *pStmt, int op, int resetFlag){
sqlite3_mutex_enter(db->mutex);
v = 0;
db->pnBytesFreed = (int*)&v;
- sqlite3VdbeClearObject(db, pVdbe);
- sqlite3DbFree(db, pVdbe);
+ assert( db->lookaside.pEnd==db->lookaside.pTrueEnd );
+ db->lookaside.pEnd = db->lookaside.pStart;
+ sqlite3VdbeDelete(pVdbe);
db->pnBytesFreed = 0;
+ db->lookaside.pEnd = db->lookaside.pTrueEnd;
sqlite3_mutex_leave(db->mutex);
}else{
v = pVdbe->aCounter[op];
@@ -87672,12 +89974,12 @@ static VdbeCursor *allocateCursor(
int nByte;
VdbeCursor *pCx = 0;
nByte =
- ROUND8(sizeof(VdbeCursor)) + 2*sizeof(u32)*nField +
+ ROUND8P(sizeof(VdbeCursor)) + 2*sizeof(u32)*nField +
(eCurType==CURTYPE_BTREE?sqlite3BtreeCursorSize():0);
assert( iCur>=0 && iCur<p->nCursor );
if( p->apCsr[iCur] ){ /*OPTIMIZATION-IF-FALSE*/
- sqlite3VdbeFreeCursor(p, p->apCsr[iCur]);
+ sqlite3VdbeFreeCursorNN(p, p->apCsr[iCur]);
p->apCsr[iCur] = 0;
}
@@ -87707,7 +90009,7 @@ static VdbeCursor *allocateCursor(
pCx->aOffset = &pCx->aType[nField];
if( eCurType==CURTYPE_BTREE ){
pCx->uc.pCursor = (BtCursor*)
- &pMem->z[ROUND8(sizeof(VdbeCursor))+2*sizeof(u32)*nField];
+ &pMem->z[ROUND8P(sizeof(VdbeCursor))+2*sizeof(u32)*nField];
sqlite3BtreeCursorZero(pCx->uc.pCursor);
}
return pCx;
@@ -87720,7 +90022,8 @@ static VdbeCursor *allocateCursor(
** return false.
*/
static int alsoAnInt(Mem *pRec, double rValue, i64 *piValue){
- i64 iValue = (double)rValue;
+ i64 iValue;
+ iValue = sqlite3RealToI64(rValue);
if( sqlite3RealSameAsInt(rValue,iValue) ){
*piValue = iValue;
return 1;
@@ -87882,17 +90185,18 @@ static u16 SQLITE_NOINLINE computeNumericType(Mem *pMem){
** But it does set pMem->u.r and pMem->u.i appropriately.
*/
static u16 numericType(Mem *pMem){
- if( pMem->flags & (MEM_Int|MEM_Real|MEM_IntReal) ){
+ assert( (pMem->flags & MEM_Null)==0
+ || pMem->db==0 || pMem->db->mallocFailed );
+ if( pMem->flags & (MEM_Int|MEM_Real|MEM_IntReal|MEM_Null) ){
testcase( pMem->flags & MEM_Int );
testcase( pMem->flags & MEM_Real );
testcase( pMem->flags & MEM_IntReal );
- return pMem->flags & (MEM_Int|MEM_Real|MEM_IntReal);
- }
- if( pMem->flags & (MEM_Str|MEM_Blob) ){
- testcase( pMem->flags & MEM_Str );
- testcase( pMem->flags & MEM_Blob );
- return computeNumericType(pMem);
+ return pMem->flags & (MEM_Int|MEM_Real|MEM_IntReal|MEM_Null);
}
+ assert( pMem->flags & (MEM_Str|MEM_Blob) );
+ testcase( pMem->flags & MEM_Str );
+ testcase( pMem->flags & MEM_Blob );
+ return computeNumericType(pMem);
return 0;
}
@@ -88146,7 +90450,7 @@ SQLITE_PRIVATE int sqlite3VdbeExec(
#endif
/*** INSERT STACK UNION HERE ***/
- assert( p->iVdbeMagic==VDBE_MAGIC_RUN ); /* sqlite3_step() verifies this */
+ assert( p->eVdbeState==VDBE_RUN_STATE ); /* sqlite3_step() verifies this */
sqlite3VdbeEnter(p);
#ifndef SQLITE_OMIT_PROGRESS_CALLBACK
if( db->xProgress ){
@@ -88389,26 +90693,39 @@ case OP_Gosub: { /* jump */
pIn1->flags = MEM_Int;
pIn1->u.i = (int)(pOp-aOp);
REGISTER_TRACE(pOp->p1, pIn1);
-
- /* Most jump operations do a goto to this spot in order to update
- ** the pOp pointer. */
-jump_to_p2:
- assert( pOp->p2>0 ); /* There are never any jumps to instruction 0 */
- assert( pOp->p2<p->nOp ); /* Jumps must be in range */
- pOp = &aOp[pOp->p2 - 1];
- break;
+ goto jump_to_p2_and_check_for_interrupt;
}
-/* Opcode: Return P1 * * * *
+/* Opcode: Return P1 P2 P3 * *
+**
+** Jump to the address stored in register P1. If P1 is a return address
+** register, then this accomplishes a return from a subroutine.
**
-** Jump to the next instruction after the address in register P1. After
-** the jump, register P1 becomes undefined.
+** If P3 is 1, then the jump is only taken if register P1 holds an integer
+** values, otherwise execution falls through to the next opcode, and the
+** OP_Return becomes a no-op. If P3 is 0, then register P1 must hold an
+** integer or else an assert() is raised. P3 should be set to 1 when
+** this opcode is used in combination with OP_BeginSubrtn, and set to 0
+** otherwise.
+**
+** The value in register P1 is unchanged by this opcode.
+**
+** P2 is not used by the byte-code engine. However, if P2 is positive
+** and also less than the current address, then the "EXPLAIN" output
+** formatter in the CLI will indent all opcodes from the P2 opcode up
+** to be not including the current Return. P2 should be the first opcode
+** in the subroutine from which this opcode is returning. Thus the P2
+** value is a byte-code indentation hint. See tag-20220407a in
+** wherecode.c and shell.c.
*/
case OP_Return: { /* in1 */
pIn1 = &aMem[pOp->p1];
- assert( pIn1->flags==MEM_Int );
- pOp = &aOp[pIn1->u.i];
- pIn1->flags = MEM_Undefined;
+ if( pIn1->flags & MEM_Int ){
+ if( pOp->p3 ){ VdbeBranchTaken(1, 2); }
+ pOp = &aOp[pIn1->u.i];
+ }else if( ALWAYS(pOp->p3) ){
+ VdbeBranchTaken(0, 2);
+ }
break;
}
@@ -88431,7 +90748,14 @@ case OP_InitCoroutine: { /* jump */
assert( !VdbeMemDynamic(pOut) );
pOut->u.i = pOp->p3 - 1;
pOut->flags = MEM_Int;
- if( pOp->p2 ) goto jump_to_p2;
+ if( pOp->p2==0 ) break;
+
+ /* Most jump operations do a goto to this spot in order to update
+ ** the pOp pointer. */
+jump_to_p2:
+ assert( pOp->p2>0 ); /* There are never any jumps to instruction 0 */
+ assert( pOp->p2<p->nOp ); /* Jumps must be in range */
+ pOp = &aOp[pOp->p2 - 1];
break;
}
@@ -88533,11 +90857,10 @@ case OP_Halt: {
VdbeFrame *pFrame;
int pcx;
- pcx = (int)(pOp - aOp);
#ifdef SQLITE_DEBUG
if( pOp->p2==OE_Abort ){ sqlite3VdbeAssertAbortable(p); }
#endif
- if( pOp->p1==SQLITE_OK && p->pFrame ){
+ if( p->pFrame && pOp->p1==SQLITE_OK ){
/* Halt the sub-program. Return control to the parent frame. */
pFrame = p->pFrame;
p->pFrame = pFrame->pParent;
@@ -88559,7 +90882,6 @@ case OP_Halt: {
}
p->rc = pOp->p1;
p->errorAction = (u8)pOp->p2;
- p->pc = pcx;
assert( pOp->p5<=4 );
if( p->rc ){
if( pOp->p5 ){
@@ -88576,6 +90898,7 @@ case OP_Halt: {
}else{
sqlite3VdbeError(p, "%s", pOp->p4.z);
}
+ pcx = (int)(pOp - aOp);
sqlite3_log(pOp->p1, "abort at %d in [%s]: %s", pcx, p->zSql, p->zErrMsg);
}
rc = sqlite3VdbeHalt(p);
@@ -88701,6 +91024,28 @@ case OP_String: { /* out2 */
break;
}
+/* Opcode: BeginSubrtn * P2 * * *
+** Synopsis: r[P2]=NULL
+**
+** Mark the beginning of a subroutine that can be entered in-line
+** or that can be called using OP_Gosub. The subroutine should
+** be terminated by an OP_Return instruction that has a P1 operand that
+** is the same as the P2 operand to this opcode and that has P3 set to 1.
+** If the subroutine is entered in-line, then the OP_Return will simply
+** fall through. But if the subroutine is entered using OP_Gosub, then
+** the OP_Return will jump back to the first instruction after the OP_Gosub.
+**
+** This routine works by loading a NULL into the P2 register. When the
+** return address register contains a NULL, the OP_Return instruction is
+** a no-op that simply falls through to the next instruction (assuming that
+** the OP_Return opcode has a P3 value of 1). Thus if the subroutine is
+** entered in-line, then the OP_Return will cause in-line execution to
+** continue. But if the subroutine is entered via OP_Gosub, then the
+** OP_Return will cause a return to the address following the OP_Gosub.
+**
+** This opcode is identical to OP_Null. It has a different name
+** only to make the byte code easier to read and verify.
+*/
/* Opcode: Null P1 P2 P3 * *
** Synopsis: r[P2..P3]=NULL
**
@@ -88713,6 +91058,7 @@ case OP_String: { /* out2 */
** NULL values will not compare equal even if SQLITE_NULLEQ is set on
** OP_Ne or OP_Eq.
*/
+case OP_BeginSubrtn:
case OP_Null: { /* out2 */
int cnt;
u16 nullFlag;
@@ -88843,11 +91189,16 @@ case OP_Move: {
break;
}
-/* Opcode: Copy P1 P2 P3 * *
+/* Opcode: Copy P1 P2 P3 * P5
** Synopsis: r[P2@P3+1]=r[P1@P3+1]
**
** Make a copy of registers P1..P1+P3 into registers P2..P2+P3.
**
+** If the 0x0002 bit of P5 is set then also clear the MEM_Subtype flag in the
+** destination. The 0x0001 bit of P5 indicates that this Copy opcode cannot
+** be merged. The 0x0001 bit is used by the query planner and does not
+** come into play during query execution.
+**
** This instruction makes a deep copy of the value. A duplicate
** is made of any string or blob constant. See also OP_SCopy.
*/
@@ -88862,6 +91213,9 @@ case OP_Copy: {
memAboutToChange(p, pOut);
sqlite3VdbeMemShallowCopy(pOut, pIn1, MEM_Ephem);
Deephemeralize(pOut);
+ if( (pOut->flags & MEM_Subtype)!=0 && (pOp->p5 & 0x0002)!=0 ){
+ pOut->flags &= ~MEM_Subtype;
+ }
#ifdef SQLITE_DEBUG
pOut->pScopyFrom = 0;
#endif
@@ -88942,45 +91296,32 @@ case OP_FkCheck: {
** the result row.
*/
case OP_ResultRow: {
- Mem *pMem;
- int i;
assert( p->nResColumn==pOp->p2 );
assert( pOp->p1>0 || CORRUPT_DB );
assert( pOp->p1+pOp->p2<=(p->nMem+1 - p->nCursor)+1 );
- /* Invalidate all ephemeral cursor row caches */
p->cacheCtr = (p->cacheCtr + 2)|1;
-
- /* Make sure the results of the current row are \000 terminated
- ** and have an assigned type. The results are de-ephemeralized as
- ** a side effect.
- */
- pMem = p->pResultSet = &aMem[pOp->p1];
- for(i=0; i<pOp->p2; i++){
- assert( memIsValid(&pMem[i]) );
- Deephemeralize(&pMem[i]);
- assert( (pMem[i].flags & MEM_Ephem)==0
- || (pMem[i].flags & (MEM_Str|MEM_Blob))==0 );
- sqlite3VdbeMemNulTerminate(&pMem[i]);
- REGISTER_TRACE(pOp->p1+i, &pMem[i]);
+ p->pResultSet = &aMem[pOp->p1];
#ifdef SQLITE_DEBUG
- /* The registers in the result will not be used again when the
- ** prepared statement restarts. This is because sqlite3_column()
- ** APIs might have caused type conversions of made other changes to
- ** the register values. Therefore, we can go ahead and break any
- ** OP_SCopy dependencies. */
- pMem[i].pScopyFrom = 0;
-#endif
+ {
+ Mem *pMem = p->pResultSet;
+ int i;
+ for(i=0; i<pOp->p2; i++){
+ assert( memIsValid(&pMem[i]) );
+ REGISTER_TRACE(pOp->p1+i, &pMem[i]);
+ /* The registers in the result will not be used again when the
+ ** prepared statement restarts. This is because sqlite3_column()
+ ** APIs might have caused type conversions of made other changes to
+ ** the register values. Therefore, we can go ahead and break any
+ ** OP_SCopy dependencies. */
+ pMem[i].pScopyFrom = 0;
+ }
}
+#endif
if( db->mallocFailed ) goto no_mem;
-
if( db->mTrace & SQLITE_TRACE_ROW ){
db->trace.xV2(SQLITE_TRACE_ROW, db->pTraceArg, p, 0);
}
-
-
- /* Return SQLITE_ROW
- */
p->pc = (int)(pOp - aOp) + 1;
rc = SQLITE_ROW;
goto vdbe_return;
@@ -89035,7 +91376,7 @@ case OP_Concat: { /* same as TK_CONCAT, in1, in2, out3 */
if( nByte>db->aLimit[SQLITE_LIMIT_LENGTH] ){
goto too_big;
}
- if( sqlite3VdbeMemGrow(pOut, (int)nByte+3, pOut==pIn2) ){
+ if( sqlite3VdbeMemGrow(pOut, (int)nByte+2, pOut==pIn2) ){
goto no_mem;
}
MemSetTypeFlag(pOut, MEM_Str);
@@ -89047,9 +91388,9 @@ case OP_Concat: { /* same as TK_CONCAT, in1, in2, out3 */
memcpy(&pOut->z[pIn2->n], pIn1->z, pIn1->n);
assert( (pIn1->flags & MEM_Dyn) == (flags1 & MEM_Dyn) );
pIn1->flags = flags1;
+ if( encoding>SQLITE_UTF8 ) nByte &= ~1;
pOut->z[nByte]=0;
pOut->z[nByte+1] = 0;
- pOut->z[nByte+2] = 0;
pOut->flags |= MEM_Term;
pOut->n = (int)nByte;
pOut->enc = encoding;
@@ -89100,7 +91441,6 @@ case OP_Subtract: /* same as TK_MINUS, in1, in2, out3 */
case OP_Multiply: /* same as TK_STAR, in1, in2, out3 */
case OP_Divide: /* same as TK_SLASH, in1, in2, out3 */
case OP_Remainder: { /* same as TK_REM, in1, in2, out3 */
- u16 flags; /* Combined MEM_* flags from both inputs */
u16 type1; /* Numeric type of left operand */
u16 type2; /* Numeric type of right operand */
i64 iA; /* Integer value of left operand */
@@ -89109,12 +91449,12 @@ case OP_Remainder: { /* same as TK_REM, in1, in2, out3 */
double rB; /* Real value of right operand */
pIn1 = &aMem[pOp->p1];
- type1 = numericType(pIn1);
+ type1 = pIn1->flags;
pIn2 = &aMem[pOp->p2];
- type2 = numericType(pIn2);
+ type2 = pIn2->flags;
pOut = &aMem[pOp->p3];
- flags = pIn1->flags | pIn2->flags;
if( (type1 & type2 & MEM_Int)!=0 ){
+int_math:
iA = pIn1->u.i;
iB = pIn2->u.i;
switch( pOp->opcode ){
@@ -89136,9 +91476,12 @@ case OP_Remainder: { /* same as TK_REM, in1, in2, out3 */
}
pOut->u.i = iB;
MemSetTypeFlag(pOut, MEM_Int);
- }else if( (flags & MEM_Null)!=0 ){
+ }else if( ((type1 | type2) & MEM_Null)!=0 ){
goto arithmetic_result_is_null;
}else{
+ type1 = numericType(pIn1);
+ type2 = numericType(pIn2);
+ if( (type1 & type2 & MEM_Int)!=0 ) goto int_math;
fp_math:
rA = sqlite3VdbeRealValue(pIn1);
rB = sqlite3VdbeRealValue(pIn2);
@@ -89494,23 +91837,23 @@ case OP_Ge: { /* same as TK_GE, jump, in1, in3 */
assert( (pOp->p5 & SQLITE_AFF_MASK)!=SQLITE_AFF_TEXT || CORRUPT_DB );
/* Common case of comparison of two integers */
if( pIn3->u.i > pIn1->u.i ){
- iCompare = +1;
if( sqlite3aGTb[pOp->opcode] ){
VdbeBranchTaken(1, (pOp->p5 & SQLITE_NULLEQ)?2:3);
goto jump_to_p2;
}
+ iCompare = +1;
}else if( pIn3->u.i < pIn1->u.i ){
- iCompare = -1;
if( sqlite3aLTb[pOp->opcode] ){
VdbeBranchTaken(1, (pOp->p5 & SQLITE_NULLEQ)?2:3);
goto jump_to_p2;
}
+ iCompare = -1;
}else{
- iCompare = 0;
if( sqlite3aEQb[pOp->opcode] ){
VdbeBranchTaken(1, (pOp->p5 & SQLITE_NULLEQ)?2:3);
goto jump_to_p2;
}
+ iCompare = 0;
}
VdbeBranchTaken(0, (pOp->p5 & SQLITE_NULLEQ)?2:3);
break;
@@ -89537,11 +91880,11 @@ case OP_Ge: { /* same as TK_GE, jump, in1, in3 */
** then the result is always NULL.
** The jump is taken if the SQLITE_JUMPIFNULL bit is set.
*/
- iCompare = 1; /* Operands are not equal */
VdbeBranchTaken(2,3);
if( pOp->p5 & SQLITE_JUMPIFNULL ){
goto jump_to_p2;
}
+ iCompare = 1; /* Operands are not equal */
break;
}
}else{
@@ -89647,9 +91990,8 @@ case OP_ElseEq: { /* same as TK_ESCAPE, jump */
** Set the permutation used by the OP_Compare operator in the next
** instruction. The permutation is stored in the P4 operand.
**
-** The permutation is only valid until the next OP_Compare that has
-** the OPFLAG_PERMUTE bit set in P5. Typically the OP_Permutation should
-** occur immediately prior to the OP_Compare.
+** The permutation is only valid for the next opcode which must be
+** an OP_Compare that has the OPFLAG_PERMUTE bit set in P5.
**
** The first integer in the P4 integer array is the length of the array
** and does not become part of the permutation.
@@ -89681,6 +92023,8 @@ case OP_Permutation: {
** The comparison is a sort comparison, so NULLs compare equal,
** NULLs are less than numbers, numbers are less than strings,
** and strings are less than blobs.
+**
+** This opcode must be immediately followed by an OP_Jump opcode.
*/
case OP_Compare: {
int n;
@@ -89739,6 +92083,7 @@ case OP_Compare: {
break;
}
}
+ assert( pOp[1].opcode==OP_Jump );
break;
}
@@ -89747,8 +92092,11 @@ case OP_Compare: {
** Jump to the instruction at address P1, P2, or P3 depending on whether
** in the most recent OP_Compare instruction the P1 vector was less than
** equal to, or greater than the P2 vector, respectively.
+**
+** This opcode must immediately follow an OP_Compare opcode.
*/
case OP_Jump: { /* jump */
+ assert( pOp>aOp && pOp[-1].opcode==OP_Compare );
if( iCompare<0 ){
VdbeBranchTaken(0,4); pOp = &aOp[pOp->p1 - 1];
}else if( iCompare==0 ){
@@ -89948,19 +92296,90 @@ case OP_IsNull: { /* same as TK_ISNULL, jump, in1 */
break;
}
-/* Opcode: IsNullOrType P1 P2 P3 * *
-** Synopsis: if typeof(r[P1]) IN (P3,5) goto P2
+/* Opcode: IsType P1 P2 P3 P4 P5
+** Synopsis: if typeof(P1.P3) in P5 goto P2
+**
+** Jump to P2 if the type of a column in a btree is one of the types specified
+** by the P5 bitmask.
+**
+** P1 is normally a cursor on a btree for which the row decode cache is
+** valid through at least column P3. In other words, there should have been
+** a prior OP_Column for column P3 or greater. If the cursor is not valid,
+** then this opcode might give spurious results.
+** The the btree row has fewer than P3 columns, then use P4 as the
+** datatype.
+**
+** If P1 is -1, then P3 is a register number and the datatype is taken
+** from the value in that register.
+**
+** P5 is a bitmask of data types. SQLITE_INTEGER is the least significant
+** (0x01) bit. SQLITE_FLOAT is the 0x02 bit. SQLITE_TEXT is 0x04.
+** SQLITE_BLOB is 0x08. SQLITE_NULL is 0x10.
+**
+** Take the jump to address P2 if and only if the datatype of the
+** value determined by P1 and P3 corresponds to one of the bits in the
+** P5 bitmask.
**
-** Jump to P2 if the value in register P1 is NULL or has a datatype P3.
-** P3 is an integer which should be one of SQLITE_INTEGER, SQLITE_FLOAT,
-** SQLITE_BLOB, SQLITE_NULL, or SQLITE_TEXT.
*/
-case OP_IsNullOrType: { /* jump, in1 */
- int doTheJump;
- pIn1 = &aMem[pOp->p1];
- doTheJump = (pIn1->flags & MEM_Null)!=0 || sqlite3_value_type(pIn1)==pOp->p3;
- VdbeBranchTaken( doTheJump, 2);
- if( doTheJump ) goto jump_to_p2;
+case OP_IsType: { /* jump */
+ VdbeCursor *pC;
+ u16 typeMask;
+ u32 serialType;
+
+ assert( pOp->p1>=(-1) && pOp->p1<p->nCursor );
+ assert( pOp->p1>=0 || (pOp->p3>=0 && pOp->p3<=(p->nMem+1 - p->nCursor)) );
+ if( pOp->p1>=0 ){
+ pC = p->apCsr[pOp->p1];
+ assert( pC!=0 );
+ assert( pOp->p3>=0 );
+ if( pOp->p3<pC->nHdrParsed ){
+ serialType = pC->aType[pOp->p3];
+ if( serialType>=12 ){
+ if( serialType&1 ){
+ typeMask = 0x04; /* SQLITE_TEXT */
+ }else{
+ typeMask = 0x08; /* SQLITE_BLOB */
+ }
+ }else{
+ static const unsigned char aMask[] = {
+ 0x10, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x2,
+ 0x01, 0x01, 0x10, 0x10
+ };
+ testcase( serialType==0 );
+ testcase( serialType==1 );
+ testcase( serialType==2 );
+ testcase( serialType==3 );
+ testcase( serialType==4 );
+ testcase( serialType==5 );
+ testcase( serialType==6 );
+ testcase( serialType==7 );
+ testcase( serialType==8 );
+ testcase( serialType==9 );
+ testcase( serialType==10 );
+ testcase( serialType==11 );
+ typeMask = aMask[serialType];
+ }
+ }else{
+ typeMask = 1 << (pOp->p4.i - 1);
+ testcase( typeMask==0x01 );
+ testcase( typeMask==0x02 );
+ testcase( typeMask==0x04 );
+ testcase( typeMask==0x08 );
+ testcase( typeMask==0x10 );
+ }
+ }else{
+ assert( memIsValid(&aMem[pOp->p3]) );
+ typeMask = 1 << (sqlite3_value_type((sqlite3_value*)&aMem[pOp->p3])-1);
+ testcase( typeMask==0x01 );
+ testcase( typeMask==0x02 );
+ testcase( typeMask==0x04 );
+ testcase( typeMask==0x08 );
+ testcase( typeMask==0x10 );
+ }
+ VdbeBranchTaken( (typeMask & pOp->p5)!=0, 2);
+ if( typeMask & pOp->p5 ){
+ goto jump_to_p2;
+ }
break;
}
@@ -90003,11 +92422,14 @@ case OP_NotNull: { /* same as TK_NOTNULL, jump, in1 */
** If it is, then set register P3 to NULL and jump immediately to P2.
** If P1 is not on a NULL row, then fall through without making any
** changes.
+**
+** If P1 is not an open cursor, then this opcode is a no-op.
*/
case OP_IfNullRow: { /* jump */
+ VdbeCursor *pC;
assert( pOp->p1>=0 && pOp->p1<p->nCursor );
- assert( p->apCsr[pOp->p1]!=0 );
- if( p->apCsr[pOp->p1]->nullRow ){
+ pC = p->apCsr[pOp->p1];
+ if( ALWAYS(pC) && pC->nullRow ){
sqlite3VdbeMemSetNull(aMem + pOp->p3);
goto jump_to_p2;
}
@@ -90053,12 +92475,12 @@ case OP_Offset: { /* out3 */
#endif /* SQLITE_ENABLE_OFFSET_SQL_FUNC */
/* Opcode: Column P1 P2 P3 P4 P5
-** Synopsis: r[P3]=PX
+** Synopsis: r[P3]=PX cursor P1 column P2
**
** Interpret the data that cursor P1 points to as a structure built using
** the MakeRecord instruction. (See the MakeRecord opcode for additional
** information about the format of the data.) Extract the P2-th column
-** from this record. If there are less that (P2+1)
+** from this record. If there are less than (P2+1)
** values in the record, extract a NULL.
**
** The value extracted is stored in register P3.
@@ -90067,15 +92489,17 @@ case OP_Offset: { /* out3 */
** if the P4 argument is a P4_MEM use the value of the P4 argument as
** the result.
**
-** If the OPFLAG_LENGTHARG and OPFLAG_TYPEOFARG bits are set on P5 then
-** the result is guaranteed to only be used as the argument of a length()
-** or typeof() function, respectively. The loading of large blobs can be
-** skipped for length() and all content loading can be skipped for typeof().
+** If the OPFLAG_LENGTHARG bit is set in P5 then the result is guaranteed
+** to only be used by the length() function or the equivalent. The content
+** of large blobs is not loaded, thus saving CPU cycles. If the
+** OPFLAG_TYPEOFARG bit is set then the result will only be used by the
+** typeof() function or the IS NULL or IS NOT NULL operators or the
+** equivalent. In this case, all content loading can be omitted.
*/
case OP_Column: {
u32 p2; /* column number to retrieve */
VdbeCursor *pC; /* The VDBE cursor */
- BtCursor *pCrsr; /* The BTree cursor */
+ BtCursor *pCrsr; /* The B-Tree cursor corresponding to pC */
u32 *aOffset; /* aOffset[i] is offset to start of data for i-th column */
int len; /* The length of the serialized data for the column */
int i; /* Loop counter */
@@ -90089,21 +92513,14 @@ case OP_Column: {
Mem *pReg; /* PseudoTable input register */
assert( pOp->p1>=0 && pOp->p1<p->nCursor );
+ assert( pOp->p3>0 && pOp->p3<=(p->nMem+1 - p->nCursor) );
pC = p->apCsr[pOp->p1];
- assert( pC!=0 );
p2 = (u32)pOp->p2;
- /* If the cursor cache is stale (meaning it is not currently point at
- ** the correct row) then bring it up-to-date by doing the necessary
- ** B-Tree seek. */
- rc = sqlite3VdbeCursorMoveto(&pC, &p2);
- if( rc ) goto abort_due_to_error;
-
- assert( pOp->p3>0 && pOp->p3<=(p->nMem+1 - p->nCursor) );
- pDest = &aMem[pOp->p3];
- memAboutToChange(p, pDest);
+op_column_restart:
assert( pC!=0 );
- assert( p2<(u32)pC->nField );
+ assert( p2<(u32)pC->nField
+ || (pC->eCurType==CURTYPE_PSEUDO && pC->seekResult==0) );
aOffset = pC->aOffset;
assert( aOffset==pC->aType+pC->nField );
assert( pC->eCurType!=CURTYPE_VTAB );
@@ -90112,21 +92529,37 @@ case OP_Column: {
if( pC->cacheStatus!=p->cacheCtr ){ /*OPTIMIZATION-IF-FALSE*/
if( pC->nullRow ){
- if( pC->eCurType==CURTYPE_PSEUDO ){
+ if( pC->eCurType==CURTYPE_PSEUDO && pC->seekResult>0 ){
/* For the special case of as pseudo-cursor, the seekResult field
** identifies the register that holds the record */
- assert( pC->seekResult>0 );
pReg = &aMem[pC->seekResult];
assert( pReg->flags & MEM_Blob );
assert( memIsValid(pReg) );
pC->payloadSize = pC->szRow = pReg->n;
pC->aRow = (u8*)pReg->z;
}else{
+ pDest = &aMem[pOp->p3];
+ memAboutToChange(p, pDest);
sqlite3VdbeMemSetNull(pDest);
goto op_column_out;
}
}else{
pCrsr = pC->uc.pCursor;
+ if( pC->deferredMoveto ){
+ u32 iMap;
+ assert( !pC->isEphemeral );
+ if( pC->ub.aAltMap && (iMap = pC->ub.aAltMap[1+p2])>0 ){
+ pC = pC->pAltCursor;
+ p2 = iMap - 1;
+ goto op_column_restart;
+ }
+ rc = sqlite3VdbeFinishMoveto(pC);
+ if( rc ) goto abort_due_to_error;
+ }else if( sqlite3BtreeCursorHasMoved(pCrsr) ){
+ rc = sqlite3VdbeHandleMovedCursor(pC);
+ if( rc ) goto abort_due_to_error;
+ goto op_column_restart;
+ }
assert( pC->eCurType==CURTYPE_BTREE );
assert( pCrsr );
assert( sqlite3BtreeCursorIsValid(pCrsr) );
@@ -90134,15 +92567,15 @@ case OP_Column: {
pC->aRow = sqlite3BtreePayloadFetch(pCrsr, &pC->szRow);
assert( pC->szRow<=pC->payloadSize );
assert( pC->szRow<=65536 ); /* Maximum page size is 64KiB */
- if( pC->payloadSize > (u32)db->aLimit[SQLITE_LIMIT_LENGTH] ){
- goto too_big;
- }
}
pC->cacheStatus = p->cacheCtr;
- pC->iHdrOffset = getVarint32(pC->aRow, aOffset[0]);
+ if( (aOffset[0] = pC->aRow[0])<0x80 ){
+ pC->iHdrOffset = 1;
+ }else{
+ pC->iHdrOffset = sqlite3GetVarint32(pC->aRow, aOffset);
+ }
pC->nHdrParsed = 0;
-
if( pC->szRow<aOffset[0] ){ /*OPTIMIZATION-IF-FALSE*/
/* pC->aRow does not have to hold the entire row, but it does at least
** need to cover the header of the record. If pC->aRow does not contain
@@ -90182,6 +92615,10 @@ case OP_Column: {
testcase( aOffset[0]==0 );
goto op_column_read_header;
}
+ }else if( sqlite3BtreeCursorHasMoved(pC->uc.pCursor) ){
+ rc = sqlite3VdbeHandleMovedCursor(pC);
+ if( rc ) goto abort_due_to_error;
+ goto op_column_restart;
}
/* Make sure at least the first p2+1 entries of the header have been
@@ -90250,6 +92687,8 @@ case OP_Column: {
** columns. So the result will be either the default value or a NULL.
*/
if( pC->nHdrParsed<=p2 ){
+ pDest = &aMem[pOp->p3];
+ memAboutToChange(p, pDest);
if( pOp->p4type==P4_MEM ){
sqlite3VdbeMemShallowCopy(pDest, pOp->p4.pMem, MEM_Static);
}else{
@@ -90267,6 +92706,8 @@ case OP_Column: {
*/
assert( p2<pC->nHdrParsed );
assert( rc==SQLITE_OK );
+ pDest = &aMem[pOp->p3];
+ memAboutToChange(p, pDest);
assert( sqlite3VdbeCheckMemInvariants(pDest) );
if( VdbeMemDynamic(pDest) ){
sqlite3VdbeMemSetNull(pDest);
@@ -90287,6 +92728,7 @@ case OP_Column: {
pDest->n = len = (t-12)/2;
pDest->enc = encoding;
if( pDest->szMalloc < len+2 ){
+ if( len>db->aLimit[SQLITE_LIMIT_LENGTH] ) goto too_big;
pDest->flags = MEM_Null;
if( sqlite3VdbeMemGrow(pDest, len+2, 0) ) goto no_mem;
}else{
@@ -90319,6 +92761,7 @@ case OP_Column: {
*/
sqlite3VdbeSerialGet((u8*)sqlite3CtypeMap, t, pDest);
}else{
+ if( len>db->aLimit[SQLITE_LIMIT_LENGTH] ) goto too_big;
rc = sqlite3VdbeMemFromBtree(pC->uc.pCursor, aOffset[p2], len, pDest);
if( rc!=SQLITE_OK ) goto abort_due_to_error;
sqlite3VdbeSerialGet((const u8*)pDest->z, t, pDest);
@@ -90531,7 +92974,6 @@ case OP_MakeRecord: {
Mem *pLast; /* Last field of the record */
int nField; /* Number of fields in the record */
char *zAffinity; /* The affinity string for the record */
- int file_format; /* File format to use for encoding */
u32 len; /* Length of a field */
u8 *zHdr; /* Where to write next byte of the header */
u8 *zPayload; /* Where to write next byte of the payload */
@@ -90560,7 +93002,6 @@ case OP_MakeRecord: {
pData0 = &aMem[nField];
nField = pOp->p2;
pLast = &pData0[nField-1];
- file_format = p->minWriteFileFormat;
/* Identify the output register */
assert( pOp->p3<pOp->p1 || pOp->p3>=pOp->p1+pOp->p2 );
@@ -90662,7 +93103,7 @@ case OP_MakeRecord: {
testcase( uu==2147483647 ); testcase( uu==2147483648LL );
testcase( uu==140737488355327LL ); testcase( uu==140737488355328LL );
if( uu<=127 ){
- if( (i&1)==i && file_format>=4 ){
+ if( (i&1)==i && p->minWriteFileFormat>=4 ){
pRec->uTemp = 8+(u32)uu;
}else{
nData++;
@@ -90767,18 +93208,60 @@ case OP_MakeRecord: {
zPayload = zHdr + nHdr;
/* Write the record */
- zHdr += putVarint32(zHdr, nHdr);
+ if( nHdr<0x80 ){
+ *(zHdr++) = nHdr;
+ }else{
+ zHdr += sqlite3PutVarint(zHdr,nHdr);
+ }
assert( pData0<=pLast );
pRec = pData0;
- do{
+ while( 1 /*exit-by-break*/ ){
serial_type = pRec->uTemp;
/* EVIDENCE-OF: R-06529-47362 Following the size varint are one or more
- ** additional varints, one per column. */
- zHdr += putVarint32(zHdr, serial_type); /* serial type */
- /* EVIDENCE-OF: R-64536-51728 The values for each column in the record
+ ** additional varints, one per column.
+ ** EVIDENCE-OF: R-64536-51728 The values for each column in the record
** immediately follow the header. */
- zPayload += sqlite3VdbeSerialPut(zPayload, pRec, serial_type); /* content */
- }while( (++pRec)<=pLast );
+ if( serial_type<=7 ){
+ *(zHdr++) = serial_type;
+ if( serial_type==0 ){
+ /* NULL value. No change in zPayload */
+ }else{
+ u64 v;
+ u32 i;
+ if( serial_type==7 ){
+ assert( sizeof(v)==sizeof(pRec->u.r) );
+ memcpy(&v, &pRec->u.r, sizeof(v));
+ swapMixedEndianFloat(v);
+ }else{
+ v = pRec->u.i;
+ }
+ len = i = sqlite3SmallTypeSizes[serial_type];
+ assert( i>0 );
+ while( 1 /*exit-by-break*/ ){
+ zPayload[--i] = (u8)(v&0xFF);
+ if( i==0 ) break;
+ v >>= 8;
+ }
+ zPayload += len;
+ }
+ }else if( serial_type<0x80 ){
+ *(zHdr++) = serial_type;
+ if( serial_type>=14 && pRec->n>0 ){
+ assert( pRec->z!=0 );
+ memcpy(zPayload, pRec->z, pRec->n);
+ zPayload += pRec->n;
+ }
+ }else{
+ zHdr += sqlite3PutVarint(zHdr, serial_type);
+ if( pRec->n ){
+ assert( pRec->z!=0 );
+ memcpy(zPayload, pRec->z, pRec->n);
+ zPayload += pRec->n;
+ }
+ }
+ if( pRec==pLast ) break;
+ pRec++;
+ }
assert( nHdr==(int)(zHdr - (u8*)pOut->z) );
assert( nByte==(int)(zPayload - (u8*)pOut->z) );
@@ -90997,7 +93480,10 @@ case OP_Savepoint: {
}
}
if( rc ) goto abort_due_to_error;
-
+ if( p->eVdbeState==VDBE_HALT_STATE ){
+ rc = SQLITE_DONE;
+ goto vdbe_return;
+ }
break;
}
@@ -91101,6 +93587,7 @@ case OP_AutoCommit: {
*/
case OP_Transaction: {
Btree *pBt;
+ Db *pDb;
int iMeta = 0;
assert( p->bIsReader );
@@ -91120,7 +93607,8 @@ case OP_Transaction: {
}
goto abort_due_to_error;
}
- pBt = db->aDb[pOp->p1].pBt;
+ pDb = &db->aDb[pOp->p1];
+ pBt = pDb->pBt;
if( pBt ){
rc = sqlite3BtreeBeginTrans(pBt, pOp->p2, &iMeta);
@@ -91161,8 +93649,7 @@ case OP_Transaction: {
assert( pOp->p5==0 || pOp->p4type==P4_INT32 );
if( rc==SQLITE_OK
&& pOp->p5
- && (iMeta!=pOp->p3
- || db->aDb[pOp->p1].pSchema->iGeneration!=pOp->p4.i)
+ && (iMeta!=pOp->p3 || pDb->pSchema->iGeneration!=pOp->p4.i)
){
/*
** IMPLEMENTATION-OF: R-03189-51135 As each SQL statement runs, the schema
@@ -91189,6 +93676,11 @@ case OP_Transaction: {
}
p->expired = 1;
rc = SQLITE_SCHEMA;
+
+ /* Set changeCntOn to 0 to prevent the value returned by sqlite3_changes()
+ ** from being modified in sqlite3VdbeHalt(). If this statement is
+ ** reprepared, changeCntOn will be set again. */
+ p->changeCntOn = 0;
}
if( rc ) goto abort_due_to_error;
break;
@@ -91255,7 +93747,7 @@ case OP_SetCookie: {
rc = sqlite3BtreeUpdateMeta(pDb->pBt, pOp->p2, pOp->p3);
if( pOp->p2==BTREE_SCHEMA_VERSION ){
/* When the schema cookie changes, record the new cookie internally */
- pDb->pSchema->schema_cookie = pOp->p3 - pOp->p5;
+ *(u32*)&pDb->pSchema->schema_cookie = *(u32*)&pOp->p3 - pOp->p5;
db->mDbFlags |= DBFLAG_SchemaChange;
sqlite3FkClearTriggerCache(db, pOp->p1);
}else if( pOp->p2==BTREE_FILE_FORMAT ){
@@ -91488,8 +93980,8 @@ case OP_OpenDup: {
pCx->pgnoRoot = pOrig->pgnoRoot;
pCx->isOrdered = pOrig->isOrdered;
pCx->ub.pBtx = pOrig->ub.pBtx;
- pCx->hasBeenDuped = 1;
- pOrig->hasBeenDuped = 1;
+ pCx->noReuse = 1;
+ pOrig->noReuse = 1;
rc = sqlite3BtreeCursor(pCx->ub.pBtx, pCx->pgnoRoot, BTREE_WRCSR,
pCx->pKeyInfo, pCx->uc.pCursor);
/* The sqlite3BtreeCursor() routine can only fail for the first cursor
@@ -91556,7 +94048,7 @@ case OP_OpenEphemeral: {
aMem[pOp->p3].z = "";
}
pCx = p->apCsr[pOp->p1];
- if( pCx && !pCx->hasBeenDuped && ALWAYS(pOp->p2<=pCx->nField) ){
+ if( pCx && !pCx->noReuse && ALWAYS(pOp->p2<=pCx->nField) ){
/* If the ephermeral table is already open and has no duplicates from
** OP_OpenDup, then erase all existing content so that the table is
** empty again, rather than creating a new table. */
@@ -91941,7 +94433,13 @@ case OP_SeekGT: { /* jump, in3, group */
r.aMem = &aMem[pOp->p3];
#ifdef SQLITE_DEBUG
- { int i; for(i=0; i<r.nField; i++) assert( memIsValid(&r.aMem[i]) ); }
+ {
+ int i;
+ for(i=0; i<r.nField; i++){
+ assert( memIsValid(&r.aMem[i]) );
+ if( i>0 ) REGISTER_TRACE(pOp->p3+i, &r.aMem[i]);
+ }
+ }
#endif
r.eqSeen = 0;
rc = sqlite3BtreeIndexMoveto(pC->uc.pCursor, &r, &res);
@@ -92004,7 +94502,7 @@ seek_not_found:
}
-/* Opcode: SeekScan P1 P2 * * *
+/* Opcode: SeekScan P1 P2 * * P5
** Synopsis: Scan-ahead up to P1 rows
**
** This opcode is a prefix opcode to OP_SeekGE. In other words, this
@@ -92014,8 +94512,8 @@ seek_not_found:
** This opcode uses the P1 through P4 operands of the subsequent
** OP_SeekGE. In the text that follows, the operands of the subsequent
** OP_SeekGE opcode are denoted as SeekOP.P1 through SeekOP.P4. Only
-** the P1 and P2 operands of this opcode are also used, and are called
-** This.P1 and This.P2.
+** the P1, P2 and P5 operands of this opcode are also used, and are called
+** This.P1, This.P2 and This.P5.
**
** This opcode helps to optimize IN operators on a multi-column index
** where the IN operator is on the later terms of the index by avoiding
@@ -92025,29 +94523,51 @@ seek_not_found:
**
** The SeekGE.P3 and SeekGE.P4 operands identify an unpacked key which
** is the desired entry that we want the cursor SeekGE.P1 to be pointing
-** to. Call this SeekGE.P4/P5 row the "target".
+** to. Call this SeekGE.P3/P4 row the "target".
**
** If the SeekGE.P1 cursor is not currently pointing to a valid row,
** then this opcode is a no-op and control passes through into the OP_SeekGE.
**
** If the SeekGE.P1 cursor is pointing to a valid row, then that row
** might be the target row, or it might be near and slightly before the
-** target row. This opcode attempts to position the cursor on the target
-** row by, perhaps by invoking sqlite3BtreeStep() on the cursor
-** between 0 and This.P1 times.
-**
-** There are three possible outcomes from this opcode:<ol>
-**
-** <li> If after This.P1 steps, the cursor is still pointing to a place that
-** is earlier in the btree than the target row, then fall through
-** into the subsquence OP_SeekGE opcode.
-**
-** <li> If the cursor is successfully moved to the target row by 0 or more
-** sqlite3BtreeNext() calls, then jump to This.P2, which will land just
-** past the OP_IdxGT or OP_IdxGE opcode that follows the OP_SeekGE.
-**
-** <li> If the cursor ends up past the target row (indicating the the target
-** row does not exist in the btree) then jump to SeekOP.P2.
+** target row, or it might be after the target row. If the cursor is
+** currently before the target row, then this opcode attempts to position
+** the cursor on or after the target row by invoking sqlite3BtreeStep()
+** on the cursor between 1 and This.P1 times.
+**
+** The This.P5 parameter is a flag that indicates what to do if the
+** cursor ends up pointing at a valid row that is past the target
+** row. If This.P5 is false (0) then a jump is made to SeekGE.P2. If
+** This.P5 is true (non-zero) then a jump is made to This.P2. The P5==0
+** case occurs when there are no inequality constraints to the right of
+** the IN constraing. The jump to SeekGE.P2 ends the loop. The P5!=0 case
+** occurs when there are inequality constraints to the right of the IN
+** operator. In that case, the This.P2 will point either directly to or
+** to setup code prior to the OP_IdxGT or OP_IdxGE opcode that checks for
+** loop terminate.
+**
+** Possible outcomes from this opcode:<ol>
+**
+** <li> If the cursor is initally not pointed to any valid row, then
+** fall through into the subsequent OP_SeekGE opcode.
+**
+** <li> If the cursor is left pointing to a row that is before the target
+** row, even after making as many as This.P1 calls to
+** sqlite3BtreeNext(), then also fall through into OP_SeekGE.
+**
+** <li> If the cursor is left pointing at the target row, either because it
+** was at the target row to begin with or because one or more
+** sqlite3BtreeNext() calls moved the cursor to the target row,
+** then jump to This.P2..,
+**
+** <li> If the cursor started out before the target row and a call to
+** to sqlite3BtreeNext() moved the cursor off the end of the index
+** (indicating that the target row definitely does not exist in the
+** btree) then jump to SeekGE.P2, ending the loop.
+**
+** <li> If the cursor ends up on a valid row that is past the target row
+** (indicating that the target row does not exist in the btree) then
+** jump to SeekOP.P2 if This.P5==0 or to This.P2 if This.P5>0.
** </ol>
*/
case OP_SeekScan: {
@@ -92058,14 +94578,25 @@ case OP_SeekScan: {
assert( pOp[1].opcode==OP_SeekGE );
- /* pOp->p2 points to the first instruction past the OP_IdxGT that
- ** follows the OP_SeekGE. */
+ /* If pOp->p5 is clear, then pOp->p2 points to the first instruction past the
+ ** OP_IdxGT that follows the OP_SeekGE. Otherwise, it points to the first
+ ** opcode past the OP_SeekGE itself. */
assert( pOp->p2>=(int)(pOp-aOp)+2 );
- assert( aOp[pOp->p2-1].opcode==OP_IdxGT || aOp[pOp->p2-1].opcode==OP_IdxGE );
- testcase( aOp[pOp->p2-1].opcode==OP_IdxGE );
- assert( pOp[1].p1==aOp[pOp->p2-1].p1 );
- assert( pOp[1].p2==aOp[pOp->p2-1].p2 );
- assert( pOp[1].p3==aOp[pOp->p2-1].p3 );
+#ifdef SQLITE_DEBUG
+ if( pOp->p5==0 ){
+ /* There are no inequality constraints following the IN constraint. */
+ assert( pOp[1].p1==aOp[pOp->p2-1].p1 );
+ assert( pOp[1].p2==aOp[pOp->p2-1].p2 );
+ assert( pOp[1].p3==aOp[pOp->p2-1].p3 );
+ assert( aOp[pOp->p2-1].opcode==OP_IdxGT
+ || aOp[pOp->p2-1].opcode==OP_IdxGE );
+ testcase( aOp[pOp->p2-1].opcode==OP_IdxGE );
+ }else{
+ /* There are inequality constraints. */
+ assert( pOp->p2==(int)(pOp-aOp)+2 );
+ assert( aOp[pOp->p2-1].opcode==OP_SeekGE );
+ }
+#endif
assert( pOp->p1>0 );
pC = p->apCsr[pOp[1].p1];
@@ -92099,8 +94630,9 @@ case OP_SeekScan: {
while(1){
rc = sqlite3VdbeIdxKeyCompare(db, pC, &r, &res);
if( rc ) goto abort_due_to_error;
- if( res>0 ){
+ if( res>0 && pOp->p5==0 ){
seekscan_search_fail:
+ /* Jump to SeekGE.P2, ending the loop */
#ifdef SQLITE_DEBUG
if( db->flags&SQLITE_VdbeTrace ){
printf("... %d steps and then skip\n", pOp->p1 - nStep);
@@ -92110,7 +94642,8 @@ case OP_SeekScan: {
pOp++;
goto jump_to_p2;
}
- if( res==0 ){
+ if( res>=0 ){
+ /* Jump to This.P2, bypassing the OP_SeekGE opcode */
#ifdef SQLITE_DEBUG
if( db->flags&SQLITE_VdbeTrace ){
printf("... %d steps and then success\n", pOp->p1 - nStep);
@@ -92186,12 +94719,16 @@ case OP_SeekHit: {
/* Opcode: IfNotOpen P1 P2 * * *
** Synopsis: if( !csr[P1] ) goto P2
**
-** If cursor P1 is not open, jump to instruction P2. Otherwise, fall through.
+** If cursor P1 is not open or if P1 is set to a NULL row using the
+** OP_NullRow opcode, then jump to instruction P2. Otherwise, fall through.
*/
case OP_IfNotOpen: { /* jump */
+ VdbeCursor *pCur;
+
assert( pOp->p1>=0 && pOp->p1<p->nCursor );
- VdbeBranchTaken(p->apCsr[pOp->p1]==0, 2);
- if( !p->apCsr[pOp->p1] ){
+ pCur = p->apCsr[pOp->p1];
+ VdbeBranchTaken(pCur==0 || pCur->nullRow, 2);
+ if( pCur==0 || pCur->nullRow ){
goto jump_to_p2_and_check_for_interrupt;
}
break;
@@ -92305,11 +94842,8 @@ case OP_NoConflict: /* jump, in3 */
case OP_NotFound: /* jump, in3 */
case OP_Found: { /* jump, in3 */
int alreadyExists;
- int takeJump;
int ii;
VdbeCursor *pC;
- int res;
- UnpackedRecord *pFree;
UnpackedRecord *pIdxKey;
UnpackedRecord r;
@@ -92324,14 +94858,15 @@ case OP_Found: { /* jump, in3 */
#ifdef SQLITE_DEBUG
pC->seekOp = pOp->opcode;
#endif
- pIn3 = &aMem[pOp->p3];
+ r.aMem = &aMem[pOp->p3];
assert( pC->eCurType==CURTYPE_BTREE );
assert( pC->uc.pCursor!=0 );
assert( pC->isTable==0 );
- if( pOp->p4.i>0 ){
+ r.nField = (u16)pOp->p4.i;
+ if( r.nField>0 ){
+ /* Key values in an array of registers */
r.pKeyInfo = pC->pKeyInfo;
- r.nField = (u16)pOp->p4.i;
- r.aMem = pIn3;
+ r.default_rc = 0;
#ifdef SQLITE_DEBUG
for(ii=0; ii<r.nField; ii++){
assert( memIsValid(&r.aMem[ii]) );
@@ -92339,37 +94874,25 @@ case OP_Found: { /* jump, in3 */
if( ii ) REGISTER_TRACE(pOp->p3+ii, &r.aMem[ii]);
}
#endif
- pIdxKey = &r;
- pFree = 0;
+ rc = sqlite3BtreeIndexMoveto(pC->uc.pCursor, &r, &pC->seekResult);
}else{
- assert( pIn3->flags & MEM_Blob );
- rc = ExpandBlob(pIn3);
+ /* Composite key generated by OP_MakeRecord */
+ assert( r.aMem->flags & MEM_Blob );
+ assert( pOp->opcode!=OP_NoConflict );
+ rc = ExpandBlob(r.aMem);
assert( rc==SQLITE_OK || rc==SQLITE_NOMEM );
if( rc ) goto no_mem;
- pFree = pIdxKey = sqlite3VdbeAllocUnpackedRecord(pC->pKeyInfo);
+ pIdxKey = sqlite3VdbeAllocUnpackedRecord(pC->pKeyInfo);
if( pIdxKey==0 ) goto no_mem;
- sqlite3VdbeRecordUnpack(pC->pKeyInfo, pIn3->n, pIn3->z, pIdxKey);
+ sqlite3VdbeRecordUnpack(pC->pKeyInfo, r.aMem->n, r.aMem->z, pIdxKey);
+ pIdxKey->default_rc = 0;
+ rc = sqlite3BtreeIndexMoveto(pC->uc.pCursor, pIdxKey, &pC->seekResult);
+ sqlite3DbFreeNN(db, pIdxKey);
}
- pIdxKey->default_rc = 0;
- takeJump = 0;
- if( pOp->opcode==OP_NoConflict ){
- /* For the OP_NoConflict opcode, take the jump if any of the
- ** input fields are NULL, since any key with a NULL will not
- ** conflict */
- for(ii=0; ii<pIdxKey->nField; ii++){
- if( pIdxKey->aMem[ii].flags & MEM_Null ){
- takeJump = 1;
- break;
- }
- }
- }
- rc = sqlite3BtreeIndexMoveto(pC->uc.pCursor, pIdxKey, &res);
- if( pFree ) sqlite3DbFreeNN(db, pFree);
if( rc!=SQLITE_OK ){
goto abort_due_to_error;
}
- pC->seekResult = res;
- alreadyExists = (res==0);
+ alreadyExists = (pC->seekResult==0);
pC->nullRow = 1-alreadyExists;
pC->deferredMoveto = 0;
pC->cacheStatus = CACHE_STALE;
@@ -92377,9 +94900,25 @@ case OP_Found: { /* jump, in3 */
VdbeBranchTaken(alreadyExists!=0,2);
if( alreadyExists ) goto jump_to_p2;
}else{
- VdbeBranchTaken(takeJump||alreadyExists==0,2);
- if( takeJump || !alreadyExists ) goto jump_to_p2;
- if( pOp->opcode==OP_IfNoHope ) pC->seekHit = pOp->p4.i;
+ if( !alreadyExists ){
+ VdbeBranchTaken(1,2);
+ goto jump_to_p2;
+ }
+ if( pOp->opcode==OP_NoConflict ){
+ /* For the OP_NoConflict opcode, take the jump if any of the
+ ** input fields are NULL, since any key with a NULL will not
+ ** conflict */
+ for(ii=0; ii<r.nField; ii++){
+ if( r.aMem[ii].flags & MEM_Null ){
+ VdbeBranchTaken(1,2);
+ goto jump_to_p2;
+ }
+ }
+ }
+ VdbeBranchTaken(0,2);
+ if( pOp->opcode==OP_IfNoHope ){
+ pC->seekHit = pOp->p4.i;
+ }
}
break;
}
@@ -93070,7 +95609,7 @@ case OP_RowData: {
}
/* Opcode: Rowid P1 P2 * * *
-** Synopsis: r[P2]=rowid
+** Synopsis: r[P2]=PX rowid of P1
**
** Store in register P2 an integer which is the key of the table entry that
** P1 is currently point to.
@@ -93126,16 +95665,24 @@ case OP_Rowid: { /* out2 */
** that occur while the cursor is on the null row will always
** write a NULL.
**
-** Or, if P1 is a Pseudo-Cursor (a cursor opened using OP_OpenPseudo)
-** just reset the cache for that cursor. This causes the row of
-** content held by the pseudo-cursor to be reparsed.
+** If cursor P1 is not previously opened, open it now to a special
+** pseudo-cursor that always returns NULL for every column.
*/
case OP_NullRow: {
VdbeCursor *pC;
assert( pOp->p1>=0 && pOp->p1<p->nCursor );
pC = p->apCsr[pOp->p1];
- assert( pC!=0 );
+ if( pC==0 ){
+ /* If the cursor is not already open, create a special kind of
+ ** pseudo-cursor that always gives null rows. */
+ pC = allocateCursor(p, pOp->p1, 1, CURTYPE_PSEUDO);
+ if( pC==0 ) goto no_mem;
+ pC->seekResult = 0;
+ pC->isTable = 1;
+ pC->noReuse = 1;
+ pC->uc.pCursor = sqlite3BtreeFakeValidCursor();
+ }
pC->nullRow = 1;
pC->cacheStatus = CACHE_STALE;
if( pC->eCurType==CURTYPE_BTREE ){
@@ -93308,7 +95855,7 @@ case OP_Rewind: { /* jump */
break;
}
-/* Opcode: Next P1 P2 P3 P4 P5
+/* Opcode: Next P1 P2 P3 * P5
**
** Advance cursor P1 so that it points to the next key/data pair in its
** table or index. If there are no more key/value pairs then fall through
@@ -93327,15 +95874,12 @@ case OP_Rewind: { /* jump */
** omitted if that index had been unique. P3 is usually 0. P3 is
** always either 0 or 1.
**
-** P4 is always of type P4_ADVANCE. The function pointer points to
-** sqlite3BtreeNext().
-**
** If P5 is positive and the jump is taken, then event counter
** number P5-1 in the prepared statement is incremented.
**
** See also: Prev
*/
-/* Opcode: Prev P1 P2 P3 P4 P5
+/* Opcode: Prev P1 P2 P3 * P5
**
** Back up cursor P1 so that it points to the previous key/data pair in its
** table or index. If there is no previous key/value pairs then fall through
@@ -93355,9 +95899,6 @@ case OP_Rewind: { /* jump */
** omitted if that index had been unique. P3 is usually 0. P3 is
** always either 0 or 1.
**
-** P4 is always of type P4_ADVANCE. The function pointer points to
-** sqlite3BtreePrevious().
-**
** If P5 is positive and the jump is taken, then event counter
** number P5-1 in the prepared statement is incremented.
*/
@@ -93375,30 +95916,37 @@ case OP_SorterNext: { /* jump */
assert( isSorter(pC) );
rc = sqlite3VdbeSorterNext(db, pC);
goto next_tail;
+
case OP_Prev: /* jump */
-case OP_Next: /* jump */
assert( pOp->p1>=0 && pOp->p1<p->nCursor );
- assert( pOp->p5<ArraySize(p->aCounter) );
+ assert( pOp->p5==0
+ || pOp->p5==SQLITE_STMTSTATUS_FULLSCAN_STEP
+ || pOp->p5==SQLITE_STMTSTATUS_AUTOINDEX);
pC = p->apCsr[pOp->p1];
assert( pC!=0 );
assert( pC->deferredMoveto==0 );
assert( pC->eCurType==CURTYPE_BTREE );
- assert( pOp->opcode!=OP_Next || pOp->p4.xAdvance==sqlite3BtreeNext );
- assert( pOp->opcode!=OP_Prev || pOp->p4.xAdvance==sqlite3BtreePrevious );
+ assert( pC->seekOp==OP_SeekLT || pC->seekOp==OP_SeekLE
+ || pC->seekOp==OP_Last || pC->seekOp==OP_IfNoHope
+ || pC->seekOp==OP_NullRow);
+ rc = sqlite3BtreePrevious(pC->uc.pCursor, pOp->p3);
+ goto next_tail;
- /* The Next opcode is only used after SeekGT, SeekGE, Rewind, and Found.
- ** The Prev opcode is only used after SeekLT, SeekLE, and Last. */
- assert( pOp->opcode!=OP_Next
- || pC->seekOp==OP_SeekGT || pC->seekOp==OP_SeekGE
+case OP_Next: /* jump */
+ assert( pOp->p1>=0 && pOp->p1<p->nCursor );
+ assert( pOp->p5==0
+ || pOp->p5==SQLITE_STMTSTATUS_FULLSCAN_STEP
+ || pOp->p5==SQLITE_STMTSTATUS_AUTOINDEX);
+ pC = p->apCsr[pOp->p1];
+ assert( pC!=0 );
+ assert( pC->deferredMoveto==0 );
+ assert( pC->eCurType==CURTYPE_BTREE );
+ assert( pC->seekOp==OP_SeekGT || pC->seekOp==OP_SeekGE
|| pC->seekOp==OP_Rewind || pC->seekOp==OP_Found
|| pC->seekOp==OP_NullRow|| pC->seekOp==OP_SeekRowid
|| pC->seekOp==OP_IfNoHope);
- assert( pOp->opcode!=OP_Prev
- || pC->seekOp==OP_SeekLT || pC->seekOp==OP_SeekLE
- || pC->seekOp==OP_Last || pC->seekOp==OP_IfNoHope
- || pC->seekOp==OP_NullRow);
+ rc = sqlite3BtreeNext(pC->uc.pCursor, pOp->p3);
- rc = pOp->p4.xAdvance(pC->uc.pCursor, pOp->p3);
next_tail:
pC->cacheStatus = CACHE_STALE;
VdbeBranchTaken(rc==SQLITE_OK,2);
@@ -93585,9 +96133,9 @@ case OP_IdxRowid: { /* out2 */
assert( pOp->p1>=0 && pOp->p1<p->nCursor );
pC = p->apCsr[pOp->p1];
assert( pC!=0 );
- assert( pC->eCurType==CURTYPE_BTREE );
+ assert( pC->eCurType==CURTYPE_BTREE || IsNullCursor(pC) );
assert( pC->uc.pCursor!=0 );
- assert( pC->isTable==0 );
+ assert( pC->isTable==0 || IsNullCursor(pC) );
assert( pC->deferredMoveto==0 );
assert( !pC->nullRow || pOp->opcode==OP_IdxRowid );
@@ -93595,10 +96143,10 @@ case OP_IdxRowid: { /* out2 */
** of sqlite3VdbeCursorRestore() and sqlite3VdbeIdxRowid(). */
rc = sqlite3VdbeCursorRestore(pC);
- /* sqlite3VbeCursorRestore() can only fail if the record has been deleted
- ** out from under the cursor. That will never happens for an IdxRowid
- ** or Seek opcode */
- if( NEVER(rc!=SQLITE_OK) ) goto abort_due_to_error;
+ /* sqlite3VdbeCursorRestore() may fail if the cursor has been disturbed
+ ** since it was last positioned and an error (e.g. OOM or an IO error)
+ ** occurs while trying to reposition it. */
+ if( rc!=SQLITE_OK ) goto abort_due_to_error;
if( !pC->nullRow ){
rowid = 0; /* Not needed. Only used to silence a warning. */
@@ -93616,6 +96164,7 @@ case OP_IdxRowid: { /* out2 */
pTabCur->nullRow = 0;
pTabCur->movetoTarget = rowid;
pTabCur->deferredMoveto = 1;
+ pTabCur->cacheStatus = CACHE_STALE;
assert( pOp->p4type==P4_INTARRAY || pOp->p4.ai==0 );
assert( !pTabCur->isEphemeral );
pTabCur->ub.aAltMap = pOp->p4.ai;
@@ -93750,7 +96299,7 @@ case OP_IdxGE: { /* jump */
rc = sqlite3VdbeMemFromBtreeZeroOffset(pCur, (u32)nCellKey, &m);
if( rc ) goto abort_due_to_error;
res = sqlite3VdbeRecordCompareWithSkip(m.n, m.z, &r, 0);
- sqlite3VdbeMemRelease(&m);
+ sqlite3VdbeMemReleaseMalloc(&m);
}
/* End of inlined sqlite3VdbeIdxKeyCompare() */
@@ -94499,7 +97048,7 @@ case OP_IfPos: { /* jump, in1 */
** Synopsis: if r[P1]>0 then r[P2]=r[P1]+max(0,r[P3]) else r[P2]=(-1)
**
** This opcode performs a commonly used computation associated with
-** LIMIT and OFFSET process. r[P1] holds the limit counter. r[P3]
+** LIMIT and OFFSET processing. r[P1] holds the limit counter. r[P3]
** holds the offset counter. The opcode computes the combined value
** of the LIMIT and OFFSET and stores that value in r[P2]. The r[P2]
** value computed is the total number of rows that will need to be
@@ -94631,6 +97180,7 @@ case OP_AggStep: {
pCtx->pVdbe = p;
pCtx->skipFlag = 0;
pCtx->isError = 0;
+ pCtx->enc = encoding;
pCtx->argc = n;
pOp->p4type = P4_FUNCCTX;
pOp->p4.pCtx = pCtx;
@@ -94760,9 +97310,6 @@ case OP_AggFinal: {
}
sqlite3VdbeChangeEncoding(pMem, encoding);
UPDATE_MAX_BLOBSIZE(pMem);
- if( sqlite3VdbeMemTooBig(pMem) ){
- goto too_big;
- }
break;
}
@@ -95270,7 +97817,6 @@ case OP_VColumn: {
VdbeCursor *pCur = p->apCsr[pOp->p1];
assert( pCur!=0 );
- assert( pCur->eCurType==CURTYPE_VTAB );
assert( pOp->p3>0 && pOp->p3<=(p->nMem+1 - p->nCursor) );
pDest = &aMem[pOp->p3];
memAboutToChange(p, pDest);
@@ -95278,11 +97824,13 @@ case OP_VColumn: {
sqlite3VdbeMemSetNull(pDest);
break;
}
+ assert( pCur->eCurType==CURTYPE_VTAB );
pVtab = pCur->uc.pVCur->pVtab;
pModule = pVtab->pModule;
assert( pModule->xColumn );
memset(&sContext, 0, sizeof(sContext));
sContext.pOut = pDest;
+ sContext.enc = encoding;
assert( pOp->p5==OPFLAG_NOCHNG || pOp->p5==0 );
if( pOp->p5 & OPFLAG_NOCHNG ){
sqlite3VdbeMemSetNull(pDest);
@@ -95301,9 +97849,6 @@ case OP_VColumn: {
REGISTER_TRACE(pOp->p3, pDest);
UPDATE_MAX_BLOBSIZE(pDest);
- if( sqlite3VdbeMemTooBig(pDest) ){
- goto too_big;
- }
if( rc ) goto abort_due_to_error;
break;
}
@@ -95570,6 +98115,7 @@ case OP_Function: { /* group */
if( pCtx->pOut != pOut ){
pCtx->pVdbe = p;
pCtx->pOut = pOut;
+ pCtx->enc = encoding;
for(i=pCtx->argc-1; i>=0; i--) pCtx->argv[i] = &aMem[pOp->p2+i];
}
assert( pCtx->pVdbe==p );
@@ -95596,17 +98142,27 @@ case OP_Function: { /* group */
if( rc ) goto abort_due_to_error;
}
- /* Copy the result of the function into register P3 */
- if( pOut->flags & (MEM_Str|MEM_Blob) ){
- sqlite3VdbeChangeEncoding(pOut, encoding);
- if( sqlite3VdbeMemTooBig(pOut) ) goto too_big;
- }
+ assert( (pOut->flags&MEM_Str)==0
+ || pOut->enc==encoding
+ || db->mallocFailed );
+ assert( !sqlite3VdbeMemTooBig(pOut) );
REGISTER_TRACE(pOp->p3, pOut);
UPDATE_MAX_BLOBSIZE(pOut);
break;
}
+/* Opcode: ClrSubtype P1 * * * *
+** Synopsis: r[P1].subtype = 0
+**
+** Clear the subtype from register P1.
+*/
+case OP_ClrSubtype: { /* in1 */
+ pIn1 = &aMem[pOp->p1];
+ pIn1->flags &= ~MEM_Subtype;
+ break;
+}
+
/* Opcode: FilterAdd P1 * P3 P4 *
** Synopsis: filter(P1) += key(P3@P4)
**
@@ -95726,7 +98282,7 @@ case OP_Init: { /* jump */
#ifndef SQLITE_OMIT_TRACE
if( (db->mTrace & (SQLITE_TRACE_STMT|SQLITE_TRACE_LEGACY))!=0
- && !p->doingRerun
+ && p->minWriteFileFormat!=254 /* tag-20220401a */
&& (zTrace = (pOp->p4.z ? pOp->p4.z : p->zSql))!=0
){
#ifndef SQLITE_OMIT_DEPRECATED
@@ -95955,7 +98511,7 @@ abort_due_to_error:
testcase( sqlite3GlobalConfig.xLog!=0 );
sqlite3_log(rc, "statement aborts at %d: [%s] %s",
(int)(pOp - aOp), p->zSql, p->zErrMsg);
- sqlite3VdbeHalt(p);
+ if( p->eVdbeState==VDBE_RUN_STATE ) sqlite3VdbeHalt(p);
if( rc==SQLITE_IOERR_NOMEM ) sqlite3OomFault(db);
if( rc==SQLITE_CORRUPT && db->autoCommit==0 ){
db->flags |= SQLITE_CorruptRdOnly;
@@ -100090,6 +102646,8 @@ SQLITE_PRIVATE int sqlite3JournalOpen(
){
MemJournal *p = (MemJournal*)pJfd;
+ assert( zName || nSpill<0 || (flags & SQLITE_OPEN_EXCLUSIVE) );
+
/* Zero the file-handle object. If nSpill was passed zero, initialize
** it using the sqlite3OsOpen() function of the underlying VFS. In this
** case none of the code in this module is executed as a result of calls
@@ -100517,53 +103075,24 @@ static void resolveAlias(
sqlite3ExprDelete(db, pDup);
pDup = 0;
}else{
+ Expr temp;
incrAggFunctionDepth(pDup, nSubquery);
if( pExpr->op==TK_COLLATE ){
assert( !ExprHasProperty(pExpr, EP_IntValue) );
pDup = sqlite3ExprAddCollateString(pParse, pDup, pExpr->u.zToken);
}
-
- /* Before calling sqlite3ExprDelete(), set the EP_Static flag. This
- ** prevents ExprDelete() from deleting the Expr structure itself,
- ** allowing it to be repopulated by the memcpy() on the following line.
- ** The pExpr->u.zToken might point into memory that will be freed by the
- ** sqlite3DbFree(db, pDup) on the last line of this block, so be sure to
- ** make a copy of the token before doing the sqlite3DbFree().
- */
- ExprSetProperty(pExpr, EP_Static);
- sqlite3ExprDelete(db, pExpr);
- memcpy(pExpr, pDup, sizeof(*pExpr));
- if( !ExprHasProperty(pExpr, EP_IntValue) && pExpr->u.zToken!=0 ){
- assert( (pExpr->flags & (EP_Reduced|EP_TokenOnly))==0 );
- pExpr->u.zToken = sqlite3DbStrDup(db, pExpr->u.zToken);
- pExpr->flags |= EP_MemToken;
- }
+ memcpy(&temp, pDup, sizeof(Expr));
+ memcpy(pDup, pExpr, sizeof(Expr));
+ memcpy(pExpr, &temp, sizeof(Expr));
if( ExprHasProperty(pExpr, EP_WinFunc) ){
if( ALWAYS(pExpr->y.pWin!=0) ){
pExpr->y.pWin->pOwner = pExpr;
}
}
- sqlite3DbFree(db, pDup);
+ sqlite3ExprDeferredDelete(pParse, pDup);
}
}
-
-/*
-** Return TRUE if the name zCol occurs anywhere in the USING clause.
-**
-** Return FALSE if the USING clause is NULL or if it does not contain
-** zCol.
-*/
-static int nameInUsingClause(IdList *pUsing, const char *zCol){
- if( pUsing ){
- int k;
- for(k=0; k<pUsing->nId; k++){
- if( sqlite3StrICmp(pUsing->a[k].zName, zCol)==0 ) return 1;
- }
- }
- return 0;
-}
-
/*
** Subqueries stores the original database, table and column names for their
** result sets in ExprList.a[].zSpan, in the form "DATABASE.TABLE.COLUMN".
@@ -100579,7 +103108,7 @@ SQLITE_PRIVATE int sqlite3MatchEName(
){
int n;
const char *zSpan;
- if( pItem->eEName!=ENAME_TAB ) return 0;
+ if( pItem->fg.eEName!=ENAME_TAB ) return 0;
zSpan = pItem->zEName;
for(n=0; ALWAYS(zSpan[n]) && zSpan[n]!='.'; n++){}
if( zDb && (sqlite3StrNICmp(zSpan, zDb, n)!=0 || zDb[n]!=0) ){
@@ -100641,6 +103170,29 @@ SQLITE_PRIVATE Bitmask sqlite3ExprColUsed(Expr *pExpr){
}
/*
+** Create a new expression term for the column specified by pMatch and
+** iColumn. Append this new expression term to the FULL JOIN Match set
+** in *ppList. Create a new *ppList if this is the first term in the
+** set.
+*/
+static void extendFJMatch(
+ Parse *pParse, /* Parsing context */
+ ExprList **ppList, /* ExprList to extend */
+ SrcItem *pMatch, /* Source table containing the column */
+ i16 iColumn /* The column number */
+){
+ Expr *pNew = sqlite3ExprAlloc(pParse->db, TK_COLUMN, 0, 0);
+ if( pNew ){
+ pNew->iTable = pMatch->iCursor;
+ pNew->iColumn = iColumn;
+ pNew->y.pTab = pMatch->pTab;
+ assert( (pMatch->fg.jointype & (JT_LEFT|JT_LTORJ))!=0 );
+ ExprSetProperty(pNew, EP_CanBeNull);
+ *ppList = sqlite3ExprListAppend(pParse, *ppList, pNew);
+ }
+}
+
+/*
** Given the name of a column of the form X.Y.Z or Y.Z or just Z, look up
** that name in the set of source tables in pSrcList and make the pExpr
** expression node refer back to that source column. The following changes
@@ -100685,11 +103237,13 @@ static int lookupName(
NameContext *pTopNC = pNC; /* First namecontext in the list */
Schema *pSchema = 0; /* Schema of the expression */
int eNewExprOp = TK_COLUMN; /* New value for pExpr->op on success */
- Table *pTab = 0; /* Table hold the row */
+ Table *pTab = 0; /* Table holding the row */
Column *pCol; /* A column of pTab */
+ ExprList *pFJMatch = 0; /* Matches for FULL JOIN .. USING */
assert( pNC ); /* the name context cannot be NULL. */
assert( zCol ); /* The Z in X.Y.Z cannot be NULL */
+ assert( zDb==0 || zTab!=0 );
assert( !ExprHasProperty(pExpr, EP_TokenOnly|EP_Reduced) );
/* Initialize the node to no-match */
@@ -100738,26 +103292,65 @@ static int lookupName(
pTab = pItem->pTab;
assert( pTab!=0 && pTab->zName!=0 );
assert( pTab->nCol>0 || pParse->nErr );
- if( pItem->pSelect && (pItem->pSelect->selFlags & SF_NestedFrom)!=0 ){
+ assert( (int)pItem->fg.isNestedFrom == IsNestedFrom(pItem->pSelect) );
+ if( pItem->fg.isNestedFrom ){
+ /* In this case, pItem is a subquery that has been formed from a
+ ** parenthesized subset of the FROM clause terms. Example:
+ ** .... FROM t1 LEFT JOIN (t2 RIGHT JOIN t3 USING(x)) USING(y) ...
+ ** \_________________________/
+ ** This pItem -------------^
+ */
int hit = 0;
+ assert( pItem->pSelect!=0 );
pEList = pItem->pSelect->pEList;
+ assert( pEList!=0 );
+ assert( pEList->nExpr==pTab->nCol );
for(j=0; j<pEList->nExpr; j++){
- if( sqlite3MatchEName(&pEList->a[j], zCol, zTab, zDb) ){
- cnt++;
- cntTab = 2;
- pMatch = pItem;
- pExpr->iColumn = j;
- hit = 1;
+ if( !sqlite3MatchEName(&pEList->a[j], zCol, zTab, zDb) ){
+ continue;
+ }
+ if( cnt>0 ){
+ if( pItem->fg.isUsing==0
+ || sqlite3IdListIndex(pItem->u3.pUsing, zCol)<0
+ ){
+ /* Two or more tables have the same column name which is
+ ** not joined by USING. This is an error. Signal as much
+ ** by clearing pFJMatch and letting cnt go above 1. */
+ sqlite3ExprListDelete(db, pFJMatch);
+ pFJMatch = 0;
+ }else
+ if( (pItem->fg.jointype & JT_RIGHT)==0 ){
+ /* An INNER or LEFT JOIN. Use the left-most table */
+ continue;
+ }else
+ if( (pItem->fg.jointype & JT_LEFT)==0 ){
+ /* A RIGHT JOIN. Use the right-most table */
+ cnt = 0;
+ sqlite3ExprListDelete(db, pFJMatch);
+ pFJMatch = 0;
+ }else{
+ /* For a FULL JOIN, we must construct a coalesce() func */
+ extendFJMatch(pParse, &pFJMatch, pMatch, pExpr->iColumn);
+ }
}
+ cnt++;
+ cntTab = 2;
+ pMatch = pItem;
+ pExpr->iColumn = j;
+ pEList->a[j].fg.bUsed = 1;
+ hit = 1;
+ if( pEList->a[j].fg.bUsingTerm ) break;
}
if( hit || zTab==0 ) continue;
}
- if( zDb ){
- if( pTab->pSchema!=pSchema ) continue;
- if( pSchema==0 && strcmp(zDb,"*")!=0 ) continue;
- }
+ assert( zDb==0 || zTab!=0 );
if( zTab ){
- const char *zTabName = pItem->zAlias ? pItem->zAlias : pTab->zName;
+ const char *zTabName;
+ if( zDb ){
+ if( pTab->pSchema!=pSchema ) continue;
+ if( pSchema==0 && strcmp(zDb,"*")!=0 ) continue;
+ }
+ zTabName = pItem->zAlias ? pItem->zAlias : pTab->zName;
assert( zTabName!=0 );
if( sqlite3StrICmp(zTabName, zTab)!=0 ){
continue;
@@ -100772,18 +103365,37 @@ static int lookupName(
if( pCol->hName==hCol
&& sqlite3StrICmp(pCol->zCnName, zCol)==0
){
- /* If there has been exactly one prior match and this match
- ** is for the right-hand table of a NATURAL JOIN or is in a
- ** USING clause, then skip this match.
- */
- if( cnt==1 ){
- if( pItem->fg.jointype & JT_NATURAL ) continue;
- if( nameInUsingClause(pItem->pUsing, zCol) ) continue;
+ if( cnt>0 ){
+ if( pItem->fg.isUsing==0
+ || sqlite3IdListIndex(pItem->u3.pUsing, zCol)<0
+ ){
+ /* Two or more tables have the same column name which is
+ ** not joined by USING. This is an error. Signal as much
+ ** by clearing pFJMatch and letting cnt go above 1. */
+ sqlite3ExprListDelete(db, pFJMatch);
+ pFJMatch = 0;
+ }else
+ if( (pItem->fg.jointype & JT_RIGHT)==0 ){
+ /* An INNER or LEFT JOIN. Use the left-most table */
+ continue;
+ }else
+ if( (pItem->fg.jointype & JT_LEFT)==0 ){
+ /* A RIGHT JOIN. Use the right-most table */
+ cnt = 0;
+ sqlite3ExprListDelete(db, pFJMatch);
+ pFJMatch = 0;
+ }else{
+ /* For a FULL JOIN, we must construct a coalesce() func */
+ extendFJMatch(pParse, &pFJMatch, pMatch, pExpr->iColumn);
+ }
}
cnt++;
pMatch = pItem;
/* Substitute the rowid (column -1) for the INTEGER PRIMARY KEY */
pExpr->iColumn = j==pTab->iPKey ? -1 : (i16)j;
+ if( pItem->fg.isNestedFrom ){
+ sqlite3SrcItemColumnUsed(pItem, j);
+ }
break;
}
}
@@ -100796,9 +103408,7 @@ static int lookupName(
pExpr->iTable = pMatch->iCursor;
assert( ExprUseYTab(pExpr) );
pExpr->y.pTab = pMatch->pTab;
- /* RIGHT JOIN not (yet) supported */
- assert( (pMatch->fg.jointype & JT_RIGHT)==0 );
- if( (pMatch->fg.jointype & JT_LEFT)!=0 ){
+ if( (pMatch->fg.jointype & (JT_LEFT|JT_LTORJ))!=0 ){
ExprSetProperty(pExpr, EP_CanBeNull);
}
pSchema = pExpr->y.pTab->pSchema;
@@ -100952,7 +103562,7 @@ static int lookupName(
assert( pEList!=0 );
for(j=0; j<pEList->nExpr; j++){
char *zAs = pEList->a[j].zEName;
- if( pEList->a[j].eEName==ENAME_NAME
+ if( pEList->a[j].fg.eEName==ENAME_NAME
&& sqlite3_stricmp(zAs, zCol)==0
){
Expr *pOrig;
@@ -101039,11 +103649,37 @@ static int lookupName(
}
/*
- ** cnt==0 means there was not match. cnt>1 means there were two or
- ** more matches. Either way, we have an error.
+ ** cnt==0 means there was not match.
+ ** cnt>1 means there were two or more matches.
+ **
+ ** cnt==0 is always an error. cnt>1 is often an error, but might
+ ** be multiple matches for a NATURAL LEFT JOIN or a LEFT JOIN USING.
*/
+ assert( pFJMatch==0 || cnt>0 );
+ assert( !ExprHasProperty(pExpr, EP_xIsSelect|EP_IntValue) );
if( cnt!=1 ){
const char *zErr;
+ if( pFJMatch ){
+ if( pFJMatch->nExpr==cnt-1 ){
+ if( ExprHasProperty(pExpr,EP_Leaf) ){
+ ExprClearProperty(pExpr,EP_Leaf);
+ }else{
+ sqlite3ExprDelete(db, pExpr->pLeft);
+ pExpr->pLeft = 0;
+ sqlite3ExprDelete(db, pExpr->pRight);
+ pExpr->pRight = 0;
+ }
+ extendFJMatch(pParse, &pFJMatch, pMatch, pExpr->iColumn);
+ pExpr->op = TK_FUNCTION;
+ pExpr->u.zToken = "coalesce";
+ pExpr->x.pList = pFJMatch;
+ cnt = 1;
+ goto lookupname_end;
+ }else{
+ sqlite3ExprListDelete(db, pFJMatch);
+ pFJMatch = 0;
+ }
+ }
zErr = cnt==0 ? "no such column" : "ambiguous column name";
if( zDb ){
sqlite3ErrorMsg(pParse, "%s: %s.%s.%s", zErr, zDb, zTab, zCol);
@@ -101056,6 +103692,16 @@ static int lookupName(
pParse->checkSchema = 1;
pTopNC->nNcErr++;
}
+ assert( pFJMatch==0 );
+
+ /* Remove all substructure from pExpr */
+ if( !ExprHasProperty(pExpr,(EP_TokenOnly|EP_Leaf)) ){
+ sqlite3ExprDelete(db, pExpr->pLeft);
+ pExpr->pLeft = 0;
+ sqlite3ExprDelete(db, pExpr->pRight);
+ pExpr->pRight = 0;
+ ExprSetProperty(pExpr, EP_Leaf);
+ }
/* If a column from a table in pSrcList is referenced, then record
** this fact in the pSrcList.a[].colUsed bitmask. Column 0 causes
@@ -101075,16 +103721,7 @@ static int lookupName(
pMatch->colUsed |= sqlite3ExprColUsed(pExpr);
}
- /* Clean up and return
- */
- if( !ExprHasProperty(pExpr,(EP_TokenOnly|EP_Leaf)) ){
- sqlite3ExprDelete(db, pExpr->pLeft);
- pExpr->pLeft = 0;
- sqlite3ExprDelete(db, pExpr->pRight);
- pExpr->pRight = 0;
- }
pExpr->op = eNewExprOp;
- ExprSetProperty(pExpr, EP_Leaf);
lookupname_end:
if( cnt==1 ){
assert( pNC!=0 );
@@ -101269,7 +103906,7 @@ static int resolveExprStep(Walker *pWalker, Expr *pExpr){
}
sqlite3WalkExpr(pWalker, pExpr->pLeft);
if( 0==sqlite3ExprCanBeNull(pExpr->pLeft) && !IN_RENAME_OBJECT ){
- testcase( ExprHasProperty(pExpr, EP_FromJoin) );
+ testcase( ExprHasProperty(pExpr, EP_OuterON) );
assert( !ExprHasProperty(pExpr, EP_IntValue) );
if( pExpr->op==TK_NOTNULL ){
pExpr->u.zToken = "true";
@@ -101678,7 +104315,7 @@ static int resolveAsName(
assert( !ExprHasProperty(pE, EP_IntValue) );
zCol = pE->u.zToken;
for(i=0; i<pEList->nExpr; i++){
- if( pEList->a[i].eEName==ENAME_NAME
+ if( pEList->a[i].fg.eEName==ENAME_NAME
&& sqlite3_stricmp(pEList->a[i].zEName, zCol)==0
){
return i+1;
@@ -101799,7 +104436,7 @@ static int resolveCompoundOrderBy(
return 1;
}
for(i=0; i<pOrderBy->nExpr; i++){
- pOrderBy->a[i].done = 0;
+ pOrderBy->a[i].fg.done = 0;
}
pSelect->pNext = 0;
while( pSelect->pPrior ){
@@ -101814,7 +104451,7 @@ static int resolveCompoundOrderBy(
for(i=0, pItem=pOrderBy->a; i<pOrderBy->nExpr; i++, pItem++){
int iCol = -1;
Expr *pE, *pDup;
- if( pItem->done ) continue;
+ if( pItem->fg.done ) continue;
pE = sqlite3ExprSkipCollateAndLikely(pItem->pExpr);
if( NEVER(pE==0) ) continue;
if( sqlite3ExprIsInteger(pE, &iCol) ){
@@ -101867,7 +104504,7 @@ static int resolveCompoundOrderBy(
sqlite3ExprDelete(db, pE);
pItem->u.x.iOrderByCol = (u16)iCol;
}
- pItem->done = 1;
+ pItem->fg.done = 1;
}else{
moreToDo = 1;
}
@@ -101875,7 +104512,7 @@ static int resolveCompoundOrderBy(
pSelect = pSelect->pNext;
}
for(i=0; i<pOrderBy->nExpr; i++){
- if( pOrderBy->a[i].done==0 ){
+ if( pOrderBy->a[i].fg.done==0 ){
sqlite3ErrorMsg(pParse, "%r ORDER BY term does not match any "
"column in the result set", i+1);
return 1;
@@ -102165,8 +104802,8 @@ static int resolveSelectStep(Walker *pWalker, Select *p){
sNC.uNC.pEList = p->pEList;
sNC.ncFlags |= NC_UEList;
if( p->pHaving ){
- if( !pGroupBy ){
- sqlite3ErrorMsg(pParse, "a GROUP BY clause is required before HAVING");
+ if( (p->selFlags & SF_Aggregate)==0 ){
+ sqlite3ErrorMsg(pParse, "HAVING clause on a non-aggregate query");
return WRC_Abort;
}
if( sqlite3ResolveExprNames(&sNC, p->pHaving) ) return WRC_Abort;
@@ -102546,9 +105183,8 @@ SQLITE_PRIVATE char sqlite3ExprAffinity(const Expr *pExpr){
if( op==TK_REGISTER ) op = pExpr->op2;
if( op==TK_COLUMN || op==TK_AGG_COLUMN ){
assert( ExprUseYTab(pExpr) );
- if( pExpr->y.pTab ){
- return sqlite3TableColumnAffinity(pExpr->y.pTab, pExpr->iColumn);
- }
+ assert( pExpr->y.pTab!=0 );
+ return sqlite3TableColumnAffinity(pExpr->y.pTab, pExpr->iColumn);
}
if( op==TK_SELECT ){
assert( ExprUseXSelect(pExpr) );
@@ -102666,17 +105302,14 @@ SQLITE_PRIVATE CollSeq *sqlite3ExprCollSeq(Parse *pParse, const Expr *pExpr){
int op = p->op;
if( op==TK_REGISTER ) op = p->op2;
if( op==TK_AGG_COLUMN || op==TK_COLUMN || op==TK_TRIGGER ){
+ int j;
assert( ExprUseYTab(p) );
- if( p->y.pTab!=0 ){
- /* op==TK_REGISTER && p->y.pTab!=0 happens when pExpr was originally
- ** a TK_COLUMN but was previously evaluated and cached in a register */
- int j = p->iColumn;
- if( j>=0 ){
- const char *zColl = sqlite3ColumnColl(&p->y.pTab->aCol[j]);
- pColl = sqlite3FindCollSeq(db, ENC(db), zColl, 0);
- }
- break;
+ assert( p->y.pTab!=0 );
+ if( (j = p->iColumn)>=0 ){
+ const char *zColl = sqlite3ColumnColl(&p->y.pTab->aCol[j]);
+ pColl = sqlite3FindCollSeq(db, ENC(db), zColl, 0);
}
+ break;
}
if( op==TK_CAST || op==TK_UPLUS ){
p = p->pLeft;
@@ -103261,7 +105894,9 @@ static void heightOfSelect(const Select *pSelect, int *pnHeight){
*/
static void exprSetHeight(Expr *p){
int nHeight = p->pLeft ? p->pLeft->nHeight : 0;
- if( p->pRight && p->pRight->nHeight>nHeight ) nHeight = p->pRight->nHeight;
+ if( NEVER(p->pRight) && p->pRight->nHeight>nHeight ){
+ nHeight = p->pRight->nHeight;
+ }
if( ExprUseXSelect(p) ){
heightOfSelect(p->x.pSelect, &nHeight);
}else if( p->x.pList ){
@@ -103404,15 +106039,26 @@ SQLITE_PRIVATE void sqlite3ExprAttachSubtrees(
sqlite3ExprDelete(db, pLeft);
sqlite3ExprDelete(db, pRight);
}else{
+ assert( ExprUseXList(pRoot) );
+ assert( pRoot->x.pSelect==0 );
if( pRight ){
pRoot->pRight = pRight;
pRoot->flags |= EP_Propagate & pRight->flags;
+#if SQLITE_MAX_EXPR_DEPTH>0
+ pRoot->nHeight = pRight->nHeight+1;
+ }else{
+ pRoot->nHeight = 1;
+#endif
}
if( pLeft ){
pRoot->pLeft = pLeft;
pRoot->flags |= EP_Propagate & pLeft->flags;
+#if SQLITE_MAX_EXPR_DEPTH>0
+ if( pLeft->nHeight>=pRoot->nHeight ){
+ pRoot->nHeight = pLeft->nHeight+1;
+ }
+#endif
}
- exprSetHeight(pRoot);
}
}
@@ -103560,6 +106206,7 @@ SQLITE_PRIVATE Expr *sqlite3ExprFunction(
sqlite3ExprListDelete(db, pList); /* Avoid memory leak when malloc fails */
return 0;
}
+ assert( !ExprHasProperty(pNew, EP_InnerON|EP_OuterON) );
pNew->w.iOfst = (int)(pToken->z - pParse->zTail);
if( pList
&& pList->nExpr > pParse->db->aLimit[SQLITE_LIMIT_FUNCTION_ARG]
@@ -103697,6 +106344,7 @@ SQLITE_PRIVATE void sqlite3ExprAssignVarNumber(Parse *pParse, Expr *pExpr, u32 n
*/
static SQLITE_NOINLINE void sqlite3ExprDeleteNN(sqlite3 *db, Expr *p){
assert( p!=0 );
+ assert( db!=0 );
assert( !ExprUseUValue(p) || p->u.iValue>=0 );
assert( !ExprUseYWin(p) || !ExprUseYSub(p) );
assert( !ExprUseYWin(p) || p->y.pWin!=0 || db->mallocFailed );
@@ -103728,18 +106376,26 @@ static SQLITE_NOINLINE void sqlite3ExprDeleteNN(sqlite3 *db, Expr *p){
#endif
}
}
- if( ExprHasProperty(p, EP_MemToken) ){
- assert( !ExprHasProperty(p, EP_IntValue) );
- sqlite3DbFree(db, p->u.zToken);
- }
if( !ExprHasProperty(p, EP_Static) ){
- sqlite3DbFreeNN(db, p);
+ sqlite3DbNNFreeNN(db, p);
}
}
SQLITE_PRIVATE void sqlite3ExprDelete(sqlite3 *db, Expr *p){
if( p ) sqlite3ExprDeleteNN(db, p);
}
+/*
+** Clear both elements of an OnOrUsing object
+*/
+SQLITE_PRIVATE void sqlite3ClearOnOrUsing(sqlite3 *db, OnOrUsing *p){
+ if( p==0 ){
+ /* Nothing to clear */
+ }else if( p->pOn ){
+ sqlite3ExprDeleteNN(db, p->pOn);
+ }else if( p->pUsing ){
+ sqlite3IdListDelete(db, p->pUsing);
+ }
+}
/*
** Arrange to cause pExpr to be deleted when the pParse is deleted.
@@ -103752,8 +106408,9 @@ SQLITE_PRIVATE void sqlite3ExprDelete(sqlite3 *db, Expr *p){
** pExpr to the pParse->pConstExpr list with a register number of 0.
*/
SQLITE_PRIVATE void sqlite3ExprDeferredDelete(Parse *pParse, Expr *pExpr){
- pParse->pConstExpr =
- sqlite3ExprListAppend(pParse, pParse->pConstExpr, pExpr);
+ sqlite3ParserAddCleanup(pParse,
+ (void(*)(sqlite3*,void*))sqlite3ExprDelete,
+ pExpr);
}
/* Invoke sqlite3RenameExprUnmap() and sqlite3ExprDelete() on the
@@ -103826,8 +106483,7 @@ static int dupedExprStructSize(const Expr *p, int flags){
nSize = EXPR_FULLSIZE;
}else{
assert( !ExprHasProperty(p, EP_TokenOnly|EP_Reduced) );
- assert( !ExprHasProperty(p, EP_FromJoin) );
- assert( !ExprHasProperty(p, EP_MemToken) );
+ assert( !ExprHasProperty(p, EP_OuterON) );
assert( !ExprHasVVAProperty(p, EP_NoReduce) );
if( p->pLeft || p->x.pList ){
nSize = EXPR_REDUCEDSIZE | EP_Reduced;
@@ -103931,7 +106587,7 @@ static Expr *exprDup(sqlite3 *db, const Expr *p, int dupFlags, u8 **pzBuffer){
}
/* Set the EP_Reduced, EP_TokenOnly, and EP_Static flags appropriately. */
- pNew->flags &= ~(EP_Reduced|EP_TokenOnly|EP_Static|EP_MemToken);
+ pNew->flags &= ~(EP_Reduced|EP_TokenOnly|EP_Static);
pNew->flags |= nStructSize & (EP_Reduced|EP_TokenOnly);
pNew->flags |= staticFlag;
ExprClearVVAProperties(pNew);
@@ -104006,6 +106662,7 @@ SQLITE_PRIVATE With *sqlite3WithDup(sqlite3 *db, With *p){
pRet->a[i].pSelect = sqlite3SelectDup(db, p->a[i].pSelect, 0);
pRet->a[i].pCols = sqlite3ExprListDup(db, p->a[i].pCols, 0);
pRet->a[i].zName = sqlite3DbStrDup(db, p->a[i].zName);
+ pRet->a[i].eM10d = p->a[i].eM10d;
}
}
}
@@ -104106,11 +106763,8 @@ SQLITE_PRIVATE ExprList *sqlite3ExprListDup(sqlite3 *db, const ExprList *p, int
}
}
pItem->zEName = sqlite3DbStrDup(db, pOldItem->zEName);
- pItem->sortFlags = pOldItem->sortFlags;
- pItem->eEName = pOldItem->eEName;
- pItem->done = 0;
- pItem->bNulls = pOldItem->bNulls;
- pItem->bSorterRef = pOldItem->bSorterRef;
+ pItem->fg = pOldItem->fg;
+ pItem->fg.done = 0;
pItem->u = pOldItem->u;
}
return pNew;
@@ -104162,8 +106816,12 @@ SQLITE_PRIVATE SrcList *sqlite3SrcListDup(sqlite3 *db, const SrcList *p, int fla
pTab->nTabRef++;
}
pNewItem->pSelect = sqlite3SelectDup(db, pOldItem->pSelect, flags);
- pNewItem->pOn = sqlite3ExprDup(db, pOldItem->pOn, flags);
- pNewItem->pUsing = sqlite3IdListDup(db, pOldItem->pUsing);
+ if( pOldItem->fg.isUsing ){
+ assert( pNewItem->fg.isUsing );
+ pNewItem->u3.pUsing = sqlite3IdListDup(db, pOldItem->u3.pUsing);
+ }else{
+ pNewItem->u3.pOn = sqlite3ExprDup(db, pOldItem->u3.pOn, flags);
+ }
pNewItem->colUsed = pOldItem->colUsed;
}
return pNew;
@@ -104173,22 +106831,16 @@ SQLITE_PRIVATE IdList *sqlite3IdListDup(sqlite3 *db, const IdList *p){
int i;
assert( db!=0 );
if( p==0 ) return 0;
- pNew = sqlite3DbMallocRawNN(db, sizeof(*pNew) );
+ assert( p->eU4!=EU4_EXPR );
+ pNew = sqlite3DbMallocRawNN(db, sizeof(*pNew)+(p->nId-1)*sizeof(p->a[0]) );
if( pNew==0 ) return 0;
pNew->nId = p->nId;
- pNew->a = sqlite3DbMallocRawNN(db, p->nId*sizeof(p->a[0]) );
- if( pNew->a==0 ){
- sqlite3DbFreeNN(db, pNew);
- return 0;
- }
- /* Note that because the size of the allocation for p->a[] is not
- ** necessarily a power of two, sqlite3IdListAppend() may not be called
- ** on the duplicate created by this function. */
+ pNew->eU4 = p->eU4;
for(i=0; i<p->nId; i++){
struct IdList_item *pNewItem = &pNew->a[i];
- struct IdList_item *pOldItem = &p->a[i];
+ const struct IdList_item *pOldItem = &p->a[i];
pNewItem->zName = sqlite3DbStrDup(db, pOldItem->zName);
- pNewItem->idx = pOldItem->idx;
+ pNewItem->u4 = pOldItem->u4;
}
return pNew;
}
@@ -104412,16 +107064,16 @@ SQLITE_PRIVATE void sqlite3ExprListSetSortOrder(ExprList *p, int iSortOrder, int
);
pItem = &p->a[p->nExpr-1];
- assert( pItem->bNulls==0 );
+ assert( pItem->fg.bNulls==0 );
if( iSortOrder==SQLITE_SO_UNDEFINED ){
iSortOrder = SQLITE_SO_ASC;
}
- pItem->sortFlags = (u8)iSortOrder;
+ pItem->fg.sortFlags = (u8)iSortOrder;
if( eNulls!=SQLITE_SO_UNDEFINED ){
- pItem->bNulls = 1;
+ pItem->fg.bNulls = 1;
if( iSortOrder!=eNulls ){
- pItem->sortFlags |= KEYINFO_ORDER_BIGNULL;
+ pItem->fg.sortFlags |= KEYINFO_ORDER_BIGNULL;
}
}
}
@@ -104447,7 +107099,7 @@ SQLITE_PRIVATE void sqlite3ExprListSetName(
assert( pList->nExpr>0 );
pItem = &pList->a[pList->nExpr-1];
assert( pItem->zEName==0 );
- assert( pItem->eEName==ENAME_NAME );
+ assert( pItem->fg.eEName==ENAME_NAME );
pItem->zEName = sqlite3DbStrNDup(pParse->db, pName->z, pName->n);
if( dequote ){
/* If dequote==0, then pName->z does not point to part of a DDL
@@ -104482,7 +107134,7 @@ SQLITE_PRIVATE void sqlite3ExprListSetSpan(
assert( pList->nExpr>0 );
if( pItem->zEName==0 ){
pItem->zEName = sqlite3DbSpanDup(db, zStart, zEnd);
- pItem->eEName = ENAME_SPAN;
+ pItem->fg.eEName = ENAME_SPAN;
}
}
}
@@ -104511,12 +107163,13 @@ static SQLITE_NOINLINE void exprListDeleteNN(sqlite3 *db, ExprList *pList){
int i = pList->nExpr;
struct ExprList_item *pItem = pList->a;
assert( pList->nExpr>0 );
+ assert( db!=0 );
do{
sqlite3ExprDelete(db, pItem->pExpr);
- sqlite3DbFree(db, pItem->zEName);
+ if( pItem->zEName ) sqlite3DbNNFreeNN(db, pItem->zEName);
pItem++;
}while( --i>0 );
- sqlite3DbFreeNN(db, pList);
+ sqlite3DbNNFreeNN(db, pList);
}
SQLITE_PRIVATE void sqlite3ExprListDelete(sqlite3 *db, ExprList *pList){
if( pList ) exprListDeleteNN(db, pList);
@@ -104654,9 +107307,9 @@ SQLITE_PRIVATE Expr *sqlite3ExprSimplifiedAndOr(Expr *pExpr){
static int exprNodeIsConstant(Walker *pWalker, Expr *pExpr){
/* If pWalker->eCode is 2 then any term of the expression that comes from
- ** the ON or USING clauses of a left join disqualifies the expression
+ ** the ON or USING clauses of an outer join disqualifies the expression
** from being considered constant. */
- if( pWalker->eCode==2 && ExprHasProperty(pExpr, EP_FromJoin) ){
+ if( pWalker->eCode==2 && ExprHasProperty(pExpr, EP_OuterON) ){
pWalker->eCode = 0;
return WRC_Abort;
}
@@ -104779,7 +107432,7 @@ SQLITE_PRIVATE int sqlite3ExprIsTableConstant(Expr *p, int iCur){
** Check pExpr to see if it is an invariant constraint on data source pSrc.
** This is an optimization. False negatives will perhaps cause slower
** queries, but false positives will yield incorrect answers. So when in
-** double, return 0.
+** doubt, return 0.
**
** To be an invariant constraint, the following must be true:
**
@@ -104787,24 +107440,28 @@ SQLITE_PRIVATE int sqlite3ExprIsTableConstant(Expr *p, int iCur){
**
** (2) pExpr cannot use subqueries or non-deterministic functions.
**
-** (*) ** Not applicable to this branch **
+** (3) pSrc cannot be part of the left operand for a RIGHT JOIN.
+** (Is there some way to relax this constraint?)
**
** (4) If pSrc is the right operand of a LEFT JOIN, then...
** (4a) pExpr must come from an ON clause..
-** (4b) and specifically the ON clause associated with the LEFT JOIN.
+ (4b) and specifically the ON clause associated with the LEFT JOIN.
**
** (5) If pSrc is not the right operand of a LEFT JOIN or the left
** operand of a RIGHT JOIN, then pExpr must be from the WHERE
** clause, not an ON clause.
*/
SQLITE_PRIVATE int sqlite3ExprIsTableConstraint(Expr *pExpr, const SrcItem *pSrc){
+ if( pSrc->fg.jointype & JT_LTORJ ){
+ return 0; /* rule (3) */
+ }
if( pSrc->fg.jointype & JT_LEFT ){
- if( !ExprHasProperty(pExpr, EP_FromJoin) ) return 0; /* rule (4a) */
- if( pExpr->w.iRightJoinTable!=pSrc->iCursor ) return 0; /* rule (4b) */
+ if( !ExprHasProperty(pExpr, EP_OuterON) ) return 0; /* rule (4a) */
+ if( pExpr->w.iJoin!=pSrc->iCursor ) return 0; /* rule (4b) */
}else{
- if( ExprHasProperty(pExpr, EP_FromJoin) ) return 0; /* rule (5) */
+ if( ExprHasProperty(pExpr, EP_OuterON) ) return 0; /* rule (5) */
}
- return sqlite3ExprIsTableConstant(pExpr, pSrc->iCursor); /* rules (1), (2) */
+ return sqlite3ExprIsTableConstant(pExpr, pSrc->iCursor); /* rules (1), (2) */
}
@@ -105134,7 +107791,7 @@ static int sqlite3InRhsIsConstant(Expr *pIn){
** all members of the RHS set, skipping duplicates.
**
** A cursor is opened on the b-tree object that is the RHS of the IN operator
-** and pX->iTable is set to the index of that cursor.
+** and the *piTab parameter is set to the index of that cursor.
**
** The returned value of this function indicates the b-tree type, as follows:
**
@@ -105154,7 +107811,10 @@ static int sqlite3InRhsIsConstant(Expr *pIn){
** If the RHS of the IN operator is a list or a more complex subquery, then
** an ephemeral table might need to be generated from the RHS and then
** pX->iTable made to point to the ephemeral table instead of an
-** existing table.
+** existing table. In this case, the creation and initialization of the
+** ephmeral table might be put inside of a subroutine, the EP_Subrtn flag
+** will be set on pX and the pX->y.sub fields will be set to show where
+** the subroutine is coded.
**
** The inFlags parameter must contain, at a minimum, one of the bits
** IN_INDEX_MEMBERSHIP or IN_INDEX_LOOP but not both. If inFlags contains
@@ -105215,12 +107875,13 @@ SQLITE_PRIVATE int sqlite3FindInIndex(
){
Select *p; /* SELECT to the right of IN operator */
int eType = 0; /* Type of RHS table. IN_INDEX_* */
- int iTab = pParse->nTab++; /* Cursor of the RHS table */
+ int iTab; /* Cursor of the RHS table */
int mustBeUnique; /* True if RHS must be unique */
Vdbe *v = sqlite3GetVdbe(pParse); /* Virtual machine being coded */
assert( pX->op==TK_IN );
mustBeUnique = (inFlags & IN_INDEX_LOOP)!=0;
+ iTab = pParse->nTab++;
/* If the RHS of this IN(...) operator is a SELECT, and if it matters
** whether or not the SELECT result contains NULL values, check whether
@@ -105386,6 +108047,8 @@ SQLITE_PRIVATE int sqlite3FindInIndex(
&& ExprUseXList(pX)
&& (!sqlite3InRhsIsConstant(pX) || pX->x.pList->nExpr<=2)
){
+ pParse->nTab--; /* Back out the allocation of the unused cursor */
+ iTab = -1; /* Cursor is not allocated */
eType = IN_INDEX_NOOP;
}
@@ -105552,6 +108215,7 @@ SQLITE_PRIVATE void sqlite3CodeRhsOfIN(
assert( ExprUseYSub(pExpr) );
sqlite3VdbeAddOp2(v, OP_Gosub, pExpr->y.sub.regReturn,
pExpr->y.sub.iAddr);
+ assert( iTab!=pExpr->iTable );
sqlite3VdbeAddOp2(v, OP_OpenDup, iTab, pExpr->iTable);
sqlite3VdbeJumpHere(v, addrOnce);
return;
@@ -105563,8 +108227,7 @@ SQLITE_PRIVATE void sqlite3CodeRhsOfIN(
assert( !ExprHasProperty(pExpr, EP_TokenOnly|EP_Reduced) );
pExpr->y.sub.regReturn = ++pParse->nMem;
pExpr->y.sub.iAddr =
- sqlite3VdbeAddOp2(v, OP_Integer, 0, pExpr->y.sub.regReturn) + 1;
- VdbeComment((v, "return address"));
+ sqlite3VdbeAddOp2(v, OP_BeginSubrtn, 0, pExpr->y.sub.regReturn) + 1;
addrOnce = sqlite3VdbeAddOp0(v, OP_Once); VdbeCoverage(v);
}
@@ -105666,6 +108329,7 @@ SQLITE_PRIVATE void sqlite3CodeRhsOfIN(
** expression we need to rerun this code each time.
*/
if( addrOnce && !sqlite3ExprIsConstant(pE2) ){
+ sqlite3VdbeChangeToNoop(v, addrOnce-1);
sqlite3VdbeChangeToNoop(v, addrOnce);
ExprClearProperty(pExpr, EP_Subrtn);
addrOnce = 0;
@@ -105683,11 +108347,15 @@ SQLITE_PRIVATE void sqlite3CodeRhsOfIN(
sqlite3VdbeChangeP4(v, addr, (void *)pKeyInfo, P4_KEYINFO);
}
if( addrOnce ){
+ sqlite3VdbeAddOp1(v, OP_NullRow, iTab);
sqlite3VdbeJumpHere(v, addrOnce);
/* Subroutine return */
assert( ExprUseYSub(pExpr) );
- sqlite3VdbeAddOp1(v, OP_Return, pExpr->y.sub.regReturn);
- sqlite3VdbeChangeP1(v, pExpr->y.sub.iAddr-1, sqlite3VdbeCurrentAddr(v)-1);
+ assert( sqlite3VdbeGetOp(v,pExpr->y.sub.iAddr-1)->opcode==OP_BeginSubrtn
+ || pParse->nErr );
+ sqlite3VdbeAddOp3(v, OP_Return, pExpr->y.sub.regReturn,
+ pExpr->y.sub.iAddr, 1);
+ VdbeCoverage(v);
sqlite3ClearTempRegCache(pParse);
}
}
@@ -105741,9 +108409,7 @@ SQLITE_PRIVATE int sqlite3CodeSubselect(Parse *pParse, Expr *pExpr){
ExprSetProperty(pExpr, EP_Subrtn);
pExpr->y.sub.regReturn = ++pParse->nMem;
pExpr->y.sub.iAddr =
- sqlite3VdbeAddOp2(v, OP_Integer, 0, pExpr->y.sub.regReturn) + 1;
- VdbeComment((v, "return address"));
-
+ sqlite3VdbeAddOp2(v, OP_BeginSubrtn, 0, pExpr->y.sub.regReturn) + 1;
/* The evaluation of the EXISTS/SELECT must be repeated every time it
** is encountered if any of the following is true:
@@ -105795,7 +108461,7 @@ SQLITE_PRIVATE int sqlite3CodeSubselect(Parse *pParse, Expr *pExpr){
pLimit = sqlite3PExpr(pParse, TK_NE,
sqlite3ExprDup(db, pSel->pLimit->pLeft, 0), pLimit);
}
- sqlite3ExprDelete(db, pSel->pLimit->pLeft);
+ sqlite3ExprDeferredDelete(pParse, pSel->pLimit->pLeft);
pSel->pLimit->pLeft = pLimit;
}else{
/* If there is no pre-existing limit add a limit of 1 */
@@ -105816,8 +108482,11 @@ SQLITE_PRIVATE int sqlite3CodeSubselect(Parse *pParse, Expr *pExpr){
/* Subroutine return */
assert( ExprUseYSub(pExpr) );
- sqlite3VdbeAddOp1(v, OP_Return, pExpr->y.sub.regReturn);
- sqlite3VdbeChangeP1(v, pExpr->y.sub.iAddr-1, sqlite3VdbeCurrentAddr(v)-1);
+ assert( sqlite3VdbeGetOp(v,pExpr->y.sub.iAddr-1)->opcode==OP_BeginSubrtn
+ || pParse->nErr );
+ sqlite3VdbeAddOp3(v, OP_Return, pExpr->y.sub.regReturn,
+ pExpr->y.sub.iAddr, 1);
+ VdbeCoverage(v);
sqlite3ClearTempRegCache(pParse);
return rReg;
}
@@ -106245,12 +108914,10 @@ SQLITE_PRIVATE void sqlite3ExprCodeGetColumnOfTable(
){
Column *pCol;
assert( v!=0 );
- if( pTab==0 ){
- sqlite3VdbeAddOp3(v, OP_Column, iTabCur, iCol, regOut);
- return;
- }
+ assert( pTab!=0 );
if( iCol<0 || iCol==pTab->iPKey ){
sqlite3VdbeAddOp2(v, OP_Rowid, iTabCur, regOut);
+ VdbeComment((v, "%s.rowid", pTab->zName));
}else{
int op;
int x;
@@ -106305,7 +108972,7 @@ SQLITE_PRIVATE int sqlite3ExprCodeGetColumn(
assert( pParse->pVdbe!=0 );
sqlite3ExprCodeGetColumnOfTable(pParse->pVdbe, pTab, iTable, iColumn, iReg);
if( p5 ){
- VdbeOp *pOp = sqlite3VdbeGetOp(pParse->pVdbe,-1);
+ VdbeOp *pOp = sqlite3VdbeGetLastOp(pParse->pVdbe);
if( pOp->opcode==OP_Column ) pOp->p5 = p5;
}
return iReg;
@@ -106374,7 +109041,7 @@ static int exprCodeVector(Parse *pParse, Expr *p, int *piFreeable){
** so that a subsequent copy will not be merged into this one.
*/
static void setDoNotMergeFlagOnCopy(Vdbe *v){
- if( sqlite3VdbeGetOp(v, -1)->opcode==OP_Copy ){
+ if( sqlite3VdbeGetLastOp(v)->opcode==OP_Copy ){
sqlite3VdbeChangeP5(v, 1); /* Tag trailing OP_Copy as not mergable */
}
}
@@ -106421,7 +109088,17 @@ static int exprCodeInlineFunction(
caseExpr.x.pList = pFarg;
return sqlite3ExprCodeTarget(pParse, &caseExpr, target);
}
-
+#ifdef SQLITE_ENABLE_OFFSET_SQL_FUNC
+ case INLINEFUNC_sqlite_offset: {
+ Expr *pArg = pFarg->a[0].pExpr;
+ if( pArg->op==TK_COLUMN && pArg->iTable>=0 ){
+ sqlite3VdbeAddOp3(v, OP_Offset, pArg->iTable, pArg->iColumn, target);
+ }else{
+ sqlite3VdbeAddOp2(v, OP_Null, 0, target);
+ }
+ break;
+ }
+#endif
default: {
/* The UNLIKELY() function is a no-op. The result is the value
** of the first argument.
@@ -106487,6 +109164,53 @@ static int exprCodeInlineFunction(
return target;
}
+/*
+** Check to see if pExpr is one of the indexed expressions on pParse->pIdxExpr.
+** If it is, then resolve the expression by reading from the index and
+** return the register into which the value has been read. If pExpr is
+** not an indexed expression, then return negative.
+*/
+static SQLITE_NOINLINE int sqlite3IndexedExprLookup(
+ Parse *pParse, /* The parsing context */
+ Expr *pExpr, /* The expression to potentially bypass */
+ int target /* Where to store the result of the expression */
+){
+ IndexedExpr *p;
+ Vdbe *v;
+ for(p=pParse->pIdxExpr; p; p=p->pIENext){
+ int iDataCur = p->iDataCur;
+ if( iDataCur<0 ) continue;
+ if( pParse->iSelfTab ){
+ if( p->iDataCur!=pParse->iSelfTab-1 ) continue;
+ iDataCur = -1;
+ }
+ if( sqlite3ExprCompare(0, pExpr, p->pExpr, iDataCur)!=0 ) continue;
+ v = pParse->pVdbe;
+ assert( v!=0 );
+ if( p->bMaybeNullRow ){
+ /* If the index is on a NULL row due to an outer join, then we
+ ** cannot extract the value from the index. The value must be
+ ** computed using the original expression. */
+ int addr = sqlite3VdbeCurrentAddr(v);
+ sqlite3VdbeAddOp3(v, OP_IfNullRow, p->iIdxCur, addr+3, target);
+ VdbeCoverage(v);
+ sqlite3VdbeAddOp3(v, OP_Column, p->iIdxCur, p->iIdxCol, target);
+ VdbeComment((v, "%s expr-column %d", p->zIdxName, p->iIdxCol));
+ sqlite3VdbeGoto(v, 0);
+ p = pParse->pIdxExpr;
+ pParse->pIdxExpr = 0;
+ sqlite3ExprCode(pParse, pExpr, target);
+ pParse->pIdxExpr = p;
+ sqlite3VdbeJumpHere(v, addr+2);
+ }else{
+ sqlite3VdbeAddOp3(v, OP_Column, p->iIdxCur, p->iIdxCol, target);
+ VdbeComment((v, "%s expr-column %d", p->zIdxName, p->iIdxCol));
+ }
+ return target;
+ }
+ return -1; /* Not found */
+}
+
/*
** Generate code into the current Vdbe to evaluate the given
@@ -106515,6 +109239,11 @@ SQLITE_PRIVATE int sqlite3ExprCodeTarget(Parse *pParse, Expr *pExpr, int target)
expr_code_doover:
if( pExpr==0 ){
op = TK_NULL;
+ }else if( pParse->pIdxExpr!=0
+ && !ExprHasProperty(pExpr, EP_Leaf)
+ && (r1 = sqlite3IndexedExprLookup(pParse, pExpr, target))>=0
+ ){
+ return r1;
}else{
assert( !ExprHasVVAProperty(pExpr,EP_Immutable) );
op = pExpr->op;
@@ -106535,7 +109264,7 @@ expr_code_doover:
pCol->iSorterColumn, target);
if( pCol->iColumn<0 ){
VdbeComment((v,"%s.rowid",pTab->zName));
- }else{
+ }else if( ALWAYS(pTab!=0) ){
VdbeComment((v,"%s.%s",
pTab->zName, pTab->aCol[pCol->iColumn].zCnName));
if( pTab->aCol[pCol->iColumn].affinity==SQLITE_AFF_REAL ){
@@ -106560,11 +109289,8 @@ expr_code_doover:
int aff;
iReg = sqlite3ExprCodeTarget(pParse, pExpr->pLeft,target);
assert( ExprUseYTab(pExpr) );
- if( pExpr->y.pTab ){
- aff = sqlite3TableColumnAffinity(pExpr->y.pTab, pExpr->iColumn);
- }else{
- aff = pExpr->affExpr;
- }
+ assert( pExpr->y.pTab!=0 );
+ aff = sqlite3TableColumnAffinity(pExpr->y.pTab, pExpr->iColumn);
if( aff>SQLITE_AFF_BLOB ){
static const char zAff[] = "B\000C\000D\000E";
assert( SQLITE_AFF_BLOB=='A' );
@@ -106626,12 +109352,10 @@ expr_code_doover:
}
}
assert( ExprUseYTab(pExpr) );
+ assert( pExpr->y.pTab!=0 );
iReg = sqlite3ExprCodeGetColumn(pParse, pExpr->y.pTab,
pExpr->iColumn, iTab, target,
pExpr->op2);
- if( pExpr->y.pTab==0 && pExpr->affExpr==SQLITE_AFF_REAL ){
- sqlite3VdbeAddOp1(v, OP_RealAffinity, iReg);
- }
return iReg;
}
case TK_INTEGER: {
@@ -106960,20 +109684,8 @@ expr_code_doover:
if( !pColl ) pColl = db->pDfltColl;
sqlite3VdbeAddOp4(v, OP_CollSeq, 0, 0, 0, (char *)pColl, P4_COLLSEQ);
}
-#ifdef SQLITE_ENABLE_OFFSET_SQL_FUNC
- if( (pDef->funcFlags & SQLITE_FUNC_OFFSET)!=0 && ALWAYS(pFarg!=0) ){
- Expr *pArg = pFarg->a[0].pExpr;
- if( pArg->op==TK_COLUMN ){
- sqlite3VdbeAddOp3(v, OP_Offset, pArg->iTable, pArg->iColumn, target);
- }else{
- sqlite3VdbeAddOp2(v, OP_Null, 0, target);
- }
- }else
-#endif
- {
- sqlite3VdbeAddFunctionCall(pParse, constMask, r1, target, nFarg,
- pDef, pExpr->op2);
- }
+ sqlite3VdbeAddFunctionCall(pParse, constMask, r1, target, nFarg,
+ pDef, pExpr->op2);
if( nFarg ){
if( constMask==0 ){
sqlite3ReleaseTempRange(pParse, r1, nFarg);
@@ -107003,16 +109715,18 @@ expr_code_doover:
}
case TK_SELECT_COLUMN: {
int n;
- if( pExpr->pLeft->iTable==0 ){
- pExpr->pLeft->iTable = sqlite3CodeSubselect(pParse, pExpr->pLeft);
+ Expr *pLeft = pExpr->pLeft;
+ if( pLeft->iTable==0 || pParse->withinRJSubrtn > pLeft->op2 ){
+ pLeft->iTable = sqlite3CodeSubselect(pParse, pLeft);
+ pLeft->op2 = pParse->withinRJSubrtn;
}
- assert( pExpr->pLeft->op==TK_SELECT || pExpr->pLeft->op==TK_ERROR );
- n = sqlite3ExprVectorSize(pExpr->pLeft);
+ assert( pLeft->op==TK_SELECT || pLeft->op==TK_ERROR );
+ n = sqlite3ExprVectorSize(pLeft);
if( pExpr->iTable!=n ){
sqlite3ErrorMsg(pParse, "%d columns assigned %d values",
pExpr->iTable, n);
}
- return pExpr->pLeft->iTable + pExpr->iColumn;
+ return pLeft->iTable + pExpr->iColumn;
}
case TK_IN: {
int destIfFalse = sqlite3VdbeMakeLabel(pParse);
@@ -107043,8 +109757,24 @@ expr_code_doover:
exprCodeBetween(pParse, pExpr, target, 0, 0);
return target;
}
+ case TK_COLLATE: {
+ if( !ExprHasProperty(pExpr, EP_Collate)
+ && ALWAYS(pExpr->pLeft)
+ && pExpr->pLeft->op==TK_FUNCTION
+ ){
+ inReg = sqlite3ExprCodeTarget(pParse, pExpr->pLeft, target);
+ if( inReg!=target ){
+ sqlite3VdbeAddOp2(v, OP_SCopy, inReg, target);
+ inReg = target;
+ }
+ sqlite3VdbeAddOp1(v, OP_ClrSubtype, inReg);
+ return inReg;
+ }else{
+ pExpr = pExpr->pLeft;
+ goto expr_code_doover; /* 2018-04-28: Prevent deep recursion. */
+ }
+ }
case TK_SPAN:
- case TK_COLLATE:
case TK_UPLUS: {
pExpr = pExpr->pLeft;
goto expr_code_doover; /* 2018-04-28: Prevent deep recursion. OSSFuzz. */
@@ -107124,6 +109854,21 @@ expr_code_doover:
case TK_IF_NULL_ROW: {
int addrINR;
u8 okConstFactor = pParse->okConstFactor;
+ AggInfo *pAggInfo = pExpr->pAggInfo;
+ if( pAggInfo ){
+ assert( pExpr->iAgg>=0 && pExpr->iAgg<pAggInfo->nColumn );
+ if( !pAggInfo->directMode ){
+ inReg = pAggInfo->aCol[pExpr->iAgg].iMem;
+ break;
+ }
+ if( pExpr->pAggInfo->useSortingIdx ){
+ sqlite3VdbeAddOp3(v, OP_Column, pAggInfo->sortingIdxPTab,
+ pAggInfo->aCol[pExpr->iAgg].iSorterColumn,
+ target);
+ inReg = target;
+ break;
+ }
+ }
addrINR = sqlite3VdbeAddOp1(v, OP_IfNullRow, pExpr->iTable);
/* Temporarily disable factoring of constant expressions, since
** even though expressions may appear to be constant, they are not
@@ -107285,7 +110030,9 @@ SQLITE_PRIVATE int sqlite3ExprCodeRunJustOnce(
struct ExprList_item *pItem;
int i;
for(pItem=p->a, i=p->nExpr; i>0; pItem++, i--){
- if( pItem->reusable && sqlite3ExprCompare(0,pItem->pExpr,pExpr,-1)==0 ){
+ if( pItem->fg.reusable
+ && sqlite3ExprCompare(0,pItem->pExpr,pExpr,-1)==0
+ ){
return pItem->u.iConstExprReg;
}
}
@@ -107308,7 +110055,7 @@ SQLITE_PRIVATE int sqlite3ExprCodeRunJustOnce(
p = sqlite3ExprListAppend(pParse, p, pExpr);
if( p ){
struct ExprList_item *pItem = &p->a[p->nExpr-1];
- pItem->reusable = regDest<0;
+ pItem->fg.reusable = regDest<0;
if( regDest<0 ) regDest = ++pParse->nMem;
pItem->u.iConstExprReg = regDest;
}
@@ -107442,7 +110189,7 @@ SQLITE_PRIVATE int sqlite3ExprCodeExprList(
for(pItem=pList->a, i=0; i<n; i++, pItem++){
Expr *pExpr = pItem->pExpr;
#ifdef SQLITE_ENABLE_SORTER_REFERENCES
- if( pItem->bSorterRef ){
+ if( pItem->fg.bSorterRef ){
i--;
n--;
}else
@@ -107463,7 +110210,7 @@ SQLITE_PRIVATE int sqlite3ExprCodeExprList(
if( inReg!=target+i ){
VdbeOp *pOp;
if( copyOp==OP_Copy
- && (pOp=sqlite3VdbeGetOp(v, -1))->opcode==OP_Copy
+ && (pOp=sqlite3VdbeGetLastOp(v))->opcode==OP_Copy
&& pOp->p1+pOp->p3+1==inReg
&& pOp->p2+pOp->p3+1==target+i
&& pOp->p5==0 /* The do-not-merge flag must be clear */
@@ -107536,8 +110283,8 @@ static void exprCodeBetween(
** so that the sqlite3ExprCodeTarget() routine will not attempt to move
** it into the Parse.pConstExpr list. We should use a new bit for this,
** for clarity, but we are out of bits in the Expr.flags field so we
- ** have to reuse the EP_FromJoin bit. Bummer. */
- pDel->flags |= EP_FromJoin;
+ ** have to reuse the EP_OuterON bit. Bummer. */
+ pDel->flags |= EP_OuterON;
sqlite3ExprCodeTarget(pParse, &exprAnd, dest);
}
sqlite3ReleaseTempReg(pParse, regFree1);
@@ -107662,6 +110409,7 @@ SQLITE_PRIVATE void sqlite3ExprIfTrue(Parse *pParse, Expr *pExpr, int dest, int
assert( TK_ISNULL==OP_IsNull ); testcase( op==TK_ISNULL );
assert( TK_NOTNULL==OP_NotNull ); testcase( op==TK_NOTNULL );
r1 = sqlite3ExprCodeTemp(pParse, pExpr->pLeft, &regFree1);
+ sqlite3VdbeTypeofColumn(v, r1);
sqlite3VdbeAddOp2(v, op, r1, dest);
VdbeCoverageIf(v, op==TK_ISNULL);
VdbeCoverageIf(v, op==TK_NOTNULL);
@@ -107836,6 +110584,7 @@ SQLITE_PRIVATE void sqlite3ExprIfFalse(Parse *pParse, Expr *pExpr, int dest, int
case TK_ISNULL:
case TK_NOTNULL: {
r1 = sqlite3ExprCodeTemp(pParse, pExpr->pLeft, &regFree1);
+ sqlite3VdbeTypeofColumn(v, r1);
sqlite3VdbeAddOp2(v, op, r1, dest);
testcase( op==TK_ISNULL ); VdbeCoverageIf(v, op==TK_ISNULL);
testcase( op==TK_NOTNULL ); VdbeCoverageIf(v, op==TK_NOTNULL);
@@ -107989,7 +110738,13 @@ SQLITE_PRIVATE int sqlite3ExprCompare(
if( pB->op==TK_COLLATE && sqlite3ExprCompare(pParse, pA,pB->pLeft,iTab)<2 ){
return 1;
}
- return 2;
+ if( pA->op==TK_AGG_COLUMN && pB->op==TK_COLUMN
+ && pB->iTable<0 && pA->iTable==iTab
+ ){
+ /* fall through */
+ }else{
+ return 2;
+ }
}
assert( !ExprHasProperty(pA, EP_IntValue) );
assert( !ExprHasProperty(pB, EP_IntValue) );
@@ -108067,7 +110822,7 @@ SQLITE_PRIVATE int sqlite3ExprListCompare(const ExprList *pA, const ExprList *pB
int res;
Expr *pExprA = pA->a[i].pExpr;
Expr *pExprB = pB->a[i].pExpr;
- if( pA->a[i].sortFlags!=pB->a[i].sortFlags ) return 1;
+ if( pA->a[i].fg.sortFlags!=pB->a[i].fg.sortFlags ) return 1;
if( (res = sqlite3ExprCompare(0, pExprA, pExprB, iTab)) ) return res;
}
return 0;
@@ -108222,7 +110977,7 @@ SQLITE_PRIVATE int sqlite3ExprImpliesExpr(
static int impliesNotNullRow(Walker *pWalker, Expr *pExpr){
testcase( pExpr->op==TK_AGG_COLUMN );
testcase( pExpr->op==TK_AGG_FUNCTION );
- if( ExprHasProperty(pExpr, EP_FromJoin) ) return WRC_Prune;
+ if( ExprHasProperty(pExpr, EP_OuterON) ) return WRC_Prune;
switch( pExpr->op ){
case TK_ISNOT:
case TK_ISNULL:
@@ -108291,10 +111046,10 @@ static int impliesNotNullRow(Walker *pWalker, Expr *pExpr){
assert( pLeft->op!=TK_COLUMN || ExprUseYTab(pLeft) );
assert( pRight->op!=TK_COLUMN || ExprUseYTab(pRight) );
if( (pLeft->op==TK_COLUMN
- && pLeft->y.pTab!=0
+ && ALWAYS(pLeft->y.pTab!=0)
&& IsVirtual(pLeft->y.pTab))
|| (pRight->op==TK_COLUMN
- && pRight->y.pTab!=0
+ && ALWAYS(pRight->y.pTab!=0)
&& IsVirtual(pRight->y.pTab))
){
return WRC_Prune;
@@ -108319,8 +111074,8 @@ static int impliesNotNullRow(Walker *pWalker, Expr *pExpr){
** False positives are not allowed, however. A false positive may result
** in an incorrect answer.
**
-** Terms of p that are marked with EP_FromJoin (and hence that come from
-** the ON or USING clauses of LEFT JOINS) are excluded from the analysis.
+** Terms of p that are marked with EP_OuterON (and hence that come from
+** the ON or USING clauses of OUTER JOINS) are excluded from the analysis.
**
** This routine is used to check if a LEFT JOIN can be converted into
** an ordinary JOIN. The p argument is the WHERE clause. If the WHERE
@@ -108499,6 +111254,7 @@ static int exprRefToSrcList(Walker *pWalker, Expr *pExpr){
SQLITE_PRIVATE int sqlite3ReferencesSrcList(Parse *pParse, Expr *pExpr, SrcList *pSrcList){
Walker w;
struct RefSrcList x;
+ assert( pParse->db!=0 );
memset(&w, 0, sizeof(w));
memset(&x, 0, sizeof(x));
w.xExprCallback = exprRefToSrcList;
@@ -108515,7 +111271,7 @@ SQLITE_PRIVATE int sqlite3ReferencesSrcList(Parse *pParse, Expr *pExpr, SrcList
sqlite3WalkExpr(&w, pExpr->y.pWin->pFilter);
}
#endif
- sqlite3DbFree(pParse->db, x.aiExclude);
+ if( x.aiExclude ) sqlite3DbNNFreeNN(pParse->db, x.aiExclude);
if( w.eCode & 0x01 ){
return 1;
}else if( w.eCode ){
@@ -108546,8 +111302,8 @@ static int agginfoPersistExprCb(Walker *pWalker, Expr *pExpr){
int iAgg = pExpr->iAgg;
Parse *pParse = pWalker->pParse;
sqlite3 *db = pParse->db;
- assert( pExpr->op==TK_AGG_COLUMN || pExpr->op==TK_AGG_FUNCTION );
- if( pExpr->op==TK_AGG_COLUMN ){
+ if( pExpr->op!=TK_AGG_FUNCTION ){
+ assert( pExpr->op==TK_AGG_COLUMN || pExpr->op==TK_IF_NULL_ROW );
assert( iAgg>=0 && iAgg<pAggInfo->nColumn );
if( pAggInfo->aCol[iAgg].pCExpr==pExpr ){
pExpr = sqlite3ExprDup(db, pExpr, 0);
@@ -108557,6 +111313,7 @@ static int agginfoPersistExprCb(Walker *pWalker, Expr *pExpr){
}
}
}else{
+ assert( pExpr->op==TK_AGG_FUNCTION );
assert( iAgg>=0 && iAgg<pAggInfo->nFunc );
if( pAggInfo->aFunc[iAgg].pFExpr==pExpr ){
pExpr = sqlite3ExprDup(db, pExpr, 0);
@@ -108627,10 +111384,12 @@ static int analyzeAggregate(Walker *pWalker, Expr *pExpr){
assert( pNC->ncFlags & NC_UAggInfo );
switch( pExpr->op ){
+ case TK_IF_NULL_ROW:
case TK_AGG_COLUMN:
case TK_COLUMN: {
testcase( pExpr->op==TK_AGG_COLUMN );
testcase( pExpr->op==TK_COLUMN );
+ testcase( pExpr->op==TK_IF_NULL_ROW );
/* Check to see if the column is in one of the tables in the FROM
** clause of the aggregate query */
if( ALWAYS(pSrcList!=0) ){
@@ -108648,8 +111407,10 @@ static int analyzeAggregate(Walker *pWalker, Expr *pExpr){
int k;
pCol = pAggInfo->aCol;
for(k=0; k<pAggInfo->nColumn; k++, pCol++){
- if( pCol->iTable==pExpr->iTable &&
- pCol->iColumn==pExpr->iColumn ){
+ if( pCol->iTable==pExpr->iTable
+ && pCol->iColumn==pExpr->iColumn
+ && pExpr->op!=TK_IF_NULL_ROW
+ ){
break;
}
}
@@ -108664,15 +111425,17 @@ static int analyzeAggregate(Walker *pWalker, Expr *pExpr){
pCol->iMem = ++pParse->nMem;
pCol->iSorterColumn = -1;
pCol->pCExpr = pExpr;
- if( pAggInfo->pGroupBy ){
+ if( pAggInfo->pGroupBy && pExpr->op!=TK_IF_NULL_ROW ){
int j, n;
ExprList *pGB = pAggInfo->pGroupBy;
struct ExprList_item *pTerm = pGB->a;
n = pGB->nExpr;
for(j=0; j<n; j++, pTerm++){
Expr *pE = pTerm->pExpr;
- if( pE->op==TK_COLUMN && pE->iTable==pExpr->iTable &&
- pE->iColumn==pExpr->iColumn ){
+ if( pE->op==TK_COLUMN
+ && pE->iTable==pExpr->iTable
+ && pE->iColumn==pExpr->iColumn
+ ){
pCol->iSorterColumn = j;
break;
}
@@ -108689,7 +111452,9 @@ static int analyzeAggregate(Walker *pWalker, Expr *pExpr){
*/
ExprSetVVAProperty(pExpr, EP_NoReduce);
pExpr->pAggInfo = pAggInfo;
- pExpr->op = TK_AGG_COLUMN;
+ if( pExpr->op==TK_COLUMN ){
+ pExpr->op = TK_AGG_COLUMN;
+ }
pExpr->iAgg = (i16)k;
break;
} /* endif pExpr->iTable==pItem->iCursor */
@@ -109734,11 +112499,10 @@ static void unmapColumnIdlistNames(
Parse *pParse,
const IdList *pIdList
){
- if( pIdList ){
- int ii;
- for(ii=0; ii<pIdList->nId; ii++){
- sqlite3RenameTokenRemap(pParse, 0, (const void*)pIdList->a[ii].zName);
- }
+ int ii;
+ assert( pIdList!=0 );
+ for(ii=0; ii<pIdList->nId; ii++){
+ sqlite3RenameTokenRemap(pParse, 0, (const void*)pIdList->a[ii].zName);
}
}
@@ -109757,7 +112521,7 @@ static int renameUnmapSelectCb(Walker *pWalker, Select *p){
if( ALWAYS(p->pEList) ){
ExprList *pList = p->pEList;
for(i=0; i<pList->nExpr; i++){
- if( pList->a[i].zEName && pList->a[i].eEName==ENAME_NAME ){
+ if( pList->a[i].zEName && pList->a[i].fg.eEName==ENAME_NAME ){
sqlite3RenameTokenRemap(pParse, 0, (void*)pList->a[i].zEName);
}
}
@@ -109766,8 +112530,11 @@ static int renameUnmapSelectCb(Walker *pWalker, Select *p){
SrcList *pSrc = p->pSrc;
for(i=0; i<pSrc->nSrc; i++){
sqlite3RenameTokenRemap(pParse, 0, (void*)pSrc->a[i].zName);
- sqlite3WalkExpr(pWalker, pSrc->a[i].pOn);
- unmapColumnIdlistNames(pParse, pSrc->a[i].pUsing);
+ if( pSrc->a[i].fg.isUsing==0 ){
+ sqlite3WalkExpr(pWalker, pSrc->a[i].u3.pOn);
+ }else{
+ unmapColumnIdlistNames(pParse, pSrc->a[i].u3.pUsing);
+ }
}
}
@@ -109803,7 +112570,7 @@ SQLITE_PRIVATE void sqlite3RenameExprlistUnmap(Parse *pParse, ExprList *pEList){
sWalker.xExprCallback = renameUnmapExprCb;
sqlite3WalkExprList(&sWalker, pEList);
for(i=0; i<pEList->nExpr; i++){
- if( ALWAYS(pEList->a[i].eEName==ENAME_NAME) ){
+ if( ALWAYS(pEList->a[i].fg.eEName==ENAME_NAME) ){
sqlite3RenameTokenRemap(pParse, 0, (void*)pEList->a[i].zEName);
}
}
@@ -109961,7 +112728,7 @@ static void renameColumnElistNames(
int i;
for(i=0; i<pEList->nExpr; i++){
const char *zName = pEList->a[i].zEName;
- if( ALWAYS(pEList->a[i].eEName==ENAME_NAME)
+ if( ALWAYS(pEList->a[i].fg.eEName==ENAME_NAME)
&& ALWAYS(zName!=0)
&& 0==sqlite3_stricmp(zName, zOld)
){
@@ -110191,27 +112958,33 @@ static int renameResolveTrigger(Parse *pParse){
if( rc==SQLITE_OK && pStep->zTarget ){
SrcList *pSrc = sqlite3TriggerStepSrc(pParse, pStep);
if( pSrc ){
- int i;
- for(i=0; i<pSrc->nSrc && rc==SQLITE_OK; i++){
- SrcItem *p = &pSrc->a[i];
- p->iCursor = pParse->nTab++;
- if( p->pSelect ){
- sqlite3SelectPrep(pParse, p->pSelect, 0);
- sqlite3ExpandSubquery(pParse, p);
- assert( i>0 );
- assert( pStep->pFrom->a[i-1].pSelect );
- sqlite3SelectPrep(pParse, pStep->pFrom->a[i-1].pSelect, 0);
- }else{
- p->pTab = sqlite3LocateTableItem(pParse, 0, p);
- if( p->pTab==0 ){
- rc = SQLITE_ERROR;
- }else{
- p->pTab->nTabRef++;
- rc = sqlite3ViewGetColumnNames(pParse, p->pTab);
+ Select *pSel = sqlite3SelectNew(
+ pParse, pStep->pExprList, pSrc, 0, 0, 0, 0, 0, 0
+ );
+ if( pSel==0 ){
+ pStep->pExprList = 0;
+ pSrc = 0;
+ rc = SQLITE_NOMEM;
+ }else{
+ sqlite3SelectPrep(pParse, pSel, 0);
+ rc = pParse->nErr ? SQLITE_ERROR : SQLITE_OK;
+ assert( pStep->pExprList==0 || pStep->pExprList==pSel->pEList );
+ assert( pSrc==pSel->pSrc );
+ if( pStep->pExprList ) pSel->pEList = 0;
+ pSel->pSrc = 0;
+ sqlite3SelectDelete(db, pSel);
+ }
+ if( pStep->pFrom ){
+ int i;
+ for(i=0; i<pStep->pFrom->nSrc && rc==SQLITE_OK; i++){
+ SrcItem *p = &pStep->pFrom->a[i];
+ if( p->pSelect ){
+ sqlite3SelectPrep(pParse, p->pSelect, 0);
}
}
}
- if( rc==SQLITE_OK && db->mallocFailed ){
+
+ if( db->mallocFailed ){
rc = SQLITE_NOMEM;
}
sNC.pSrcList = pSrc;
@@ -110663,6 +113436,15 @@ static void renameTableFunc(
if( pStep->zTarget && 0==sqlite3_stricmp(pStep->zTarget, zOld) ){
renameTokenFind(&sParse, &sCtx, pStep->zTarget);
}
+ if( pStep->pFrom ){
+ int i;
+ for(i=0; i<pStep->pFrom->nSrc; i++){
+ SrcItem *pItem = &pStep->pFrom->a[i];
+ if( 0==sqlite3_stricmp(pItem->zName, zOld) ){
+ renameTokenFind(&sParse, &sCtx, pItem->zName);
+ }
+ }
+ }
}
}
}
@@ -111986,9 +114768,14 @@ static void statGet(
** * "WHERE a=? AND b=?" matches 2 rows.
**
** If D is the count of distinct values and K is the total number of
- ** rows, then each estimate is computed as:
+ ** rows, then each estimate is usually computed as:
**
** I = (K+D-1)/D
+ **
+ ** In other words, I is K/D rounded up to the next whole integer.
+ ** However, if I is between 1.0 and 1.1 (in other words if I is
+ ** close to 1.0 but just a little larger) then do not round up but
+ ** instead keep the I value at 1.0.
*/
sqlite3_str sStat; /* Text of the constructed "stat" line */
int i; /* Loop counter */
@@ -111999,6 +114786,7 @@ static void statGet(
for(i=0; i<p->nKeyCol; i++){
u64 nDistinct = p->current.anDLt[i] + 1;
u64 iVal = (p->nRow + nDistinct - 1) / nDistinct;
+ if( iVal==2 && p->nRow*10 <= nDistinct*11 ) iVal = 1;
sqlite3_str_appendf(&sStat, " %llu", iVal);
assert( p->current.anEq[i] );
}
@@ -112086,6 +114874,7 @@ static void analyzeVdbeCommentIndexWithColumnName(
if( NEVER(i==XN_ROWID) ){
VdbeComment((v,"%s.rowid",pIdx->zName));
}else if( i==XN_EXPR ){
+ assert( pIdx->bHasExpr );
VdbeComment((v,"%s.expr(%d)",pIdx->zName, k));
}else{
VdbeComment((v,"%s.%s", pIdx->zName, pIdx->pTable->aCol[i].zCnName));
@@ -112162,7 +114951,7 @@ static void analyzeOneTable(
memcpy(pStat1->zName, "sqlite_stat1", 13);
pStat1->nCol = 3;
pStat1->iPKey = -1;
- sqlite3VdbeAddOp4(pParse->pVdbe, OP_Noop, 0, 0, 0,(char*)pStat1,P4_DYNBLOB);
+ sqlite3VdbeAddOp4(pParse->pVdbe, OP_Noop, 0, 0, 0,(char*)pStat1,P4_DYNAMIC);
}
#endif
@@ -113552,7 +116341,11 @@ static int fixSelectCb(Walker *p, Select *pSelect){
pItem->fg.fromDDL = 1;
}
#if !defined(SQLITE_OMIT_VIEW) || !defined(SQLITE_OMIT_TRIGGER)
- if( sqlite3WalkExpr(&pFix->w, pList->a[i].pOn) ) return WRC_Abort;
+ if( pList->a[i].fg.isUsing==0
+ && sqlite3WalkExpr(&pFix->w, pList->a[i].u3.pOn)
+ ){
+ return WRC_Abort;
+ }
#endif
}
if( pSelect->pWith ){
@@ -114084,6 +116877,7 @@ SQLITE_PRIVATE int sqlite3DbMaskAllZero(yDbMask m){
SQLITE_PRIVATE void sqlite3FinishCoding(Parse *pParse){
sqlite3 *db;
Vdbe *v;
+ int iDb, i;
assert( pParse->pToplevel==0 );
db = pParse->db;
@@ -114113,12 +116907,9 @@ SQLITE_PRIVATE void sqlite3FinishCoding(Parse *pParse){
if( pParse->bReturning ){
Returning *pReturning = pParse->u1.pReturning;
int addrRewind;
- int i;
int reg;
- if( NEVER(pReturning->nRetCol==0) ){
- assert( CORRUPT_DB );
- }else{
+ if( pReturning->nRetCol ){
sqlite3VdbeAddOp0(v, OP_FkCheck);
addrRewind =
sqlite3VdbeAddOp1(v, OP_Rewind, pReturning->iRetCur);
@@ -114152,76 +116943,69 @@ SQLITE_PRIVATE void sqlite3FinishCoding(Parse *pParse){
** transaction on each used database and to verify the schema cookie
** on each used database.
*/
- if( db->mallocFailed==0
- && (DbMaskNonZero(pParse->cookieMask) || pParse->pConstExpr)
- ){
- int iDb, i;
- assert( sqlite3VdbeGetOp(v, 0)->opcode==OP_Init );
- sqlite3VdbeJumpHere(v, 0);
- for(iDb=0; iDb<db->nDb; iDb++){
- Schema *pSchema;
- if( DbMaskTest(pParse->cookieMask, iDb)==0 ) continue;
- sqlite3VdbeUsesBtree(v, iDb);
- pSchema = db->aDb[iDb].pSchema;
- sqlite3VdbeAddOp4Int(v,
- OP_Transaction, /* Opcode */
- iDb, /* P1 */
- DbMaskTest(pParse->writeMask,iDb), /* P2 */
- pSchema->schema_cookie, /* P3 */
- pSchema->iGeneration /* P4 */
- );
- if( db->init.busy==0 ) sqlite3VdbeChangeP5(v, 1);
- VdbeComment((v,
- "usesStmtJournal=%d", pParse->mayAbort && pParse->isMultiWrite));
- }
+ assert( pParse->nErr>0 || sqlite3VdbeGetOp(v, 0)->opcode==OP_Init );
+ sqlite3VdbeJumpHere(v, 0);
+ assert( db->nDb>0 );
+ iDb = 0;
+ do{
+ Schema *pSchema;
+ if( DbMaskTest(pParse->cookieMask, iDb)==0 ) continue;
+ sqlite3VdbeUsesBtree(v, iDb);
+ pSchema = db->aDb[iDb].pSchema;
+ sqlite3VdbeAddOp4Int(v,
+ OP_Transaction, /* Opcode */
+ iDb, /* P1 */
+ DbMaskTest(pParse->writeMask,iDb), /* P2 */
+ pSchema->schema_cookie, /* P3 */
+ pSchema->iGeneration /* P4 */
+ );
+ if( db->init.busy==0 ) sqlite3VdbeChangeP5(v, 1);
+ VdbeComment((v,
+ "usesStmtJournal=%d", pParse->mayAbort && pParse->isMultiWrite));
+ }while( ++iDb<db->nDb );
#ifndef SQLITE_OMIT_VIRTUALTABLE
- for(i=0; i<pParse->nVtabLock; i++){
- char *vtab = (char *)sqlite3GetVTable(db, pParse->apVtabLock[i]);
- sqlite3VdbeAddOp4(v, OP_VBegin, 0, 0, 0, vtab, P4_VTAB);
- }
- pParse->nVtabLock = 0;
+ for(i=0; i<pParse->nVtabLock; i++){
+ char *vtab = (char *)sqlite3GetVTable(db, pParse->apVtabLock[i]);
+ sqlite3VdbeAddOp4(v, OP_VBegin, 0, 0, 0, vtab, P4_VTAB);
+ }
+ pParse->nVtabLock = 0;
#endif
- /* Once all the cookies have been verified and transactions opened,
- ** obtain the required table-locks. This is a no-op unless the
- ** shared-cache feature is enabled.
- */
- codeTableLocks(pParse);
+ /* Once all the cookies have been verified and transactions opened,
+ ** obtain the required table-locks. This is a no-op unless the
+ ** shared-cache feature is enabled.
+ */
+ codeTableLocks(pParse);
- /* Initialize any AUTOINCREMENT data structures required.
- */
- sqlite3AutoincrementBegin(pParse);
+ /* Initialize any AUTOINCREMENT data structures required.
+ */
+ sqlite3AutoincrementBegin(pParse);
- /* Code constant expressions that where factored out of inner loops.
- **
- ** The pConstExpr list might also contain expressions that we simply
- ** want to keep around until the Parse object is deleted. Such
- ** expressions have iConstExprReg==0. Do not generate code for
- ** those expressions, of course.
- */
- if( pParse->pConstExpr ){
- ExprList *pEL = pParse->pConstExpr;
- pParse->okConstFactor = 0;
- for(i=0; i<pEL->nExpr; i++){
- int iReg = pEL->a[i].u.iConstExprReg;
- if( iReg>0 ){
- sqlite3ExprCode(pParse, pEL->a[i].pExpr, iReg);
- }
- }
+ /* Code constant expressions that where factored out of inner loops.
+ **
+ ** The pConstExpr list might also contain expressions that we simply
+ ** want to keep around until the Parse object is deleted. Such
+ ** expressions have iConstExprReg==0. Do not generate code for
+ ** those expressions, of course.
+ */
+ if( pParse->pConstExpr ){
+ ExprList *pEL = pParse->pConstExpr;
+ pParse->okConstFactor = 0;
+ for(i=0; i<pEL->nExpr; i++){
+ int iReg = pEL->a[i].u.iConstExprReg;
+ sqlite3ExprCode(pParse, pEL->a[i].pExpr, iReg);
}
+ }
- if( pParse->bReturning ){
- Returning *pRet = pParse->u1.pReturning;
- if( NEVER(pRet->nRetCol==0) ){
- assert( CORRUPT_DB );
- }else{
- sqlite3VdbeAddOp2(v, OP_OpenEphemeral, pRet->iRetCur, pRet->nRetCol);
- }
+ if( pParse->bReturning ){
+ Returning *pRet = pParse->u1.pReturning;
+ if( pRet->nRetCol ){
+ sqlite3VdbeAddOp2(v, OP_OpenEphemeral, pRet->iRetCur, pRet->nRetCol);
}
-
- /* Finally, jump back to the beginning of the executable code. */
- sqlite3VdbeGoto(v, 1);
}
+
+ /* Finally, jump back to the beginning of the executable code. */
+ sqlite3VdbeGoto(v, 1);
}
/* Get the VDBE program ready for execution
@@ -114277,8 +117061,6 @@ SQLITE_PRIVATE void sqlite3NestedParse(Parse *pParse, const char *zFormat, ...){
memset(PARSE_TAIL(pParse), 0, PARSE_TAIL_SZ);
db->mDbFlags |= DBFLAG_PreferBuiltin;
sqlite3RunParser(pParse, zSql);
- sqlite3DbFree(db, pParse->zErrMsg);
- pParse->zErrMsg = 0;
db->mDbFlags = savedDbFlags;
sqlite3DbFree(db, zSql);
memcpy(PARSE_TAIL(pParse), saveBuf, PARSE_TAIL_SZ);
@@ -114408,7 +117190,7 @@ SQLITE_PRIVATE Table *sqlite3LocateTable(
/* If zName is the not the name of a table in the schema created using
** CREATE, then check to see if it is the name of an virtual table that
** can be an eponymous virtual table. */
- if( pParse->disableVtab==0 && db->init.busy==0 ){
+ if( (pParse->prepFlags & SQLITE_PREPARE_NO_VTAB)==0 && db->init.busy==0 ){
Module *pMod = (Module*)sqlite3HashFind(&db->aModule, zName);
if( pMod==0 && sqlite3_strnicmp(zName, "pragma_", 7)==0 ){
pMod = sqlite3PragmaVtabRegister(db, zName);
@@ -114421,7 +117203,7 @@ SQLITE_PRIVATE Table *sqlite3LocateTable(
#endif
if( flags & LOCATE_NOERR ) return 0;
pParse->checkSchema = 1;
- }else if( IsVirtual(p) && pParse->disableVtab ){
+ }else if( IsVirtual(p) && (pParse->prepFlags & SQLITE_PREPARE_NO_VTAB)!=0 ){
p = 0;
}
@@ -114730,16 +117512,17 @@ SQLITE_PRIVATE void sqlite3DeleteColumnNames(sqlite3 *db, Table *pTable){
int i;
Column *pCol;
assert( pTable!=0 );
+ assert( db!=0 );
if( (pCol = pTable->aCol)!=0 ){
for(i=0; i<pTable->nCol; i++, pCol++){
assert( pCol->zCnName==0 || pCol->hName==sqlite3StrIHash(pCol->zCnName) );
sqlite3DbFree(db, pCol->zCnName);
}
- sqlite3DbFree(db, pTable->aCol);
+ sqlite3DbNNFreeNN(db, pTable->aCol);
if( IsOrdinaryTable(pTable) ){
sqlite3ExprListDelete(db, pTable->u.tab.pDfltList);
}
- if( db==0 || db->pnBytesFreed==0 ){
+ if( db->pnBytesFreed==0 ){
pTable->aCol = 0;
pTable->nCol = 0;
if( IsOrdinaryTable(pTable) ){
@@ -114776,7 +117559,8 @@ static void SQLITE_NOINLINE deleteTable(sqlite3 *db, Table *pTable){
** a Table object that was going to be marked ephemeral. So do not check
** that no lookaside memory is used in this case either. */
int nLookaside = 0;
- if( db && !db->mallocFailed && (pTable->tabFlags & TF_Ephemeral)==0 ){
+ assert( db!=0 );
+ if( !db->mallocFailed && (pTable->tabFlags & TF_Ephemeral)==0 ){
nLookaside = sqlite3LookasideUsed(db, 0);
}
#endif
@@ -114786,7 +117570,7 @@ static void SQLITE_NOINLINE deleteTable(sqlite3 *db, Table *pTable){
pNext = pIndex->pNext;
assert( pIndex->pSchema==pTable->pSchema
|| (IsVirtual(pTable) && pIndex->idxType!=SQLITE_IDXTYPE_APPDEF) );
- if( (db==0 || db->pnBytesFreed==0) && !IsVirtual(pTable) ){
+ if( db->pnBytesFreed==0 && !IsVirtual(pTable) ){
char *zName = pIndex->zName;
TESTONLY ( Index *pOld = ) sqlite3HashInsert(
&pIndex->pSchema->idxHash, zName, 0
@@ -114823,8 +117607,9 @@ static void SQLITE_NOINLINE deleteTable(sqlite3 *db, Table *pTable){
}
SQLITE_PRIVATE void sqlite3DeleteTable(sqlite3 *db, Table *pTable){
/* Do not delete the table until the reference count reaches zero. */
+ assert( db!=0 );
if( !pTable ) return;
- if( ((!db || db->pnBytesFreed==0) && (--pTable->nTabRef)>0) ) return;
+ if( db->pnBytesFreed==0 && (--pTable->nTabRef)>0 ) return;
deleteTable(db, pTable);
}
@@ -115836,7 +118621,7 @@ SQLITE_PRIVATE void sqlite3AddPrimaryKey(
pTab->keyConf = (u8)onError;
assert( autoInc==0 || autoInc==1 );
pTab->tabFlags |= autoInc*TF_Autoincrement;
- if( pList ) pParse->iPkSortOrder = pList->a[0].sortFlags;
+ if( pList ) pParse->iPkSortOrder = pList->a[0].fg.sortFlags;
(void)sqlite3HasExplicitNulls(pParse, pList);
}else if( autoInc ){
#ifndef SQLITE_OMIT_AUTOINCREMENT
@@ -116228,7 +119013,8 @@ static int isDupColumn(Index *pIdx, int nKey, Index *pPk, int iCol){
/* Recompute the colNotIdxed field of the Index.
**
** colNotIdxed is a bitmask that has a 0 bit representing each indexed
-** columns that are within the first 63 columns of the table. The
+** columns that are within the first 63 columns of the table and a 1 for
+** all other bits (all columns that are not in the index). The
** high-order bit of colNotIdxed is always 1. All unindexed columns
** of the table have a 1.
**
@@ -116256,7 +119042,7 @@ static void recomputeColumnsNotIndexed(Index *pIdx){
}
}
pIdx->colNotIdxed = ~m;
- assert( (pIdx->colNotIdxed>>63)==1 );
+ assert( (pIdx->colNotIdxed>>63)==1 ); /* See note-20221022-a */
}
/*
@@ -116330,7 +119116,7 @@ static void convertToWithoutRowidTable(Parse *pParse, Table *pTab){
if( IN_RENAME_OBJECT ){
sqlite3RenameTokenRemap(pParse, pList->a[0].pExpr, &pTab->iPKey);
}
- pList->a[0].sortFlags = pParse->iPkSortOrder;
+ pList->a[0].fg.sortFlags = pParse->iPkSortOrder;
assert( pParse->pNewTable==pTab );
pTab->iPKey = -1;
sqlite3CreateIndex(pParse, 0, 0, 0, pList, pTab->keyConf, 0, 0, 0, 0,
@@ -116997,11 +119783,10 @@ create_view_fail:
** the columns of the view in the pTable structure. Return the number
** of errors. If an error is seen leave an error message in pParse->zErrMsg.
*/
-SQLITE_PRIVATE int sqlite3ViewGetColumnNames(Parse *pParse, Table *pTable){
+static SQLITE_NOINLINE int viewGetColumnNames(Parse *pParse, Table *pTable){
Table *pSelTab; /* A fake table from which we get the result set */
Select *pSel; /* Copy of the SELECT that implements the view */
int nErr = 0; /* Number of errors encountered */
- int n; /* Temporarily holds the number of cursors assigned */
sqlite3 *db = pParse->db; /* Database connection for malloc errors */
#ifndef SQLITE_OMIT_VIRTUALTABLE
int rc;
@@ -117023,9 +119808,10 @@ SQLITE_PRIVATE int sqlite3ViewGetColumnNames(Parse *pParse, Table *pTable){
#ifndef SQLITE_OMIT_VIEW
/* A positive nCol means the columns names for this view are
- ** already known.
+ ** already known. This routine is not called unless either the
+ ** table is virtual or nCol is zero.
*/
- if( pTable->nCol>0 ) return 0;
+ assert( pTable->nCol<=0 );
/* A negative nCol is a special marker meaning that we are currently
** trying to compute the column names. If we enter this routine with
@@ -117059,8 +119845,9 @@ SQLITE_PRIVATE int sqlite3ViewGetColumnNames(Parse *pParse, Table *pTable){
pSel = sqlite3SelectDup(db, pTable->u.view.pSelect, 0);
if( pSel ){
u8 eParseMode = pParse->eParseMode;
+ int nTab = pParse->nTab;
+ int nSelect = pParse->nSelect;
pParse->eParseMode = PARSE_MODE_NORMAL;
- n = pParse->nTab;
sqlite3SrcListAssignCursors(pParse, pSel->pSrc);
pTable->nCol = -1;
DisableLookaside;
@@ -117072,7 +119859,8 @@ SQLITE_PRIVATE int sqlite3ViewGetColumnNames(Parse *pParse, Table *pTable){
#else
pSelTab = sqlite3ResultSetOfSelect(pParse, pSel, SQLITE_AFF_NONE);
#endif
- pParse->nTab = n;
+ pParse->nTab = nTab;
+ pParse->nSelect = nSelect;
if( pSelTab==0 ){
pTable->nCol = 0;
nErr++;
@@ -117119,6 +119907,11 @@ SQLITE_PRIVATE int sqlite3ViewGetColumnNames(Parse *pParse, Table *pTable){
#endif /* SQLITE_OMIT_VIEW */
return nErr;
}
+SQLITE_PRIVATE int sqlite3ViewGetColumnNames(Parse *pParse, Table *pTable){
+ assert( pTable!=0 );
+ if( !IsVirtual(pTable) && pTable->nCol>0 ) return 0;
+ return viewGetColumnNames(pParse, pTable);
+}
#endif /* !defined(SQLITE_OMIT_VIEW) || !defined(SQLITE_OMIT_VIRTUALTABLE) */
#ifndef SQLITE_OMIT_VIEW
@@ -117817,8 +120610,8 @@ SQLITE_PRIVATE int sqlite3HasExplicitNulls(Parse *pParse, ExprList *pList){
if( pList ){
int i;
for(i=0; i<pList->nExpr; i++){
- if( pList->a[i].bNulls ){
- u8 sf = pList->a[i].sortFlags;
+ if( pList->a[i].fg.bNulls ){
+ u8 sf = pList->a[i].fg.sortFlags;
sqlite3ErrorMsg(pParse, "unsupported use of NULLS %s",
(sf==0 || sf==3) ? "FIRST" : "LAST"
);
@@ -117984,7 +120777,7 @@ SQLITE_PRIVATE void sqlite3CreateIndex(
}
if( !IN_RENAME_OBJECT ){
if( !db->init.busy ){
- if( sqlite3FindTable(db, zName, 0)!=0 ){
+ if( sqlite3FindTable(db, zName, pDb->zDbSName)!=0 ){
sqlite3ErrorMsg(pParse, "there is already a table named %s", zName);
goto exit_create_index;
}
@@ -118137,6 +120930,7 @@ SQLITE_PRIVATE void sqlite3CreateIndex(
j = XN_EXPR;
pIndex->aiColumn[i] = XN_EXPR;
pIndex->uniqNotNull = 0;
+ pIndex->bHasExpr = 1;
}else{
j = pCExpr->iColumn;
assert( j<=0x7fff );
@@ -118148,6 +120942,7 @@ SQLITE_PRIVATE void sqlite3CreateIndex(
}
if( pTab->aCol[j].colFlags & COLFLAG_VIRTUAL ){
pIndex->bHasVCol = 1;
+ pIndex->bHasExpr = 1;
}
}
pIndex->aiColumn[i] = (i16)j;
@@ -118171,7 +120966,7 @@ SQLITE_PRIVATE void sqlite3CreateIndex(
goto exit_create_index;
}
pIndex->azColl[i] = zColl;
- requestedSortOrder = pListItem->sortFlags & sortOrderMask;
+ requestedSortOrder = pListItem->fg.sortFlags & sortOrderMask;
pIndex->aSortOrder[i] = (u8)requestedSortOrder;
}
@@ -118614,18 +121409,17 @@ SQLITE_PRIVATE IdList *sqlite3IdListAppend(Parse *pParse, IdList *pList, Token *
if( pList==0 ){
pList = sqlite3DbMallocZero(db, sizeof(IdList) );
if( pList==0 ) return 0;
+ }else{
+ IdList *pNew;
+ pNew = sqlite3DbRealloc(db, pList,
+ sizeof(IdList) + pList->nId*sizeof(pList->a));
+ if( pNew==0 ){
+ sqlite3IdListDelete(db, pList);
+ return 0;
+ }
+ pList = pNew;
}
- pList->a = sqlite3ArrayAllocate(
- db,
- pList->a,
- sizeof(pList->a[0]),
- &pList->nId,
- &i
- );
- if( i<0 ){
- sqlite3IdListDelete(db, pList);
- return 0;
- }
+ i = pList->nId++;
pList->a[i].zName = sqlite3NameFromToken(db, pToken);
if( IN_RENAME_OBJECT && pList->a[i].zName ){
sqlite3RenameTokenMap(pParse, (void*)pList->a[i].zName, pToken);
@@ -118638,12 +121432,13 @@ SQLITE_PRIVATE IdList *sqlite3IdListAppend(Parse *pParse, IdList *pList, Token *
*/
SQLITE_PRIVATE void sqlite3IdListDelete(sqlite3 *db, IdList *pList){
int i;
+ assert( db!=0 );
if( pList==0 ) return;
+ assert( pList->eU4!=EU4_EXPR ); /* EU4_EXPR mode is not currently used */
for(i=0; i<pList->nId; i++){
sqlite3DbFree(db, pList->a[i].zName);
}
- sqlite3DbFree(db, pList->a);
- sqlite3DbFreeNN(db, pList);
+ sqlite3DbNNFreeNN(db, pList);
}
/*
@@ -118652,7 +121447,7 @@ SQLITE_PRIVATE void sqlite3IdListDelete(sqlite3 *db, IdList *pList){
*/
SQLITE_PRIVATE int sqlite3IdListIndex(IdList *pList, const char *zName){
int i;
- if( pList==0 ) return -1;
+ assert( pList!=0 );
for(i=0; i<pList->nId; i++){
if( sqlite3StrICmp(pList->a[i].zName, zName)==0 ) return i;
}
@@ -118846,19 +121641,23 @@ SQLITE_PRIVATE void sqlite3SrcListAssignCursors(Parse *pParse, SrcList *pList){
SQLITE_PRIVATE void sqlite3SrcListDelete(sqlite3 *db, SrcList *pList){
int i;
SrcItem *pItem;
+ assert( db!=0 );
if( pList==0 ) return;
for(pItem=pList->a, i=0; i<pList->nSrc; i++, pItem++){
- if( pItem->zDatabase ) sqlite3DbFreeNN(db, pItem->zDatabase);
- sqlite3DbFree(db, pItem->zName);
- if( pItem->zAlias ) sqlite3DbFreeNN(db, pItem->zAlias);
+ if( pItem->zDatabase ) sqlite3DbNNFreeNN(db, pItem->zDatabase);
+ if( pItem->zName ) sqlite3DbNNFreeNN(db, pItem->zName);
+ if( pItem->zAlias ) sqlite3DbNNFreeNN(db, pItem->zAlias);
if( pItem->fg.isIndexedBy ) sqlite3DbFree(db, pItem->u1.zIndexedBy);
if( pItem->fg.isTabFunc ) sqlite3ExprListDelete(db, pItem->u1.pFuncArg);
sqlite3DeleteTable(db, pItem->pTab);
if( pItem->pSelect ) sqlite3SelectDelete(db, pItem->pSelect);
- if( pItem->pOn ) sqlite3ExprDelete(db, pItem->pOn);
- if( pItem->pUsing ) sqlite3IdListDelete(db, pItem->pUsing);
+ if( pItem->fg.isUsing ){
+ sqlite3IdListDelete(db, pItem->u3.pUsing);
+ }else if( pItem->u3.pOn ){
+ sqlite3ExprDelete(db, pItem->u3.pOn);
+ }
}
- sqlite3DbFreeNN(db, pList);
+ sqlite3DbNNFreeNN(db, pList);
}
/*
@@ -118884,14 +121683,13 @@ SQLITE_PRIVATE SrcList *sqlite3SrcListAppendFromTerm(
Token *pDatabase, /* Name of the database containing pTable */
Token *pAlias, /* The right-hand side of the AS subexpression */
Select *pSubquery, /* A subquery used in place of a table name */
- Expr *pOn, /* The ON clause of a join */
- IdList *pUsing /* The USING clause of a join */
+ OnOrUsing *pOnUsing /* Either the ON clause or the USING clause */
){
SrcItem *pItem;
sqlite3 *db = pParse->db;
- if( !p && (pOn || pUsing) ){
+ if( !p && pOnUsing!=0 && (pOnUsing->pOn || pOnUsing->pUsing) ){
sqlite3ErrorMsg(pParse, "a JOIN clause is required before %s",
- (pOn ? "ON" : "USING")
+ (pOnUsing->pOn ? "ON" : "USING")
);
goto append_from_error;
}
@@ -118911,15 +121709,27 @@ SQLITE_PRIVATE SrcList *sqlite3SrcListAppendFromTerm(
if( pAlias->n ){
pItem->zAlias = sqlite3NameFromToken(db, pAlias);
}
- pItem->pSelect = pSubquery;
- pItem->pOn = pOn;
- pItem->pUsing = pUsing;
+ if( pSubquery ){
+ pItem->pSelect = pSubquery;
+ if( pSubquery->selFlags & SF_NestedFrom ){
+ pItem->fg.isNestedFrom = 1;
+ }
+ }
+ assert( pOnUsing==0 || pOnUsing->pOn==0 || pOnUsing->pUsing==0 );
+ assert( pItem->fg.isUsing==0 );
+ if( pOnUsing==0 ){
+ pItem->u3.pOn = 0;
+ }else if( pOnUsing->pUsing ){
+ pItem->fg.isUsing = 1;
+ pItem->u3.pUsing = pOnUsing->pUsing;
+ }else{
+ pItem->u3.pOn = pOnUsing->pOn;
+ }
return p;
append_from_error:
assert( p==0 );
- sqlite3ExprDelete(db, pOn);
- sqlite3IdListDelete(db, pUsing);
+ sqlite3ClearOnOrUsing(db, pOnUsing);
sqlite3SelectDelete(db, pSubquery);
return 0;
}
@@ -118964,6 +121774,7 @@ SQLITE_PRIVATE SrcList *sqlite3SrcListAppendList(Parse *pParse, SrcList *p1, Src
p1 = pNew;
memcpy(&p1->a[1], p2->a, p2->nSrc*sizeof(SrcItem));
sqlite3DbFree(pParse->db, p2);
+ p1->a[0].fg.jointype |= (JT_LTORJ & p1->a[1].fg.jointype);
}
}
return p1;
@@ -119000,14 +121811,34 @@ SQLITE_PRIVATE void sqlite3SrcListFuncArgs(Parse *pParse, SrcList *p, ExprList *
** The operator is "natural cross join". The A and B operands are stored
** in p->a[0] and p->a[1], respectively. The parser initially stores the
** operator with A. This routine shifts that operator over to B.
+**
+** Additional changes:
+**
+** * All tables to the left of the right-most RIGHT JOIN are tagged with
+** JT_LTORJ (mnemonic: Left Table Of Right Join) so that the
+** code generator can easily tell that the table is part of
+** the left operand of at least one RIGHT JOIN.
*/
-SQLITE_PRIVATE void sqlite3SrcListShiftJoinType(SrcList *p){
- if( p ){
- int i;
- for(i=p->nSrc-1; i>0; i--){
- p->a[i].fg.jointype = p->a[i-1].fg.jointype;
- }
+SQLITE_PRIVATE void sqlite3SrcListShiftJoinType(Parse *pParse, SrcList *p){
+ (void)pParse;
+ if( p && p->nSrc>1 ){
+ int i = p->nSrc-1;
+ u8 allFlags = 0;
+ do{
+ allFlags |= p->a[i].fg.jointype = p->a[i-1].fg.jointype;
+ }while( (--i)>0 );
p->a[0].fg.jointype = 0;
+
+ /* All terms to the left of a RIGHT JOIN should be tagged with the
+ ** JT_LTORJ flags */
+ if( allFlags & JT_RIGHT ){
+ for(i=p->nSrc-1; ALWAYS(i>0) && (p->a[i].fg.jointype&JT_RIGHT)==0; i--){}
+ i--;
+ assert( i>=0 );
+ do{
+ p->a[i].fg.jointype |= JT_LTORJ;
+ }while( (--i)>=0 );
+ }
}
}
@@ -120078,19 +122909,21 @@ SQLITE_PRIVATE void sqlite3SchemaClear(void *p){
Hash temp2;
HashElem *pElem;
Schema *pSchema = (Schema *)p;
+ sqlite3 xdb;
+ memset(&xdb, 0, sizeof(xdb));
temp1 = pSchema->tblHash;
temp2 = pSchema->trigHash;
sqlite3HashInit(&pSchema->trigHash);
sqlite3HashClear(&pSchema->idxHash);
for(pElem=sqliteHashFirst(&temp2); pElem; pElem=sqliteHashNext(pElem)){
- sqlite3DeleteTrigger(0, (Trigger*)sqliteHashData(pElem));
+ sqlite3DeleteTrigger(&xdb, (Trigger*)sqliteHashData(pElem));
}
sqlite3HashClear(&temp2);
sqlite3HashInit(&pSchema->tblHash);
for(pElem=sqliteHashFirst(&temp1); pElem; pElem=sqliteHashNext(pElem)){
Table *pTab = sqliteHashData(pElem);
- sqlite3DeleteTable(0, pTab);
+ sqlite3DeleteTable(&xdb, pTab);
}
sqlite3HashClear(&temp1);
sqlite3HashClear(&pSchema->fkeyHash);
@@ -120189,18 +123022,42 @@ SQLITE_PRIVATE void sqlite3CodeChangeCount(Vdbe *v, int regCounter, const char *
** 1) It is a virtual table and no implementation of the xUpdate method
** has been provided
**
-** 2) It is a system table (i.e. sqlite_schema), this call is not
+** 2) A trigger is currently being coded and the table is a virtual table
+** that is SQLITE_VTAB_DIRECTONLY or if PRAGMA trusted_schema=OFF and
+** the table is not SQLITE_VTAB_INNOCUOUS.
+**
+** 3) It is a system table (i.e. sqlite_schema), this call is not
** part of a nested parse and writable_schema pragma has not
** been specified
**
-** 3) The table is a shadow table, the database connection is in
+** 4) The table is a shadow table, the database connection is in
** defensive mode, and the current sqlite3_prepare()
** is for a top-level SQL statement.
*/
+static int vtabIsReadOnly(Parse *pParse, Table *pTab){
+ if( sqlite3GetVTable(pParse->db, pTab)->pMod->pModule->xUpdate==0 ){
+ return 1;
+ }
+
+ /* Within triggers:
+ ** * Do not allow DELETE, INSERT, or UPDATE of SQLITE_VTAB_DIRECTONLY
+ ** virtual tables
+ ** * Only allow DELETE, INSERT, or UPDATE of non-SQLITE_VTAB_INNOCUOUS
+ ** virtual tables if PRAGMA trusted_schema=ON.
+ */
+ if( pParse->pToplevel!=0
+ && pTab->u.vtab.p->eVtabRisk >
+ ((pParse->db->flags & SQLITE_TrustedSchema)!=0)
+ ){
+ sqlite3ErrorMsg(pParse, "unsafe use of virtual table \"%s\"",
+ pTab->zName);
+ }
+ return 0;
+}
static int tabIsReadOnly(Parse *pParse, Table *pTab){
sqlite3 *db;
if( IsVirtual(pTab) ){
- return sqlite3GetVTable(pParse->db, pTab)->pMod->pModule->xUpdate==0;
+ return vtabIsReadOnly(pParse, pTab);
}
if( (pTab->tabFlags & (TF_Readonly|TF_Shadow))==0 ) return 0;
db = pParse->db;
@@ -120212,9 +123069,11 @@ static int tabIsReadOnly(Parse *pParse, Table *pTab){
}
/*
-** Check to make sure the given table is writable. If it is not
-** writable, generate an error message and return 1. If it is
-** writable return 0;
+** Check to make sure the given table is writable.
+**
+** If pTab is not writable -> generate an error message and return 1.
+** If pTab is writable but other errors have occurred -> return 1.
+** If pTab is writable and no prior errors -> return 0;
*/
SQLITE_PRIVATE int sqlite3IsReadOnly(Parse *pParse, Table *pTab, int viewOk){
if( tabIsReadOnly(pParse, pTab) ){
@@ -120256,8 +123115,8 @@ SQLITE_PRIVATE void sqlite3MaterializeView(
assert( pFrom->nSrc==1 );
pFrom->a[0].zName = sqlite3DbStrDup(db, pView->zName);
pFrom->a[0].zDatabase = sqlite3DbStrDup(db, db->aDb[iDb].zDbSName);
- assert( pFrom->a[0].pOn==0 );
- assert( pFrom->a[0].pUsing==0 );
+ assert( pFrom->a[0].fg.isUsing==0 );
+ assert( pFrom->a[0].u3.pOn==0 );
}
pSel = sqlite3SelectNew(pParse, 0, pFrom, pWhere, 0, 0, pOrderBy,
SF_IncludeHidden, pLimit);
@@ -120428,7 +123287,6 @@ SQLITE_PRIVATE void sqlite3DeleteFrom(
assert( db->mallocFailed==0 );
assert( pTabList->nSrc==1 );
-
/* Locate the table which we want to delete. This table has to be
** put in an SrcList structure because some of the subroutines we
** will be calling are designed to work with multiple tables and expect
@@ -120453,6 +123311,14 @@ SQLITE_PRIVATE void sqlite3DeleteFrom(
# define isView 0
#endif
+#if TREETRACE_ENABLED
+ if( sqlite3TreeTrace & 0x10000 ){
+ sqlite3TreeViewLine(0, "In sqlite3Delete() at %s:%d", __FILE__, __LINE__);
+ sqlite3TreeViewDelete(pParse->pWith, pTabList, pWhere,
+ pOrderBy, pLimit, pTrigger);
+ }
+#endif
+
#ifdef SQLITE_ENABLE_UPDATE_DELETE_LIMIT
if( !isView ){
pWhere = sqlite3LimitWhere(
@@ -120568,9 +123434,10 @@ SQLITE_PRIVATE void sqlite3DeleteFrom(
}
for(pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext){
assert( pIdx->pSchema==pTab->pSchema );
- sqlite3VdbeAddOp2(v, OP_Clear, pIdx->tnum, iDb);
if( IsPrimaryKeyIndex(pIdx) && !HasRowid(pTab) ){
- sqlite3VdbeChangeP3(v, -1, memCnt ? memCnt : -1);
+ sqlite3VdbeAddOp3(v, OP_Clear, pIdx->tnum, iDb, memCnt ? memCnt : -1);
+ }else{
+ sqlite3VdbeAddOp2(v, OP_Clear, pIdx->tnum, iDb);
}
}
}else
@@ -120770,7 +123637,7 @@ delete_from_cleanup:
sqlite3ExprListDelete(db, pOrderBy);
sqlite3ExprDelete(db, pLimit);
#endif
- sqlite3DbFree(db, aToOpen);
+ if( aToOpen ) sqlite3DbNNFreeNN(db, aToOpen);
return;
}
/* Make sure "isView" and other macros defined above are undefined. Otherwise
@@ -121853,7 +124720,7 @@ static int patternCompare(
** c but in the other case and search the input string for either
** c or cx.
*/
- if( c<=0x80 ){
+ if( c<0x80 ){
char zStop[3];
int bMatch;
if( noCase ){
@@ -121936,7 +124803,13 @@ static int patternCompare(
** non-zero if there is no match.
*/
SQLITE_API int sqlite3_strglob(const char *zGlobPattern, const char *zString){
- return patternCompare((u8*)zGlobPattern, (u8*)zString, &globInfo, '[');
+ if( zString==0 ){
+ return zGlobPattern!=0;
+ }else if( zGlobPattern==0 ){
+ return 1;
+ }else {
+ return patternCompare((u8*)zGlobPattern, (u8*)zString, &globInfo, '[');
+ }
}
/*
@@ -121944,7 +124817,13 @@ SQLITE_API int sqlite3_strglob(const char *zGlobPattern, const char *zString){
** a miss - like strcmp().
*/
SQLITE_API int sqlite3_strlike(const char *zPattern, const char *zStr, unsigned int esc){
- return patternCompare((u8*)zPattern, (u8*)zStr, &likeInfoNorm, esc);
+ if( zStr==0 ){
+ return zPattern!=0;
+ }else if( zPattern==0 ){
+ return 1;
+ }else{
+ return patternCompare((u8*)zPattern, (u8*)zStr, &likeInfoNorm, esc);
+ }
}
/*
@@ -123211,11 +126090,11 @@ static void logFunc(
switch( SQLITE_PTR_TO_INT(sqlite3_user_data(context)) ){
case 1:
/* Convert from natural logarithm to log base 10 */
- ans *= 1.0/M_LN10;
+ ans /= M_LN10;
break;
case 2:
/* Convert from natural logarithm to log base 2 */
- ans *= 1.0/M_LN2;
+ ans /= M_LN2;
break;
default:
break;
@@ -123354,8 +126233,7 @@ SQLITE_PRIVATE void sqlite3RegisterBuiltinFunctions(void){
INLINE_FUNC(likelihood, 2, INLINEFUNC_unlikely, SQLITE_FUNC_UNLIKELY),
INLINE_FUNC(likely, 1, INLINEFUNC_unlikely, SQLITE_FUNC_UNLIKELY),
#ifdef SQLITE_ENABLE_OFFSET_SQL_FUNC
- {1, SQLITE_FUNC_BUILTIN|SQLITE_UTF8|SQLITE_FUNC_OFFSET|SQLITE_FUNC_TYPEOF,
- 0, 0, noopFunc, 0, 0, 0, "sqlite_offset", {0} },
+ INLINE_FUNC(sqlite_offset, 1, INLINEFUNC_sqlite_offset, 0 ),
#endif
FUNCTION(ltrim, 1, 1, 0, trimFunc ),
FUNCTION(ltrim, 2, 1, 0, trimFunc ),
@@ -123890,7 +126768,6 @@ static void fkLookupParent(
}else{
int nCol = pFKey->nCol;
int regTemp = sqlite3GetTempRange(pParse, nCol);
- int regRec = sqlite3GetTempReg(pParse);
sqlite3VdbeAddOp3(v, OP_OpenRead, iCur, pIdx->tnum, iDb);
sqlite3VdbeSetP4KeyInfo(pParse, pIdx);
@@ -123930,11 +126807,10 @@ static void fkLookupParent(
sqlite3VdbeGoto(v, iOk);
}
- sqlite3VdbeAddOp4(v, OP_MakeRecord, regTemp, nCol, regRec,
+ sqlite3VdbeAddOp4(v, OP_Affinity, regTemp, nCol, 0,
sqlite3IndexAffinityStr(pParse->db,pIdx), nCol);
- sqlite3VdbeAddOp4Int(v, OP_Found, iCur, iOk, regRec, 0); VdbeCoverage(v);
-
- sqlite3ReleaseTempReg(pParse, regRec);
+ sqlite3VdbeAddOp4Int(v, OP_Found, iCur, iOk, regTemp, nCol);
+ VdbeCoverage(v);
sqlite3ReleaseTempRange(pParse, regTemp, nCol);
}
}
@@ -124036,14 +126912,10 @@ static Expr *exprTableColumn(
** Operation | FK type | Action taken
** --------------------------------------------------------------------------
** DELETE immediate Increment the "immediate constraint counter".
-** Or, if the ON (UPDATE|DELETE) action is RESTRICT,
-** throw a "FOREIGN KEY constraint failed" exception.
**
** INSERT immediate Decrement the "immediate constraint counter".
**
** DELETE deferred Increment the "deferred constraint counter".
-** Or, if the ON (UPDATE|DELETE) action is RESTRICT,
-** throw a "FOREIGN KEY constraint failed" exception.
**
** INSERT deferred Decrement the "deferred constraint counter".
**
@@ -124691,9 +127563,9 @@ SQLITE_PRIVATE int sqlite3FkRequired(
**
** It returns a pointer to a Trigger structure containing a trigger
** equivalent to the ON UPDATE or ON DELETE action specified by pFKey.
-** If the action is "NO ACTION" or "RESTRICT", then a NULL pointer is
-** returned (these actions require no special handling by the triggers
-** sub-system, code for them is created by fkScanChildren()).
+** If the action is "NO ACTION" then a NULL pointer is returned (these actions
+** require no special handling by the triggers sub-system, code for them is
+** created by fkScanChildren()).
**
** For example, if pFKey is the foreign key and pTab is table "p" in
** the following schema:
@@ -124822,18 +127694,23 @@ static Trigger *fkActionTrigger(
nFrom = sqlite3Strlen30(zFrom);
if( action==OE_Restrict ){
+ int iDb = sqlite3SchemaToIndex(db, pTab->pSchema);
Token tFrom;
+ Token tDb;
Expr *pRaise;
tFrom.z = zFrom;
tFrom.n = nFrom;
+ tDb.z = db->aDb[iDb].zDbSName;
+ tDb.n = sqlite3Strlen30(tDb.z);
+
pRaise = sqlite3Expr(db, TK_RAISE, "FOREIGN KEY constraint failed");
if( pRaise ){
pRaise->affExpr = OE_Abort;
}
pSelect = sqlite3SelectNew(pParse,
sqlite3ExprListAppend(pParse, 0, pRaise),
- sqlite3SrcListAppend(pParse, 0, &tFrom, 0),
+ sqlite3SrcListAppend(pParse, 0, &tDb, &tFrom),
pWhere,
0, 0, 0, 0, 0
);
@@ -124940,11 +127817,12 @@ SQLITE_PRIVATE void sqlite3FkDelete(sqlite3 *db, Table *pTab){
FKey *pNext; /* Copy of pFKey->pNextFrom */
assert( IsOrdinaryTable(pTab) );
+ assert( db!=0 );
for(pFKey=pTab->u.tab.pFKey; pFKey; pFKey=pNext){
assert( db==0 || sqlite3SchemaMutexHeld(db, 0, pTab->pSchema) );
/* Remove the FK from the fkeyHash hash table. */
- if( !db || db->pnBytesFreed==0 ){
+ if( db->pnBytesFreed==0 ){
if( pFKey->pPrevTo ){
pFKey->pPrevTo->pNextTo = pFKey->pNextTo;
}else{
@@ -125074,6 +127952,7 @@ SQLITE_PRIVATE const char *sqlite3IndexAffinityStr(sqlite3 *db, Index *pIdx){
aff = SQLITE_AFF_INTEGER;
}else{
assert( x==XN_EXPR );
+ assert( pIdx->bHasExpr );
assert( pIdx->aColExpr!=0 );
aff = sqlite3ExprAffinity(pIdx->aColExpr->a[n].pExpr);
}
@@ -125088,6 +127967,28 @@ SQLITE_PRIVATE const char *sqlite3IndexAffinityStr(sqlite3 *db, Index *pIdx){
}
/*
+** Compute an affinity string for a table. Space is obtained
+** from sqlite3DbMalloc(). The caller is responsible for freeing
+** the space when done.
+*/
+SQLITE_PRIVATE char *sqlite3TableAffinityStr(sqlite3 *db, const Table *pTab){
+ char *zColAff;
+ zColAff = (char *)sqlite3DbMallocRaw(db, pTab->nCol+1);
+ if( zColAff ){
+ int i, j;
+ for(i=j=0; i<pTab->nCol; i++){
+ if( (pTab->aCol[i].colFlags & COLFLAG_VIRTUAL)==0 ){
+ zColAff[j++] = pTab->aCol[i].affinity;
+ }
+ }
+ do{
+ zColAff[j--] = 0;
+ }while( j>=0 && zColAff[j]<=SQLITE_AFF_BLOB );
+ }
+ return zColAff;
+}
+
+/*
** Make changes to the evolving bytecode to do affinity transformations
** of values that are about to be gathered into a row for table pTab.
**
@@ -125128,7 +128029,7 @@ SQLITE_PRIVATE const char *sqlite3IndexAffinityStr(sqlite3 *db, Index *pIdx){
** Apply the type checking to that array of registers.
*/
SQLITE_PRIVATE void sqlite3TableAffinity(Vdbe *v, Table *pTab, int iReg){
- int i, j;
+ int i;
char *zColAff;
if( pTab->tabFlags & TF_Strict ){
if( iReg==0 ){
@@ -125137,7 +128038,7 @@ SQLITE_PRIVATE void sqlite3TableAffinity(Vdbe *v, Table *pTab, int iReg){
** OP_MakeRecord is found */
VdbeOp *pPrev;
sqlite3VdbeAppendP4(v, pTab, P4_TABLE);
- pPrev = sqlite3VdbeGetOp(v, -1);
+ pPrev = sqlite3VdbeGetLastOp(v);
assert( pPrev!=0 );
assert( pPrev->opcode==OP_MakeRecord || sqlite3VdbeDb(v)->mallocFailed );
pPrev->opcode = OP_TypeCheck;
@@ -125151,22 +128052,11 @@ SQLITE_PRIVATE void sqlite3TableAffinity(Vdbe *v, Table *pTab, int iReg){
}
zColAff = pTab->zColAff;
if( zColAff==0 ){
- sqlite3 *db = sqlite3VdbeDb(v);
- zColAff = (char *)sqlite3DbMallocRaw(0, pTab->nCol+1);
+ zColAff = sqlite3TableAffinityStr(0, pTab);
if( !zColAff ){
- sqlite3OomFault(db);
+ sqlite3OomFault(sqlite3VdbeDb(v));
return;
}
-
- for(i=j=0; i<pTab->nCol; i++){
- assert( pTab->aCol[i].affinity!=0 || sqlite3VdbeParser(v)->nErr>0 );
- if( (pTab->aCol[i].colFlags & COLFLAG_VIRTUAL)==0 ){
- zColAff[j++] = pTab->aCol[i].affinity;
- }
- }
- do{
- zColAff[j--] = 0;
- }while( j>=0 && zColAff[j]<=SQLITE_AFF_BLOB );
pTab->zColAff = zColAff;
}
assert( zColAff!=0 );
@@ -125175,7 +128065,7 @@ SQLITE_PRIVATE void sqlite3TableAffinity(Vdbe *v, Table *pTab, int iReg){
if( iReg ){
sqlite3VdbeAddOp4(v, OP_Affinity, iReg, i, 0, zColAff, i);
}else{
- assert( sqlite3VdbeGetOp(v, -1)->opcode==OP_MakeRecord
+ assert( sqlite3VdbeGetLastOp(v)->opcode==OP_MakeRecord
|| sqlite3VdbeDb(v)->mallocFailed );
sqlite3VdbeChangeP4(v, -1, zColAff, i);
}
@@ -125261,7 +128151,7 @@ SQLITE_PRIVATE void sqlite3ComputeGeneratedColumns(
*/
sqlite3TableAffinity(pParse->pVdbe, pTab, iRegStore);
if( (pTab->tabFlags & TF_HasStored)!=0 ){
- pOp = sqlite3VdbeGetOp(pParse->pVdbe,-1);
+ pOp = sqlite3VdbeGetLastOp(pParse->pVdbe);
if( pOp->opcode==OP_Affinity ){
/* Change the OP_Affinity argument to '@' (NONE) for all stored
** columns. '@' is the no-op affinity and those columns have not
@@ -125743,6 +128633,14 @@ SQLITE_PRIVATE void sqlite3Insert(
#endif
assert( (pTrigger && tmask) || (pTrigger==0 && tmask==0) );
+#if TREETRACE_ENABLED
+ if( sqlite3TreeTrace & 0x10000 ){
+ sqlite3TreeViewLine(0, "In sqlite3Insert() at %s:%d", __FILE__, __LINE__);
+ sqlite3TreeViewInsert(pParse->pWith, pTabList, pColumn, pSelect, pList,
+ onError, pUpsert, pTrigger);
+ }
+#endif
+
/* If pTab is really a view, make sure it has been initialized.
** ViewGetColumnNames() is a no-op if pTab is not a view.
*/
@@ -125821,13 +128719,15 @@ SQLITE_PRIVATE void sqlite3Insert(
*/
bIdListInOrder = (pTab->tabFlags & (TF_OOOHidden|TF_HasStored))==0;
if( pColumn ){
+ assert( pColumn->eU4!=EU4_EXPR );
+ pColumn->eU4 = EU4_IDX;
for(i=0; i<pColumn->nId; i++){
- pColumn->a[i].idx = -1;
+ pColumn->a[i].u4.idx = -1;
}
for(i=0; i<pColumn->nId; i++){
for(j=0; j<pTab->nCol; j++){
if( sqlite3StrICmp(pColumn->a[i].zName, pTab->aCol[j].zCnName)==0 ){
- pColumn->a[i].idx = j;
+ pColumn->a[i].u4.idx = j;
if( i!=j ) bIdListInOrder = 0;
if( j==pTab->iPKey ){
ipkColumn = i; assert( !withoutRowid );
@@ -126129,7 +129029,8 @@ SQLITE_PRIVATE void sqlite3Insert(
}
}
if( pColumn ){
- for(j=0; j<pColumn->nId && pColumn->a[j].idx!=i; j++){}
+ assert( pColumn->eU4==EU4_IDX );
+ for(j=0; j<pColumn->nId && pColumn->a[j].u4.idx!=i; j++){}
if( j>=pColumn->nId ){
/* A column not named in the insert column list gets its
** default value */
@@ -126156,7 +129057,12 @@ SQLITE_PRIVATE void sqlite3Insert(
sqlite3VdbeAddOp2(v, OP_SCopy, regFromSelect+k, iRegStore);
}
}else{
- sqlite3ExprCode(pParse, pList->a[k].pExpr, iRegStore);
+ Expr *pX = pList->a[k].pExpr;
+ int y = sqlite3ExprCodeTarget(pParse, pX, iRegStore);
+ if( y!=iRegStore ){
+ sqlite3VdbeAddOp2(v,
+ ExprHasProperty(pX, EP_Subquery) ? OP_Copy : OP_SCopy, y, iRegStore);
+ }
}
}
@@ -126293,7 +129199,9 @@ SQLITE_PRIVATE void sqlite3Insert(
sqlite3GenerateConstraintChecks(pParse, pTab, aRegIdx, iDataCur, iIdxCur,
regIns, 0, ipkColumn>=0, onError, endOfLoop, &isReplace, 0, pUpsert
);
- sqlite3FkCheck(pParse, pTab, 0, regIns, 0, 0);
+ if( db->flags & SQLITE_ForeignKeys ){
+ sqlite3FkCheck(pParse, pTab, 0, regIns, 0, 0);
+ }
/* Set the OPFLAG_USESEEKRESULT flag if either (a) there are no REPLACE
** constraints or (b) there are no triggers and this table is not a
@@ -126377,7 +129285,7 @@ insert_cleanup:
sqlite3UpsertDelete(db, pUpsert);
sqlite3SelectDelete(db, pSelect);
sqlite3IdListDelete(db, pColumn);
- sqlite3DbFree(db, aRegIdx);
+ if( aRegIdx ) sqlite3DbNNFreeNN(db, aRegIdx);
}
/* Make sure "isView" and other macros defined above are undefined. Otherwise
@@ -127260,7 +130168,7 @@ SQLITE_PRIVATE void sqlite3GenerateConstraintChecks(
if( isUpdate ){
/* If currently processing the PRIMARY KEY of a WITHOUT ROWID
** table, only conflict if the new PRIMARY KEY values are actually
- ** different from the old.
+ ** different from the old. See TH3 withoutrowid04.test.
**
** For a UNIQUE index, only conflict if the PRIMARY KEY values
** of the matched index row are different from the original PRIMARY
@@ -128604,9 +131512,9 @@ struct sqlite3_api_routines {
const char *(*filename_journal)(const char*);
const char *(*filename_wal)(const char*);
/* Version 3.32.0 and later */
- char *(*create_filename)(const char*,const char*,const char*,
+ const char *(*create_filename)(const char*,const char*,const char*,
int,const char**);
- void (*free_filename)(char*);
+ void (*free_filename)(const char*);
sqlite3_file *(*database_file_object)(const char*);
/* Version 3.34.0 and later */
int (*txn_state)(sqlite3*,const char*);
@@ -128624,6 +131532,14 @@ struct sqlite3_api_routines {
int (*vtab_in)(sqlite3_index_info*,int,int);
int (*vtab_in_first)(sqlite3_value*,sqlite3_value**);
int (*vtab_in_next)(sqlite3_value*,sqlite3_value**);
+ /* Version 3.39.0 and later */
+ int (*deserialize)(sqlite3*,const char*,unsigned char*,
+ sqlite3_int64,sqlite3_int64,unsigned);
+ unsigned char *(*serialize)(sqlite3*,const char *,sqlite3_int64*,
+ unsigned int);
+ const char *(*db_name)(sqlite3*,int);
+ /* Version 3.40.0 and later */
+ int (*value_encoding)(sqlite3_value*);
};
/*
@@ -128942,6 +131858,14 @@ typedef int (*sqlite3_loadext_entry)(
#define sqlite3_vtab_in sqlite3_api->vtab_in
#define sqlite3_vtab_in_first sqlite3_api->vtab_in_first
#define sqlite3_vtab_in_next sqlite3_api->vtab_in_next
+/* Version 3.39.0 and later */
+#ifndef SQLITE_OMIT_DESERIALIZE
+#define sqlite3_deserialize sqlite3_api->deserialize
+#define sqlite3_serialize sqlite3_api->serialize
+#endif
+#define sqlite3_db_name sqlite3_api->db_name
+/* Version 3.40.0 and later */
+#define sqlite3_value_encoding sqlite3_api->value_encoding
#endif /* !defined(SQLITE_CORE) && !defined(SQLITE_OMIT_LOAD_EXTENSION) */
#if !defined(SQLITE_CORE) && !defined(SQLITE_OMIT_LOAD_EXTENSION)
@@ -129433,11 +132357,30 @@ static const sqlite3_api_routines sqlite3Apis = {
sqlite3_autovacuum_pages,
/* Version 3.38.0 and later */
sqlite3_error_offset,
+#ifndef SQLITE_OMIT_VIRTUALTABLE
sqlite3_vtab_rhs_value,
sqlite3_vtab_distinct,
sqlite3_vtab_in,
sqlite3_vtab_in_first,
- sqlite3_vtab_in_next
+ sqlite3_vtab_in_next,
+#else
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+#endif
+ /* Version 3.39.0 and later */
+#ifndef SQLITE_OMIT_DESERIALIZE
+ sqlite3_deserialize,
+ sqlite3_serialize,
+#else
+ 0,
+ 0,
+#endif
+ sqlite3_db_name,
+ /* Version 3.40.0 and later */
+ sqlite3_value_encoding
};
/* True if x is the directory separator character
@@ -130108,7 +133051,7 @@ static const PragmaName aPragmaName[] = {
#if !defined(SQLITE_OMIT_SCHEMA_PRAGMAS)
{/* zName: */ "database_list",
/* ePragTyp: */ PragTyp_DATABASE_LIST,
- /* ePragFlg: */ PragFlg_NeedSchema|PragFlg_Result0,
+ /* ePragFlg: */ PragFlg_Result0,
/* ColNames: */ 47, 3,
/* iArg: */ 0 },
#endif
@@ -130796,15 +133739,16 @@ static void pragmaFunclistLine(
int isBuiltin, /* True if this is a built-in function */
int showInternFuncs /* True if showing internal functions */
){
+ u32 mask =
+ SQLITE_DETERMINISTIC |
+ SQLITE_DIRECTONLY |
+ SQLITE_SUBTYPE |
+ SQLITE_INNOCUOUS |
+ SQLITE_FUNC_INTERNAL
+ ;
+ if( showInternFuncs ) mask = 0xffffffff;
for(; p; p=p->pNext){
const char *zType;
- static const u32 mask =
- SQLITE_DETERMINISTIC |
- SQLITE_DIRECTONLY |
- SQLITE_SUBTYPE |
- SQLITE_INNOCUOUS |
- SQLITE_FUNC_INTERNAL
- ;
static const char *azEnc[] = { 0, "utf8", "utf16le", "utf16be" };
assert( SQLITE_FUNC_ENCMASK==0x3 );
@@ -131296,7 +134240,7 @@ SQLITE_PRIVATE void sqlite3Pragma(
*/
#ifndef SQLITE_OMIT_AUTOVACUUM
case PragTyp_INCREMENTAL_VACUUM: {
- int iLimit, addr;
+ int iLimit = 0, addr;
if( zRight==0 || !sqlite3GetInt32(zRight, &iLimit) || iLimit<=0 ){
iLimit = 0x7fffffff;
}
@@ -131453,6 +134397,7 @@ SQLITE_PRIVATE void sqlite3Pragma(
**
*/
case PragTyp_TEMP_STORE_DIRECTORY: {
+ sqlite3_mutex_enter(sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_TEMPDIR));
if( !zRight ){
returnSingleText(v, sqlite3_temp_directory);
}else{
@@ -131462,6 +134407,7 @@ SQLITE_PRIVATE void sqlite3Pragma(
rc = sqlite3OsAccess(db->pVfs, zRight, SQLITE_ACCESS_READWRITE, &res);
if( rc!=SQLITE_OK || res==0 ){
sqlite3ErrorMsg(pParse, "not a writable directory");
+ sqlite3_mutex_leave(sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_TEMPDIR));
goto pragma_out;
}
}
@@ -131479,6 +134425,7 @@ SQLITE_PRIVATE void sqlite3Pragma(
}
#endif /* SQLITE_OMIT_WSD */
}
+ sqlite3_mutex_leave(sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_TEMPDIR));
break;
}
@@ -131497,6 +134444,7 @@ SQLITE_PRIVATE void sqlite3Pragma(
**
*/
case PragTyp_DATA_STORE_DIRECTORY: {
+ sqlite3_mutex_enter(sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_TEMPDIR));
if( !zRight ){
returnSingleText(v, sqlite3_data_directory);
}else{
@@ -131506,6 +134454,7 @@ SQLITE_PRIVATE void sqlite3Pragma(
rc = sqlite3OsAccess(db->pVfs, zRight, SQLITE_ACCESS_READWRITE, &res);
if( rc!=SQLITE_OK || res==0 ){
sqlite3ErrorMsg(pParse, "not a writable directory");
+ sqlite3_mutex_leave(sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_TEMPDIR));
goto pragma_out;
}
}
@@ -131517,6 +134466,7 @@ SQLITE_PRIVATE void sqlite3Pragma(
}
#endif /* SQLITE_OMIT_WSD */
}
+ sqlite3_mutex_leave(sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_TEMPDIR));
break;
}
#endif
@@ -131984,7 +134934,6 @@ SQLITE_PRIVATE void sqlite3Pragma(
HashElem *k; /* Loop counter: Next table in schema */
int x; /* result variable */
int regResult; /* 3 registers to hold a result row */
- int regKey; /* Register to hold key for checking the FK */
int regRow; /* Registers to hold a row from pTab */
int addrTop; /* Top of a loop checking foreign keys */
int addrOk; /* Jump here if the key is OK */
@@ -131992,7 +134941,6 @@ SQLITE_PRIVATE void sqlite3Pragma(
regResult = pParse->nMem+1;
pParse->nMem += 4;
- regKey = ++pParse->nMem;
regRow = ++pParse->nMem;
k = sqliteHashFirst(&db->aDb[iDb].pSchema->tblHash);
while( k ){
@@ -132059,9 +135007,9 @@ SQLITE_PRIVATE void sqlite3Pragma(
/* Generate code to query the parent index for a matching parent
** key. If a match is found, jump to addrOk. */
if( pIdx ){
- sqlite3VdbeAddOp4(v, OP_MakeRecord, regRow, pFK->nCol, regKey,
+ sqlite3VdbeAddOp4(v, OP_Affinity, regRow, pFK->nCol, 0,
sqlite3IndexAffinityStr(db,pIdx), pFK->nCol);
- sqlite3VdbeAddOp4Int(v, OP_Found, i, addrOk, regKey, 0);
+ sqlite3VdbeAddOp4Int(v, OP_Found, i, addrOk, regRow, pFK->nCol);
VdbeCoverage(v);
}else if( pParent ){
int jmp = sqlite3VdbeCurrentAddr(v)+2;
@@ -132232,15 +135180,24 @@ SQLITE_PRIVATE void sqlite3Pragma(
for(x=sqliteHashFirst(pTbls); x; x=sqliteHashNext(x)){
Table *pTab = sqliteHashData(x);
Index *pIdx, *pPk;
- Index *pPrior = 0;
+ Index *pPrior = 0; /* Previous index */
int loopTop;
int iDataCur, iIdxCur;
int r1 = -1;
- int bStrict;
+ int bStrict; /* True for a STRICT table */
+ int r2; /* Previous key for WITHOUT ROWID tables */
+ int mxCol; /* Maximum non-virtual column number */
if( !IsOrdinaryTable(pTab) ) continue;
if( pObjTab && pObjTab!=pTab ) continue;
- pPk = HasRowid(pTab) ? 0 : sqlite3PrimaryKeyIndex(pTab);
+ if( isQuick || HasRowid(pTab) ){
+ pPk = 0;
+ r2 = 0;
+ }else{
+ pPk = sqlite3PrimaryKeyIndex(pTab);
+ r2 = sqlite3GetTempRange(pParse, pPk->nKeyCol);
+ sqlite3VdbeAddOp3(v, OP_Null, 1, r2, r2+pPk->nKeyCol-1);
+ }
sqlite3OpenTableAndIndices(pParse, pTab, OP_OpenRead, 0,
1, 0, &iDataCur, &iIdxCur);
/* reg[7] counts the number of entries in the table.
@@ -132254,52 +135211,157 @@ SQLITE_PRIVATE void sqlite3Pragma(
assert( sqlite3NoTempsInRange(pParse,1,7+j) );
sqlite3VdbeAddOp2(v, OP_Rewind, iDataCur, 0); VdbeCoverage(v);
loopTop = sqlite3VdbeAddOp2(v, OP_AddImm, 7, 1);
+
+ /* Fetch the right-most column from the table. This will cause
+ ** the entire record header to be parsed and sanity checked. It
+ ** will also prepopulate the cursor column cache that is used
+ ** by the OP_IsType code, so it is a required step.
+ */
+ mxCol = pTab->nCol-1;
+ while( mxCol>=0
+ && ((pTab->aCol[mxCol].colFlags & COLFLAG_VIRTUAL)!=0
+ || pTab->iPKey==mxCol) ) mxCol--;
+ if( mxCol>=0 ){
+ sqlite3ExprCodeGetColumnOfTable(v, pTab, iDataCur, mxCol, 3);
+ sqlite3VdbeTypeofColumn(v, 3);
+ }
+
if( !isQuick ){
- /* Sanity check on record header decoding */
- sqlite3VdbeAddOp3(v, OP_Column, iDataCur, pTab->nNVCol-1,3);
- sqlite3VdbeChangeP5(v, OPFLAG_TYPEOFARG);
- VdbeComment((v, "(right-most column)"));
+ if( pPk ){
+ /* Verify WITHOUT ROWID keys are in ascending order */
+ int a1;
+ char *zErr;
+ a1 = sqlite3VdbeAddOp4Int(v, OP_IdxGT, iDataCur, 0,r2,pPk->nKeyCol);
+ VdbeCoverage(v);
+ sqlite3VdbeAddOp1(v, OP_IsNull, r2); VdbeCoverage(v);
+ zErr = sqlite3MPrintf(db,
+ "row not in PRIMARY KEY order for %s",
+ pTab->zName);
+ sqlite3VdbeAddOp4(v, OP_String8, 0, 3, 0, zErr, P4_DYNAMIC);
+ integrityCheckResultRow(v);
+ sqlite3VdbeJumpHere(v, a1);
+ sqlite3VdbeJumpHere(v, a1+1);
+ for(j=0; j<pPk->nKeyCol; j++){
+ sqlite3ExprCodeLoadIndexColumn(pParse, pPk, iDataCur, j, r2+j);
+ }
+ }
}
- /* Verify that all NOT NULL columns really are NOT NULL. At the
- ** same time verify the type of the content of STRICT tables */
+ /* Verify datatypes for all columns:
+ **
+ ** (1) NOT NULL columns may not contain a NULL
+ ** (2) Datatype must be exact for non-ANY columns in STRICT tables
+ ** (3) Datatype for TEXT columns in non-STRICT tables must be
+ ** NULL, TEXT, or BLOB.
+ ** (4) Datatype for numeric columns in non-STRICT tables must not
+ ** be a TEXT value that can be losslessly converted to numeric.
+ */
bStrict = (pTab->tabFlags & TF_Strict)!=0;
for(j=0; j<pTab->nCol; j++){
char *zErr;
- Column *pCol = pTab->aCol + j;
- int doError, jmp2;
+ Column *pCol = pTab->aCol + j; /* The column to be checked */
+ int labelError; /* Jump here to report an error */
+ int labelOk; /* Jump here if all looks ok */
+ int p1, p3, p4; /* Operands to the OP_IsType opcode */
+ int doTypeCheck; /* Check datatypes (besides NOT NULL) */
+
if( j==pTab->iPKey ) continue;
- if( pCol->notNull==0 && !bStrict ) continue;
- doError = bStrict ? sqlite3VdbeMakeLabel(pParse) : 0;
- sqlite3ExprCodeGetColumnOfTable(v, pTab, iDataCur, j, 3);
- if( sqlite3VdbeGetOp(v,-1)->opcode==OP_Column ){
- sqlite3VdbeChangeP5(v, OPFLAG_TYPEOFARG);
+ if( bStrict ){
+ doTypeCheck = pCol->eCType>COLTYPE_ANY;
+ }else{
+ doTypeCheck = pCol->affinity>SQLITE_AFF_BLOB;
}
+ if( pCol->notNull==0 && !doTypeCheck ) continue;
+
+ /* Compute the operands that will be needed for OP_IsType */
+ p4 = SQLITE_NULL;
+ if( pCol->colFlags & COLFLAG_VIRTUAL ){
+ sqlite3ExprCodeGetColumnOfTable(v, pTab, iDataCur, j, 3);
+ p1 = -1;
+ p3 = 3;
+ }else{
+ if( pCol->iDflt ){
+ sqlite3_value *pDfltValue = 0;
+ sqlite3ValueFromExpr(db, sqlite3ColumnExpr(pTab,pCol), ENC(db),
+ pCol->affinity, &pDfltValue);
+ if( pDfltValue ){
+ p4 = sqlite3_value_type(pDfltValue);
+ sqlite3ValueFree(pDfltValue);
+ }
+ }
+ p1 = iDataCur;
+ if( !HasRowid(pTab) ){
+ testcase( j!=sqlite3TableColumnToStorage(pTab, j) );
+ p3 = sqlite3TableColumnToIndex(sqlite3PrimaryKeyIndex(pTab), j);
+ }else{
+ p3 = sqlite3TableColumnToStorage(pTab,j);
+ testcase( p3!=j);
+ }
+ }
+
+ labelError = sqlite3VdbeMakeLabel(pParse);
+ labelOk = sqlite3VdbeMakeLabel(pParse);
if( pCol->notNull ){
- jmp2 = sqlite3VdbeAddOp1(v, OP_NotNull, 3); VdbeCoverage(v);
+ /* (1) NOT NULL columns may not contain a NULL */
+ int jmp2 = sqlite3VdbeAddOp4Int(v, OP_IsType, p1, labelOk, p3, p4);
+ sqlite3VdbeChangeP5(v, 0x0f);
+ VdbeCoverage(v);
zErr = sqlite3MPrintf(db, "NULL value in %s.%s", pTab->zName,
pCol->zCnName);
sqlite3VdbeAddOp4(v, OP_String8, 0, 3, 0, zErr, P4_DYNAMIC);
- if( bStrict && pCol->eCType!=COLTYPE_ANY ){
- sqlite3VdbeGoto(v, doError);
+ if( doTypeCheck ){
+ sqlite3VdbeGoto(v, labelError);
+ sqlite3VdbeJumpHere(v, jmp2);
}else{
- integrityCheckResultRow(v);
+ /* VDBE byte code will fall thru */
}
- sqlite3VdbeJumpHere(v, jmp2);
}
- if( (pTab->tabFlags & TF_Strict)!=0
- && pCol->eCType!=COLTYPE_ANY
- ){
- jmp2 = sqlite3VdbeAddOp3(v, OP_IsNullOrType, 3, 0,
- sqlite3StdTypeMap[pCol->eCType-1]);
+ if( bStrict && doTypeCheck ){
+ /* (2) Datatype must be exact for non-ANY columns in STRICT tables*/
+ static unsigned char aStdTypeMask[] = {
+ 0x1f, /* ANY */
+ 0x18, /* BLOB */
+ 0x11, /* INT */
+ 0x11, /* INTEGER */
+ 0x13, /* REAL */
+ 0x14 /* TEXT */
+ };
+ sqlite3VdbeAddOp4Int(v, OP_IsType, p1, labelOk, p3, p4);
+ assert( pCol->eCType>=1 && pCol->eCType<=sizeof(aStdTypeMask) );
+ sqlite3VdbeChangeP5(v, aStdTypeMask[pCol->eCType-1]);
VdbeCoverage(v);
zErr = sqlite3MPrintf(db, "non-%s value in %s.%s",
sqlite3StdType[pCol->eCType-1],
pTab->zName, pTab->aCol[j].zCnName);
sqlite3VdbeAddOp4(v, OP_String8, 0, 3, 0, zErr, P4_DYNAMIC);
- sqlite3VdbeResolveLabel(v, doError);
- integrityCheckResultRow(v);
- sqlite3VdbeJumpHere(v, jmp2);
+ }else if( !bStrict && pCol->affinity==SQLITE_AFF_TEXT ){
+ /* (3) Datatype for TEXT columns in non-STRICT tables must be
+ ** NULL, TEXT, or BLOB. */
+ sqlite3VdbeAddOp4Int(v, OP_IsType, p1, labelOk, p3, p4);
+ sqlite3VdbeChangeP5(v, 0x1c); /* NULL, TEXT, or BLOB */
+ VdbeCoverage(v);
+ zErr = sqlite3MPrintf(db, "NUMERIC value in %s.%s",
+ pTab->zName, pTab->aCol[j].zCnName);
+ sqlite3VdbeAddOp4(v, OP_String8, 0, 3, 0, zErr, P4_DYNAMIC);
+ }else if( !bStrict && pCol->affinity>=SQLITE_AFF_NUMERIC ){
+ /* (4) Datatype for numeric columns in non-STRICT tables must not
+ ** be a TEXT value that can be converted to numeric. */
+ sqlite3VdbeAddOp4Int(v, OP_IsType, p1, labelOk, p3, p4);
+ sqlite3VdbeChangeP5(v, 0x1b); /* NULL, INT, FLOAT, or BLOB */
+ VdbeCoverage(v);
+ if( p1>=0 ){
+ sqlite3ExprCodeGetColumnOfTable(v, pTab, iDataCur, j, 3);
+ }
+ sqlite3VdbeAddOp4(v, OP_Affinity, 3, 1, 0, "C", P4_STATIC);
+ sqlite3VdbeAddOp4Int(v, OP_IsType, -1, labelOk, 3, p4);
+ sqlite3VdbeChangeP5(v, 0x1c); /* NULL, TEXT, or BLOB */
+ VdbeCoverage(v);
+ zErr = sqlite3MPrintf(db, "TEXT value in %s.%s",
+ pTab->zName, pTab->aCol[j].zCnName);
+ sqlite3VdbeAddOp4(v, OP_String8, 0, 3, 0, zErr, P4_DYNAMIC);
}
+ sqlite3VdbeResolveLabel(v, labelError);
+ integrityCheckResultRow(v);
+ sqlite3VdbeResolveLabel(v, labelOk);
}
/* Verify CHECK constraints */
if( pTab->pCheck && (db->flags & SQLITE_IgnoreChecks)==0 ){
@@ -132387,6 +135449,9 @@ SQLITE_PRIVATE void sqlite3Pragma(
integrityCheckResultRow(v);
sqlite3VdbeJumpHere(v, addr);
}
+ if( pPk ){
+ sqlite3ReleaseTempRange(pParse, r2, pPk->nKeyCol);
+ }
}
}
}
@@ -132537,6 +135602,11 @@ SQLITE_PRIVATE void sqlite3Pragma(
aOp[1].p2 = iCookie;
aOp[1].p3 = sqlite3Atoi(zRight);
aOp[1].p5 = 1;
+ if( iCookie==BTREE_SCHEMA_VERSION && (db->flags & SQLITE_Defensive)!=0 ){
+ /* Do not allow the use of PRAGMA schema_version=VALUE in defensive
+ ** mode. Change the OP_SetCookie opcode into a no-op. */
+ aOp[1].opcode = OP_Noop;
+ }
}else{
/* Read the specified cookie value */
static const VdbeOpList readCookie[] = {
@@ -133785,15 +136855,15 @@ SQLITE_PRIVATE void sqlite3ParseObjectReset(Parse *pParse){
assert( db->pParse==pParse );
assert( pParse->nested==0 );
#ifndef SQLITE_OMIT_SHARED_CACHE
- sqlite3DbFree(db, pParse->aTableLock);
+ if( pParse->aTableLock ) sqlite3DbNNFreeNN(db, pParse->aTableLock);
#endif
while( pParse->pCleanup ){
ParseCleanup *pCleanup = pParse->pCleanup;
pParse->pCleanup = pCleanup->pNext;
pCleanup->xCleanup(db, pCleanup->pPtr);
- sqlite3DbFreeNN(db, pCleanup);
+ sqlite3DbNNFreeNN(db, pCleanup);
}
- sqlite3DbFree(db, pParse->aLabel);
+ if( pParse->aLabel ) sqlite3DbNNFreeNN(db, pParse->aLabel);
if( pParse->pConstExpr ){
sqlite3ExprListDelete(db, pParse->pConstExpr);
}
@@ -133916,7 +136986,7 @@ static int sqlite3Prepare(
sParse.disableLookaside++;
DisableLookaside;
}
- sParse.disableVtab = (prepFlags & SQLITE_PREPARE_NO_VTAB)!=0;
+ sParse.prepFlags = prepFlags & 0xff;
/* Check to verify that it is possible to get a read lock on all
** database schemas. The inability to get a read lock indicates that
@@ -133957,7 +137027,9 @@ static int sqlite3Prepare(
}
}
- sqlite3VtabUnlockList(db);
+#ifndef SQLITE_OMIT_VIRTUALTABLE
+ if( db->pDisconnect ) sqlite3VtabUnlockList(db);
+#endif
if( nBytes>=0 && (nBytes==0 || zSql[nBytes-1]!=0) ){
char *zSqlCopy;
@@ -134297,7 +137369,7 @@ SQLITE_API int sqlite3_prepare16_v3(
*/
typedef struct DistinctCtx DistinctCtx;
struct DistinctCtx {
- u8 isTnct; /* True if the DISTINCT keyword is present */
+ u8 isTnct; /* 0: Not distinct. 1: DISTICT 2: DISTINCT and ORDER BY */
u8 eTnctType; /* One of the WHERE_DISTINCT_* operators */
int tabTnct; /* Ephemeral table used for DISTINCT processing */
int addrTnct; /* Address of OP_OpenEphemeral opcode for tabTnct */
@@ -134352,6 +137424,7 @@ struct SortCtx {
** If bFree==0, Leave the first Select object unfreed
*/
static void clearSelect(sqlite3 *db, Select *p, int bFree){
+ assert( db!=0 );
while( p ){
Select *pPrior = p->pPrior;
sqlite3ExprListDelete(db, p->pEList);
@@ -134371,7 +137444,7 @@ static void clearSelect(sqlite3 *db, Select *p, int bFree){
sqlite3WindowUnlinkFromSelect(p->pWin);
}
#endif
- if( bFree ) sqlite3DbFreeNN(db, p);
+ if( bFree ) sqlite3DbNNFreeNN(db, p);
p = pPrior;
bFree = 1;
}
@@ -134480,6 +137553,52 @@ static Select *findRightmost(Select *p){
**
** If an illegal or unsupported join type is seen, then still return
** a join type, but put an error in the pParse structure.
+**
+** These are the valid join types:
+**
+**
+** pA pB pC Return Value
+** ------- ----- ----- ------------
+** CROSS - - JT_CROSS
+** INNER - - JT_INNER
+** LEFT - - JT_LEFT|JT_OUTER
+** LEFT OUTER - JT_LEFT|JT_OUTER
+** RIGHT - - JT_RIGHT|JT_OUTER
+** RIGHT OUTER - JT_RIGHT|JT_OUTER
+** FULL - - JT_LEFT|JT_RIGHT|JT_OUTER
+** FULL OUTER - JT_LEFT|JT_RIGHT|JT_OUTER
+** NATURAL INNER - JT_NATURAL|JT_INNER
+** NATURAL LEFT - JT_NATURAL|JT_LEFT|JT_OUTER
+** NATURAL LEFT OUTER JT_NATURAL|JT_LEFT|JT_OUTER
+** NATURAL RIGHT - JT_NATURAL|JT_RIGHT|JT_OUTER
+** NATURAL RIGHT OUTER JT_NATURAL|JT_RIGHT|JT_OUTER
+** NATURAL FULL - JT_NATURAL|JT_LEFT|JT_RIGHT
+** NATURAL FULL OUTER JT_NATRUAL|JT_LEFT|JT_RIGHT
+**
+** To preserve historical compatibly, SQLite also accepts a variety
+** of other non-standard and in many cases non-sensical join types.
+** This routine makes as much sense at it can from the nonsense join
+** type and returns a result. Examples of accepted nonsense join types
+** include but are not limited to:
+**
+** INNER CROSS JOIN -> same as JOIN
+** NATURAL CROSS JOIN -> same as NATURAL JOIN
+** OUTER LEFT JOIN -> same as LEFT JOIN
+** LEFT NATURAL JOIN -> same as NATURAL LEFT JOIN
+** LEFT RIGHT JOIN -> same as FULL JOIN
+** RIGHT OUTER FULL JOIN -> same as FULL JOIN
+** CROSS CROSS CROSS JOIN -> same as JOIN
+**
+** The only restrictions on the join type name are:
+**
+** * "INNER" cannot appear together with "OUTER", "LEFT", "RIGHT",
+** or "FULL".
+**
+** * "CROSS" cannot appear together with "OUTER", "LEFT", "RIGHT,
+** or "FULL".
+**
+** * If "OUTER" is present then there must also be one of
+** "LEFT", "RIGHT", or "FULL"
*/
SQLITE_PRIVATE int sqlite3JoinType(Parse *pParse, Token *pA, Token *pB, Token *pC){
int jointype = 0;
@@ -134492,13 +137611,13 @@ SQLITE_PRIVATE int sqlite3JoinType(Parse *pParse, Token *pA, Token *pB, Token *p
u8 nChar; /* Length of the keyword in characters */
u8 code; /* Join type mask */
} aKeyword[] = {
- /* natural */ { 0, 7, JT_NATURAL },
- /* left */ { 6, 4, JT_LEFT|JT_OUTER },
- /* outer */ { 10, 5, JT_OUTER },
- /* right */ { 14, 5, JT_RIGHT|JT_OUTER },
- /* full */ { 19, 4, JT_LEFT|JT_RIGHT|JT_OUTER },
- /* inner */ { 23, 5, JT_INNER },
- /* cross */ { 28, 5, JT_INNER|JT_CROSS },
+ /* (0) natural */ { 0, 7, JT_NATURAL },
+ /* (1) left */ { 6, 4, JT_LEFT|JT_OUTER },
+ /* (2) outer */ { 10, 5, JT_OUTER },
+ /* (3) right */ { 14, 5, JT_RIGHT|JT_OUTER },
+ /* (4) full */ { 19, 4, JT_LEFT|JT_RIGHT|JT_OUTER },
+ /* (5) inner */ { 23, 5, JT_INNER },
+ /* (6) cross */ { 28, 5, JT_INNER|JT_CROSS },
};
int i, j;
apAll[0] = pA;
@@ -134521,18 +137640,15 @@ SQLITE_PRIVATE int sqlite3JoinType(Parse *pParse, Token *pA, Token *pB, Token *p
}
if(
(jointype & (JT_INNER|JT_OUTER))==(JT_INNER|JT_OUTER) ||
- (jointype & JT_ERROR)!=0
+ (jointype & JT_ERROR)!=0 ||
+ (jointype & (JT_OUTER|JT_LEFT|JT_RIGHT))==JT_OUTER
){
- const char *zSp = " ";
- assert( pB!=0 );
- if( pC==0 ){ zSp++; }
- sqlite3ErrorMsg(pParse, "unknown or unsupported join type: "
- "%T %T%s%T", pA, pB, zSp, pC);
- jointype = JT_INNER;
- }else if( (jointype & JT_OUTER)!=0
- && (jointype & (JT_LEFT|JT_RIGHT))!=JT_LEFT ){
- sqlite3ErrorMsg(pParse,
- "RIGHT and FULL OUTER JOINs are not currently supported");
+ const char *zSp1 = " ";
+ const char *zSp2 = " ";
+ if( pB==0 ){ zSp1++; }
+ if( pC==0 ){ zSp2++; }
+ sqlite3ErrorMsg(pParse, "unknown join type: "
+ "%T%s%T%s%T", pA, zSp1, pB, zSp2, pC);
jointype = JT_INNER;
}
return jointype;
@@ -134553,8 +137669,25 @@ SQLITE_PRIVATE int sqlite3ColumnIndex(Table *pTab, const char *zCol){
}
/*
-** Search the first N tables in pSrc, from left to right, looking for a
-** table that has a column named zCol.
+** Mark a subquery result column as having been used.
+*/
+SQLITE_PRIVATE void sqlite3SrcItemColumnUsed(SrcItem *pItem, int iCol){
+ assert( pItem!=0 );
+ assert( (int)pItem->fg.isNestedFrom == IsNestedFrom(pItem->pSelect) );
+ if( pItem->fg.isNestedFrom ){
+ ExprList *pResults;
+ assert( pItem->pSelect!=0 );
+ pResults = pItem->pSelect->pEList;
+ assert( pResults!=0 );
+ assert( iCol>=0 && iCol<pResults->nExpr );
+ pResults->a[iCol].fg.bUsed = 1;
+ }
+}
+
+/*
+** Search the tables iStart..iEnd (inclusive) in pSrc, looking for a
+** table that has a column named zCol. The search is left-to-right.
+** The first match found is returned.
**
** When found, set *piTab and *piCol to the table index and column index
** of the matching column and return TRUE.
@@ -134563,22 +137696,27 @@ SQLITE_PRIVATE int sqlite3ColumnIndex(Table *pTab, const char *zCol){
*/
static int tableAndColumnIndex(
SrcList *pSrc, /* Array of tables to search */
- int N, /* Number of tables in pSrc->a[] to search */
+ int iStart, /* First member of pSrc->a[] to check */
+ int iEnd, /* Last member of pSrc->a[] to check */
const char *zCol, /* Name of the column we are looking for */
int *piTab, /* Write index of pSrc->a[] here */
int *piCol, /* Write index of pSrc->a[*piTab].pTab->aCol[] here */
- int bIgnoreHidden /* True to ignore hidden columns */
+ int bIgnoreHidden /* Ignore hidden columns */
){
int i; /* For looping over tables in pSrc */
int iCol; /* Index of column matching zCol */
+ assert( iEnd<pSrc->nSrc );
+ assert( iStart>=0 );
assert( (piTab==0)==(piCol==0) ); /* Both or neither are NULL */
- for(i=0; i<N; i++){
+
+ for(i=iStart; i<=iEnd; i++){
iCol = sqlite3ColumnIndex(pSrc->a[i].pTab, zCol);
if( iCol>=0
&& (bIgnoreHidden==0 || IsHiddenColumn(&pSrc->a[i].pTab->aCol[iCol])==0)
){
if( piTab ){
+ sqlite3SrcItemColumnUsed(&pSrc->a[i], iCol);
*piTab = i;
*piCol = iCol;
}
@@ -134589,66 +137727,19 @@ static int tableAndColumnIndex(
}
/*
-** This function is used to add terms implied by JOIN syntax to the
-** WHERE clause expression of a SELECT statement. The new term, which
-** is ANDed with the existing WHERE clause, is of the form:
-**
-** (tab1.col1 = tab2.col2)
-**
-** where tab1 is the iSrc'th table in SrcList pSrc and tab2 is the
-** (iSrc+1)'th. Column col1 is column iColLeft of tab1, and col2 is
-** column iColRight of tab2.
-*/
-static void addWhereTerm(
- Parse *pParse, /* Parsing context */
- SrcList *pSrc, /* List of tables in FROM clause */
- int iLeft, /* Index of first table to join in pSrc */
- int iColLeft, /* Index of column in first table */
- int iRight, /* Index of second table in pSrc */
- int iColRight, /* Index of column in second table */
- int isOuterJoin, /* True if this is an OUTER join */
- Expr **ppWhere /* IN/OUT: The WHERE clause to add to */
-){
- sqlite3 *db = pParse->db;
- Expr *pE1;
- Expr *pE2;
- Expr *pEq;
-
- assert( iLeft<iRight );
- assert( pSrc->nSrc>iRight );
- assert( pSrc->a[iLeft].pTab );
- assert( pSrc->a[iRight].pTab );
-
- pE1 = sqlite3CreateColumnExpr(db, pSrc, iLeft, iColLeft);
- pE2 = sqlite3CreateColumnExpr(db, pSrc, iRight, iColRight);
-
- pEq = sqlite3PExpr(pParse, TK_EQ, pE1, pE2);
- assert( pE2!=0 || pEq==0 ); /* Due to db->mallocFailed test
- ** in sqlite3DbMallocRawNN() called from
- ** sqlite3PExpr(). */
- if( pEq && isOuterJoin ){
- ExprSetProperty(pEq, EP_FromJoin);
- assert( !ExprHasProperty(pEq, EP_TokenOnly|EP_Reduced) );
- ExprSetVVAProperty(pEq, EP_NoReduce);
- pEq->w.iRightJoinTable = pE2->iTable;
- }
- *ppWhere = sqlite3ExprAnd(pParse, *ppWhere, pEq);
-}
-
-/*
-** Set the EP_FromJoin property on all terms of the given expression.
-** And set the Expr.w.iRightJoinTable to iTable for every term in the
+** Set the EP_OuterON property on all terms of the given expression.
+** And set the Expr.w.iJoin to iTable for every term in the
** expression.
**
-** The EP_FromJoin property is used on terms of an expression to tell
-** the LEFT OUTER JOIN processing logic that this term is part of the
+** The EP_OuterON property is used on terms of an expression to tell
+** the OUTER JOIN processing logic that this term is part of the
** join restriction specified in the ON or USING clause and not a part
** of the more general WHERE clause. These terms are moved over to the
** WHERE clause during join processing but we need to remember that they
** originated in the ON or USING clause.
**
-** The Expr.w.iRightJoinTable tells the WHERE clause processing that the
-** expression depends on table w.iRightJoinTable even if that table is not
+** The Expr.w.iJoin tells the WHERE clause processing that the
+** expression depends on table w.iJoin even if that table is not
** explicitly mentioned in the expression. That information is needed
** for cases like this:
**
@@ -134661,39 +137752,48 @@ static void addWhereTerm(
** after the t1 loop and rows with t1.x!=5 will never appear in
** the output, which is incorrect.
*/
-SQLITE_PRIVATE void sqlite3SetJoinExpr(Expr *p, int iTable){
+SQLITE_PRIVATE void sqlite3SetJoinExpr(Expr *p, int iTable, u32 joinFlag){
+ assert( joinFlag==EP_OuterON || joinFlag==EP_InnerON );
while( p ){
- ExprSetProperty(p, EP_FromJoin);
+ ExprSetProperty(p, joinFlag);
assert( !ExprHasProperty(p, EP_TokenOnly|EP_Reduced) );
ExprSetVVAProperty(p, EP_NoReduce);
- p->w.iRightJoinTable = iTable;
+ p->w.iJoin = iTable;
if( p->op==TK_FUNCTION ){
assert( ExprUseXList(p) );
if( p->x.pList ){
int i;
for(i=0; i<p->x.pList->nExpr; i++){
- sqlite3SetJoinExpr(p->x.pList->a[i].pExpr, iTable);
+ sqlite3SetJoinExpr(p->x.pList->a[i].pExpr, iTable, joinFlag);
}
}
}
- sqlite3SetJoinExpr(p->pLeft, iTable);
+ sqlite3SetJoinExpr(p->pLeft, iTable, joinFlag);
p = p->pRight;
}
}
-/* Undo the work of sqlite3SetJoinExpr(). In the expression p, convert every
-** term that is marked with EP_FromJoin and w.iRightJoinTable==iTable into
-** an ordinary term that omits the EP_FromJoin mark.
+/* Undo the work of sqlite3SetJoinExpr(). This is used when a LEFT JOIN
+** is simplified into an ordinary JOIN, and when an ON expression is
+** "pushed down" into the WHERE clause of a subquery.
+**
+** Convert every term that is marked with EP_OuterON and w.iJoin==iTable into
+** an ordinary term that omits the EP_OuterON mark. Or if iTable<0, then
+** just clear every EP_OuterON and EP_InnerON mark from the expression tree.
**
-** This happens when a LEFT JOIN is simplified into an ordinary JOIN.
+** If nullable is true, that means that Expr p might evaluate to NULL even
+** if it is a reference to a NOT NULL column. This can happen, for example,
+** if the table that p references is on the left side of a RIGHT JOIN.
+** If nullable is true, then take care to not remove the EP_CanBeNull bit.
+** See forum thread https://sqlite.org/forum/forumpost/b40696f50145d21c
*/
-static void unsetJoinExpr(Expr *p, int iTable){
+static void unsetJoinExpr(Expr *p, int iTable, int nullable){
while( p ){
- if( ExprHasProperty(p, EP_FromJoin)
- && (iTable<0 || p->w.iRightJoinTable==iTable) ){
- ExprClearProperty(p, EP_FromJoin);
+ if( iTable<0 || (ExprHasProperty(p, EP_OuterON) && p->w.iJoin==iTable) ){
+ ExprClearProperty(p, EP_OuterON|EP_InnerON);
+ if( iTable>=0 ) ExprSetProperty(p, EP_InnerON);
}
- if( p->op==TK_COLUMN && p->iTable==iTable ){
+ if( p->op==TK_COLUMN && p->iTable==iTable && !nullable ){
ExprClearProperty(p, EP_CanBeNull);
}
if( p->op==TK_FUNCTION ){
@@ -134701,30 +137801,37 @@ static void unsetJoinExpr(Expr *p, int iTable){
if( p->x.pList ){
int i;
for(i=0; i<p->x.pList->nExpr; i++){
- unsetJoinExpr(p->x.pList->a[i].pExpr, iTable);
+ unsetJoinExpr(p->x.pList->a[i].pExpr, iTable, nullable);
}
}
}
- unsetJoinExpr(p->pLeft, iTable);
+ unsetJoinExpr(p->pLeft, iTable, nullable);
p = p->pRight;
}
}
/*
** This routine processes the join information for a SELECT statement.
-** ON and USING clauses are converted into extra terms of the WHERE clause.
-** NATURAL joins also create extra WHERE clause terms.
+**
+** * A NATURAL join is converted into a USING join. After that, we
+** do not need to be concerned with NATURAL joins and we only have
+** think about USING joins.
+**
+** * ON and USING clauses result in extra terms being added to the
+** WHERE clause to enforce the specified constraints. The extra
+** WHERE clause terms will be tagged with EP_OuterON or
+** EP_InnerON so that we know that they originated in ON/USING.
**
** The terms of a FROM clause are contained in the Select.pSrc structure.
** The left most table is the first entry in Select.pSrc. The right-most
** table is the last entry. The join operator is held in the entry to
-** the left. Thus entry 0 contains the join operator for the join between
+** the right. Thus entry 1 contains the join operator for the join between
** entries 0 and 1. Any ON or USING clauses associated with the join are
-** also attached to the left entry.
+** also attached to the right entry.
**
** This routine returns the number of errors encountered.
*/
-static int sqliteProcessJoin(Parse *pParse, Select *p){
+static int sqlite3ProcessJoin(Parse *pParse, Select *p){
SrcList *pSrc; /* All tables in the FROM clause */
int i, j; /* Loop counters */
SrcItem *pLeft; /* Left table being joined */
@@ -134735,49 +137842,41 @@ static int sqliteProcessJoin(Parse *pParse, Select *p){
pRight = &pLeft[1];
for(i=0; i<pSrc->nSrc-1; i++, pRight++, pLeft++){
Table *pRightTab = pRight->pTab;
- int isOuter;
+ u32 joinType;
if( NEVER(pLeft->pTab==0 || pRightTab==0) ) continue;
- isOuter = (pRight->fg.jointype & JT_OUTER)!=0;
+ joinType = (pRight->fg.jointype & JT_OUTER)!=0 ? EP_OuterON : EP_InnerON;
- /* When the NATURAL keyword is present, add WHERE clause terms for
- ** every column that the two tables have in common.
+ /* If this is a NATURAL join, synthesize an approprate USING clause
+ ** to specify which columns should be joined.
*/
if( pRight->fg.jointype & JT_NATURAL ){
- if( pRight->pOn || pRight->pUsing ){
+ IdList *pUsing = 0;
+ if( pRight->fg.isUsing || pRight->u3.pOn ){
sqlite3ErrorMsg(pParse, "a NATURAL join may not have "
"an ON or USING clause", 0);
return 1;
}
for(j=0; j<pRightTab->nCol; j++){
char *zName; /* Name of column in the right table */
- int iLeft; /* Matching left table */
- int iLeftCol; /* Matching column in the left table */
if( IsHiddenColumn(&pRightTab->aCol[j]) ) continue;
zName = pRightTab->aCol[j].zCnName;
- if( tableAndColumnIndex(pSrc, i+1, zName, &iLeft, &iLeftCol, 1) ){
- addWhereTerm(pParse, pSrc, iLeft, iLeftCol, i+1, j,
- isOuter, &p->pWhere);
+ if( tableAndColumnIndex(pSrc, 0, i, zName, 0, 0, 1) ){
+ pUsing = sqlite3IdListAppend(pParse, pUsing, 0);
+ if( pUsing ){
+ assert( pUsing->nId>0 );
+ assert( pUsing->a[pUsing->nId-1].zName==0 );
+ pUsing->a[pUsing->nId-1].zName = sqlite3DbStrDup(pParse->db, zName);
+ }
}
}
- }
-
- /* Disallow both ON and USING clauses in the same join
- */
- if( pRight->pOn && pRight->pUsing ){
- sqlite3ErrorMsg(pParse, "cannot have both ON and USING "
- "clauses in the same join");
- return 1;
- }
-
- /* Add the ON clause to the end of the WHERE clause, connected by
- ** an AND operator.
- */
- if( pRight->pOn ){
- if( isOuter ) sqlite3SetJoinExpr(pRight->pOn, pRight->iCursor);
- p->pWhere = sqlite3ExprAnd(pParse, p->pWhere, pRight->pOn);
- pRight->pOn = 0;
+ if( pUsing ){
+ pRight->fg.isUsing = 1;
+ pRight->fg.isSynthUsing = 1;
+ pRight->u3.pUsing = pUsing;
+ }
+ if( pParse->nErr ) return 1;
}
/* Create extra terms on the WHERE clause for each column named
@@ -134787,27 +137886,88 @@ static int sqliteProcessJoin(Parse *pParse, Select *p){
** Report an error if any column mentioned in the USING clause is
** not contained in both tables to be joined.
*/
- if( pRight->pUsing ){
- IdList *pList = pRight->pUsing;
+ if( pRight->fg.isUsing ){
+ IdList *pList = pRight->u3.pUsing;
+ sqlite3 *db = pParse->db;
+ assert( pList!=0 );
for(j=0; j<pList->nId; j++){
char *zName; /* Name of the term in the USING clause */
int iLeft; /* Table on the left with matching column name */
int iLeftCol; /* Column number of matching column on the left */
int iRightCol; /* Column number of matching column on the right */
+ Expr *pE1; /* Reference to the column on the LEFT of the join */
+ Expr *pE2; /* Reference to the column on the RIGHT of the join */
+ Expr *pEq; /* Equality constraint. pE1 == pE2 */
zName = pList->a[j].zName;
iRightCol = sqlite3ColumnIndex(pRightTab, zName);
if( iRightCol<0
- || !tableAndColumnIndex(pSrc, i+1, zName, &iLeft, &iLeftCol, 0)
+ || tableAndColumnIndex(pSrc, 0, i, zName, &iLeft, &iLeftCol,
+ pRight->fg.isSynthUsing)==0
){
sqlite3ErrorMsg(pParse, "cannot join using column %s - column "
"not present in both tables", zName);
return 1;
}
- addWhereTerm(pParse, pSrc, iLeft, iLeftCol, i+1, iRightCol,
- isOuter, &p->pWhere);
+ pE1 = sqlite3CreateColumnExpr(db, pSrc, iLeft, iLeftCol);
+ sqlite3SrcItemColumnUsed(&pSrc->a[iLeft], iLeftCol);
+ if( (pSrc->a[0].fg.jointype & JT_LTORJ)!=0 ){
+ /* This branch runs if the query contains one or more RIGHT or FULL
+ ** JOINs. If only a single table on the left side of this join
+ ** contains the zName column, then this branch is a no-op.
+ ** But if there are two or more tables on the left side
+ ** of the join, construct a coalesce() function that gathers all
+ ** such tables. Raise an error if more than one of those references
+ ** to zName is not also within a prior USING clause.
+ **
+ ** We really ought to raise an error if there are two or more
+ ** non-USING references to zName on the left of an INNER or LEFT
+ ** JOIN. But older versions of SQLite do not do that, so we avoid
+ ** adding a new error so as to not break legacy applications.
+ */
+ ExprList *pFuncArgs = 0; /* Arguments to the coalesce() */
+ static const Token tkCoalesce = { "coalesce", 8 };
+ while( tableAndColumnIndex(pSrc, iLeft+1, i, zName, &iLeft, &iLeftCol,
+ pRight->fg.isSynthUsing)!=0 ){
+ if( pSrc->a[iLeft].fg.isUsing==0
+ || sqlite3IdListIndex(pSrc->a[iLeft].u3.pUsing, zName)<0
+ ){
+ sqlite3ErrorMsg(pParse, "ambiguous reference to %s in USING()",
+ zName);
+ break;
+ }
+ pFuncArgs = sqlite3ExprListAppend(pParse, pFuncArgs, pE1);
+ pE1 = sqlite3CreateColumnExpr(db, pSrc, iLeft, iLeftCol);
+ sqlite3SrcItemColumnUsed(&pSrc->a[iLeft], iLeftCol);
+ }
+ if( pFuncArgs ){
+ pFuncArgs = sqlite3ExprListAppend(pParse, pFuncArgs, pE1);
+ pE1 = sqlite3ExprFunction(pParse, pFuncArgs, &tkCoalesce, 0);
+ }
+ }
+ pE2 = sqlite3CreateColumnExpr(db, pSrc, i+1, iRightCol);
+ sqlite3SrcItemColumnUsed(pRight, iRightCol);
+ pEq = sqlite3PExpr(pParse, TK_EQ, pE1, pE2);
+ assert( pE2!=0 || pEq==0 );
+ if( pEq ){
+ ExprSetProperty(pEq, joinType);
+ assert( !ExprHasProperty(pEq, EP_TokenOnly|EP_Reduced) );
+ ExprSetVVAProperty(pEq, EP_NoReduce);
+ pEq->w.iJoin = pE2->iTable;
+ }
+ p->pWhere = sqlite3ExprAnd(pParse, p->pWhere, pEq);
}
}
+
+ /* Add the ON clause to the end of the WHERE clause, connected by
+ ** an AND operator.
+ */
+ else if( pRight->u3.pOn ){
+ sqlite3SetJoinExpr(pRight->u3.pOn, pRight->iCursor, joinType);
+ p->pWhere = sqlite3ExprAnd(pParse, p->pWhere, pRight->u3.pOn);
+ pRight->u3.pOn = 0;
+ pRight->fg.isOn = 1;
+ }
}
return 0;
}
@@ -135196,7 +138356,7 @@ static void fixDistinctOpenEph(
** retrieved directly from table t1. If the values are very large, this
** can be more efficient than storing them directly in the sorter records.
**
-** The ExprList_item.bSorterRef flag is set for each expression in pEList
+** The ExprList_item.fg.bSorterRef flag is set for each expression in pEList
** for which the sorter-reference optimization should be enabled.
** Additionally, the pSort->aDefer[] array is populated with entries
** for all cursors required to evaluate all selected expressions. Finally.
@@ -135256,7 +138416,7 @@ static void selectExprDefer(
nDefer++;
}
}
- pItem->bSorterRef = 1;
+ pItem->fg.bSorterRef = 1;
}
}
}
@@ -135387,7 +138547,7 @@ static void selectInnerLoop(
for(i=0; i<pEList->nExpr; i++){
if( pEList->a[i].u.x.iOrderByCol>0
#ifdef SQLITE_ENABLE_SORTER_REFERENCES
- || pEList->a[i].bSorterRef
+ || pEList->a[i].fg.bSorterRef
#endif
){
nResultCol--;
@@ -135475,6 +138635,9 @@ static void selectInnerLoop(
testcase( eDest==SRT_Fifo );
testcase( eDest==SRT_DistFifo );
sqlite3VdbeAddOp3(v, OP_MakeRecord, regResult, nResultCol, r1+nPrefixReg);
+ if( pDest->zAffSdst ){
+ sqlite3VdbeChangeP4(v, -1, pDest->zAffSdst, nResultCol);
+ }
#ifndef SQLITE_OMIT_CTE
if( eDest==SRT_DistFifo ){
/* If the destination is DistFifo, then cursor (iParm+1) is open
@@ -135690,9 +138853,10 @@ SQLITE_PRIVATE KeyInfo *sqlite3KeyInfoAlloc(sqlite3 *db, int N, int X){
*/
SQLITE_PRIVATE void sqlite3KeyInfoUnref(KeyInfo *p){
if( p ){
+ assert( p->db!=0 );
assert( p->nRef>0 );
p->nRef--;
- if( p->nRef==0 ) sqlite3DbFreeNN(p->db, p);
+ if( p->nRef==0 ) sqlite3DbNNFreeNN(p->db, p);
}
}
@@ -135749,7 +138913,7 @@ SQLITE_PRIVATE KeyInfo *sqlite3KeyInfoFromExprList(
assert( sqlite3KeyInfoIsWriteable(pInfo) );
for(i=iStart, pItem=pList->a+iStart; i<nExpr; i++, pItem++){
pInfo->aColl[i-iStart] = sqlite3ExprNNCollSeq(pParse, pItem->pExpr);
- pInfo->aSortFlags[i-iStart] = pItem->sortFlags;
+ pInfo->aSortFlags[i-iStart] = pItem->fg.sortFlags;
}
}
return pInfo;
@@ -135877,7 +139041,7 @@ static void generateSortTail(
if( addrOnce ) sqlite3VdbeJumpHere(v, addrOnce);
addr = 1 + sqlite3VdbeAddOp2(v, OP_SorterSort, iTab, addrBreak);
VdbeCoverage(v);
- codeOffset(v, p->iOffset, addrContinue);
+ assert( p->iLimit==0 && p->iOffset==0 );
sqlite3VdbeAddOp3(v, OP_SorterData, iTab, regSortOut, iSortTab);
bSeq = 0;
}else{
@@ -135885,10 +139049,13 @@ static void generateSortTail(
codeOffset(v, p->iOffset, addrContinue);
iSortTab = iTab;
bSeq = 1;
+ if( p->iOffset>0 ){
+ sqlite3VdbeAddOp2(v, OP_AddImm, p->iLimit, -1);
+ }
}
for(i=0, iCol=nKey+bSeq-1; i<nColumn; i++){
#ifdef SQLITE_ENABLE_SORTER_REFERENCES
- if( aOutEx[i].bSorterRef ) continue;
+ if( aOutEx[i].fg.bSorterRef ) continue;
#endif
if( aOutEx[i].u.x.iOrderByCol==0 ) iCol++;
}
@@ -135925,7 +139092,7 @@ static void generateSortTail(
#endif
for(i=nColumn-1; i>=0; i--){
#ifdef SQLITE_ENABLE_SORTER_REFERENCES
- if( aOutEx[i].bSorterRef ){
+ if( aOutEx[i].fg.bSorterRef ){
sqlite3ExprCode(pParse, aOutEx[i].pExpr, regRow+i);
}else
#endif
@@ -136009,9 +139176,6 @@ static void generateSortTail(
** Return a pointer to a string containing the 'declaration type' of the
** expression pExpr. The string may be treated as static by the caller.
**
-** Also try to estimate the size of the returned value and return that
-** result in *pEstWidth.
-**
** The declaration type is the exact datatype definition extracted from the
** original CREATE TABLE statement if the expression is a column. The
** declaration type for a ROWID field is INTEGER. Exactly when an expression
@@ -136291,7 +139455,7 @@ SQLITE_PRIVATE void sqlite3GenerateColumnNames(
assert( p->op!=TK_AGG_COLUMN ); /* Agg processing has not run yet */
assert( p->op!=TK_COLUMN
|| (ExprUseYTab(p) && p->y.pTab!=0) ); /* Covering idx not yet coded */
- if( pEList->a[i].zEName && pEList->a[i].eEName==ENAME_NAME ){
+ if( pEList->a[i].zEName && pEList->a[i].fg.eEName==ENAME_NAME ){
/* An AS clause always takes first priority */
char *zName = pEList->a[i].zEName;
sqlite3VdbeSetColName(v, i, COLNAME_NAME, zName, SQLITE_TRANSIENT);
@@ -136376,22 +139540,25 @@ SQLITE_PRIVATE int sqlite3ColumnsFromExprList(
*paCol = aCol;
for(i=0, pCol=aCol; i<nCol && !db->mallocFailed; i++, pCol++){
+ struct ExprList_item *pX = &pEList->a[i];
+ struct ExprList_item *pCollide;
/* Get an appropriate name for the column
*/
- if( (zName = pEList->a[i].zEName)!=0 && pEList->a[i].eEName==ENAME_NAME ){
+ if( (zName = pX->zEName)!=0 && pX->fg.eEName==ENAME_NAME ){
/* If the column contains an "AS <name>" phrase, use <name> as the name */
}else{
- Expr *pColExpr = sqlite3ExprSkipCollateAndLikely(pEList->a[i].pExpr);
+ Expr *pColExpr = sqlite3ExprSkipCollateAndLikely(pX->pExpr);
while( ALWAYS(pColExpr!=0) && pColExpr->op==TK_DOT ){
pColExpr = pColExpr->pRight;
assert( pColExpr!=0 );
}
if( pColExpr->op==TK_COLUMN
&& ALWAYS( ExprUseYTab(pColExpr) )
- && (pTab = pColExpr->y.pTab)!=0
+ && ALWAYS( pColExpr->y.pTab!=0 )
){
/* For columns use the column name name */
int iCol = pColExpr->iColumn;
+ pTab = pColExpr->y.pTab;
if( iCol<0 ) iCol = pTab->iPKey;
zName = iCol>=0 ? pTab->aCol[iCol].zCnName : "rowid";
}else if( pColExpr->op==TK_ID ){
@@ -136399,7 +139566,7 @@ SQLITE_PRIVATE int sqlite3ColumnsFromExprList(
zName = pColExpr->u.zToken;
}else{
/* Use the original text of the column expression as its name */
- zName = pEList->a[i].zEName;
+ assert( zName==pX->zEName ); /* pointer comparison intended */
}
}
if( zName && !sqlite3IsTrueOrFalse(zName) ){
@@ -136412,7 +139579,10 @@ SQLITE_PRIVATE int sqlite3ColumnsFromExprList(
** append an integer to the name so that it becomes unique.
*/
cnt = 0;
- while( zName && sqlite3HashFind(&ht, zName)!=0 ){
+ while( zName && (pCollide = sqlite3HashFind(&ht, zName))!=0 ){
+ if( pCollide->fg.bUsingTerm ){
+ pCol->colFlags |= COLFLAG_NOEXPAND;
+ }
nName = sqlite3Strlen30(zName);
if( nName>0 ){
for(j=nName-1; j>0 && sqlite3Isdigit(zName[j]); j--){}
@@ -136423,8 +139593,11 @@ SQLITE_PRIVATE int sqlite3ColumnsFromExprList(
}
pCol->zCnName = zName;
pCol->hName = sqlite3StrIHash(zName);
+ if( pX->fg.bNoExpand ){
+ pCol->colFlags |= COLFLAG_NOEXPAND;
+ }
sqlite3ColumnPropertiesFromName(0, pCol);
- if( zName && sqlite3HashInsert(&ht, zName, pCol)==pCol ){
+ if( zName && sqlite3HashInsert(&ht, zName, pX)==pX ){
sqlite3OomFault(db);
}
}
@@ -136681,7 +139854,7 @@ static KeyInfo *multiSelectOrderByKeyInfo(Parse *pParse, Select *p, int nExtra){
}
assert( sqlite3KeyInfoIsWriteable(pRet) );
pRet->aColl[i] = pColl;
- pRet->aSortFlags[i] = pOrderBy->a[i].sortFlags;
+ pRet->aSortFlags[i] = pOrderBy->a[i].fg.sortFlags;
}
}
@@ -136899,7 +140072,7 @@ static int multiSelectOrderBy(
** The "LIMIT of exactly 1" case of condition (1) comes about when a VALUES
** clause occurs within scalar expression (ex: "SELECT (VALUES(1),(2),(3))").
** The sqlite3CodeSubselect will have added the LIMIT 1 clause in tht case.
-** Since the limit is exactly 1, we only need to evalutes the left-most VALUES.
+** Since the limit is exactly 1, we only need to evaluate the left-most VALUES.
*/
static int multiSelectValues(
Parse *pParse, /* Parsing context */
@@ -137868,10 +141041,11 @@ static int multiSelectOrderBy(
*/
sqlite3VdbeResolveLabel(v, labelEnd);
- /* Reassembly the compound query so that it will be freed correctly
- ** by the calling function */
+ /* Make arrangements to free the 2nd and subsequent arms of the compound
+ ** after the parse has finished */
if( pSplit->pPrior ){
- sqlite3SelectDelete(db, pSplit->pPrior);
+ sqlite3ParserAddCleanup(pParse,
+ (void(*)(sqlite3*,void*))sqlite3SelectDelete, pSplit->pPrior);
}
pSplit->pPrior = pPrior;
pPrior->pNext = pSplit;
@@ -137892,13 +141066,42 @@ static int multiSelectOrderBy(
**
** All references to columns in table iTable are to be replaced by corresponding
** expressions in pEList.
+**
+** ## About "isOuterJoin":
+**
+** The isOuterJoin column indicates that the replacement will occur into a
+** position in the parent that NULL-able due to an OUTER JOIN. Either the
+** target slot in the parent is the right operand of a LEFT JOIN, or one of
+** the left operands of a RIGHT JOIN. In either case, we need to potentially
+** bypass the substituted expression with OP_IfNullRow.
+**
+** Suppose the original expression is an integer constant. Even though the table
+** has the nullRow flag set, because the expression is an integer constant,
+** it will not be NULLed out. So instead, we insert an OP_IfNullRow opcode
+** that checks to see if the nullRow flag is set on the table. If the nullRow
+** flag is set, then the value in the register is set to NULL and the original
+** expression is bypassed. If the nullRow flag is not set, then the original
+** expression runs to populate the register.
+**
+** Example where this is needed:
+**
+** CREATE TABLE t1(a INTEGER PRIMARY KEY, b INT);
+** CREATE TABLE t2(x INT UNIQUE);
+**
+** SELECT a,b,m,x FROM t1 LEFT JOIN (SELECT 59 AS m,x FROM t2) ON b=x;
+**
+** When the subquery on the right side of the LEFT JOIN is flattened, we
+** have to add OP_IfNullRow in front of the OP_Integer that implements the
+** "m" value of the subquery so that a NULL will be loaded instead of 59
+** when processing a non-matched row of the left.
*/
typedef struct SubstContext {
Parse *pParse; /* The parsing context */
int iTable; /* Replace references to this table */
int iNewTable; /* New table number */
- int isLeftJoin; /* Add TK_IF_NULL_ROW opcodes on each replacement */
+ int isOuterJoin; /* Add TK_IF_NULL_ROW opcodes on each replacement */
ExprList *pEList; /* Replacement expressions */
+ ExprList *pCList; /* Collation sequences for replacement expr */
} SubstContext;
/* Forward Declarations */
@@ -137923,10 +141126,11 @@ static Expr *substExpr(
Expr *pExpr /* Expr in which substitution occurs */
){
if( pExpr==0 ) return 0;
- if( ExprHasProperty(pExpr, EP_FromJoin)
- && pExpr->w.iRightJoinTable==pSubst->iTable
+ if( ExprHasProperty(pExpr, EP_OuterON|EP_InnerON)
+ && pExpr->w.iJoin==pSubst->iTable
){
- pExpr->w.iRightJoinTable = pSubst->iNewTable;
+ testcase( ExprHasProperty(pExpr, EP_InnerON) );
+ pExpr->w.iJoin = pSubst->iNewTable;
}
if( pExpr->op==TK_COLUMN
&& pExpr->iTable==pSubst->iTable
@@ -137939,19 +141143,21 @@ static Expr *substExpr(
#endif
{
Expr *pNew;
- Expr *pCopy = pSubst->pEList->a[pExpr->iColumn].pExpr;
+ int iColumn = pExpr->iColumn;
+ Expr *pCopy = pSubst->pEList->a[iColumn].pExpr;
Expr ifNullRow;
- assert( pSubst->pEList!=0 && pExpr->iColumn<pSubst->pEList->nExpr );
+ assert( pSubst->pEList!=0 && iColumn<pSubst->pEList->nExpr );
assert( pExpr->pRight==0 );
if( sqlite3ExprIsVector(pCopy) ){
sqlite3VectorErrorMsg(pSubst->pParse, pCopy);
}else{
sqlite3 *db = pSubst->pParse->db;
- if( pSubst->isLeftJoin && pCopy->op!=TK_COLUMN ){
+ if( pSubst->isOuterJoin && pCopy->op!=TK_COLUMN ){
memset(&ifNullRow, 0, sizeof(ifNullRow));
ifNullRow.op = TK_IF_NULL_ROW;
ifNullRow.pLeft = pCopy;
ifNullRow.iTable = pSubst->iNewTable;
+ ifNullRow.iColumn = -99;
ifNullRow.flags = EP_IfNullRow;
pCopy = &ifNullRow;
}
@@ -137961,22 +141167,33 @@ static Expr *substExpr(
sqlite3ExprDelete(db, pNew);
return pExpr;
}
- if( pSubst->isLeftJoin ){
+ if( pSubst->isOuterJoin ){
ExprSetProperty(pNew, EP_CanBeNull);
}
- if( ExprHasProperty(pExpr,EP_FromJoin) ){
- sqlite3SetJoinExpr(pNew, pExpr->w.iRightJoinTable);
+ if( ExprHasProperty(pExpr,EP_OuterON|EP_InnerON) ){
+ sqlite3SetJoinExpr(pNew, pExpr->w.iJoin,
+ pExpr->flags & (EP_OuterON|EP_InnerON));
}
sqlite3ExprDelete(db, pExpr);
pExpr = pNew;
+ if( pExpr->op==TK_TRUEFALSE ){
+ pExpr->u.iValue = sqlite3ExprTruthValue(pExpr);
+ pExpr->op = TK_INTEGER;
+ ExprSetProperty(pExpr, EP_IntValue);
+ }
/* Ensure that the expression now has an implicit collation sequence,
** just as it did when it was a column of a view or sub-query. */
- if( pExpr->op!=TK_COLUMN && pExpr->op!=TK_COLLATE ){
- CollSeq *pColl = sqlite3ExprCollSeq(pSubst->pParse, pExpr);
- pExpr = sqlite3ExprAddCollateString(pSubst->pParse, pExpr,
- (pColl ? pColl->zName : "BINARY")
+ {
+ CollSeq *pNat = sqlite3ExprCollSeq(pSubst->pParse, pExpr);
+ CollSeq *pColl = sqlite3ExprCollSeq(pSubst->pParse,
+ pSubst->pCList->a[iColumn].pExpr
);
+ if( pNat!=pColl || (pExpr->op!=TK_COLUMN && pExpr->op!=TK_COLLATE) ){
+ pExpr = sqlite3ExprAddCollateString(pSubst->pParse, pExpr,
+ (pColl ? pColl->zName : "BINARY")
+ );
+ }
}
ExprClearProperty(pExpr, EP_Collate);
}
@@ -138129,8 +141346,8 @@ static int renumberCursorsCb(Walker *pWalker, Expr *pExpr){
if( op==TK_COLUMN || op==TK_IF_NULL_ROW ){
renumberCursorDoMapping(pWalker, &pExpr->iTable);
}
- if( ExprHasProperty(pExpr, EP_FromJoin) ){
- renumberCursorDoMapping(pWalker, &pExpr->w.iRightJoinTable);
+ if( ExprHasProperty(pExpr, EP_OuterON) ){
+ renumberCursorDoMapping(pWalker, &pExpr->w.iJoin);
}
return WRC_Continue;
}
@@ -138169,6 +141386,18 @@ static void renumberCursors(
}
#endif /* !defined(SQLITE_OMIT_SUBQUERY) || !defined(SQLITE_OMIT_VIEW) */
+/*
+** If pSel is not part of a compound SELECT, return a pointer to its
+** expression list. Otherwise, return a pointer to the expression list
+** of the leftmost SELECT in the compound.
+*/
+static ExprList *findLeftmostExprlist(Select *pSel){
+ while( pSel->pPrior ){
+ pSel = pSel->pPrior;
+ }
+ return pSel->pEList;
+}
+
#if !defined(SQLITE_OMIT_SUBQUERY) || !defined(SQLITE_OMIT_VIEW)
/*
** This routine attempts to flatten subqueries as a performance optimization.
@@ -138213,8 +141442,10 @@ static void renumberCursors(
** (3a) the subquery may not be a join and
** (3b) the FROM clause of the subquery may not contain a virtual
** table and
-** (3c) the outer query may not be an aggregate.
+** (**) Was: "The outer query may not have a GROUP BY." This case
+** is now managed correctly
** (3d) the outer query may not be DISTINCT.
+** See also (26) for restrictions on RIGHT JOIN.
**
** (4) The subquery can not be DISTINCT.
**
@@ -138266,6 +141497,11 @@ static void renumberCursors(
** (17d2) DISTINCT
** (17e) the subquery may not contain window functions, and
** (17f) the subquery must not be the RHS of a LEFT JOIN.
+** (17g) either the subquery is the first element of the outer
+** query or there are no RIGHT or FULL JOINs in any arm
+** of the subquery. (This is a duplicate of condition (27b).)
+** (17h) The corresponding result set expressions in all arms of the
+** compound must have the same affinity.
**
** The parent and sub-query may contain WHERE clauses. Subject to
** rules (11), (13) and (14), they may also contain ORDER BY,
@@ -138313,6 +141549,17 @@ static void renumberCursors(
** function in the select list or ORDER BY clause, flattening
** is not attempted.
**
+** (26) The subquery may not be the right operand of a RIGHT JOIN.
+** See also (3) for restrictions on LEFT JOIN.
+**
+** (27) The subquery may not contain a FULL or RIGHT JOIN unless it
+** is the first element of the parent query. Two subcases:
+** (27a) the subquery is not a compound query.
+** (27b) the subquery is a compound query and the RIGHT JOIN occurs
+** in any arm of the compound query. (See also (17g).)
+**
+** (28) The subquery is not a MATERIALIZED CTE.
+**
**
** In this routine, the "p" parameter is a pointer to the outer query.
** The subquery is p->pSrc->a[iFrom]. isAgg is true if the outer query
@@ -138338,7 +141585,7 @@ static int flattenSubquery(
SrcList *pSubSrc; /* The FROM clause of the subquery */
int iParent; /* VDBE cursor number of the pSub result set temp table */
int iNewParent = -1;/* Replacement table for iParent */
- int isLeftJoin = 0; /* True if pSub is the right side of a LEFT JOIN */
+ int isOuterJoin = 0; /* True if pSub is the right side of a LEFT JOIN */
int i; /* Loop counter */
Expr *pWhere; /* The WHERE clause */
SrcItem *pSubitem; /* The subquery */
@@ -138404,32 +141651,26 @@ static int flattenSubquery(
**
** which is not at all the same thing.
**
- ** If the subquery is the right operand of a LEFT JOIN, then the outer
- ** query cannot be an aggregate. (3c) This is an artifact of the way
- ** aggregates are processed - there is no mechanism to determine if
- ** the LEFT JOIN table should be all-NULL.
- **
** See also tickets #306, #350, and #3300.
*/
- if( (pSubitem->fg.jointype & JT_OUTER)!=0 ){
- isLeftJoin = 1;
- if( pSubSrc->nSrc>1 /* (3a) */
- || isAgg /* (3b) */
- || IsVirtual(pSubSrc->a[0].pTab) /* (3c) */
- || (p->selFlags & SF_Distinct)!=0 /* (3d) */
+ if( (pSubitem->fg.jointype & (JT_OUTER|JT_LTORJ))!=0 ){
+ if( pSubSrc->nSrc>1 /* (3a) */
+ || IsVirtual(pSubSrc->a[0].pTab) /* (3b) */
+ || (p->selFlags & SF_Distinct)!=0 /* (3d) */
+ || (pSubitem->fg.jointype & JT_RIGHT)!=0 /* (26) */
){
return 0;
}
+ isOuterJoin = 1;
}
-#ifdef SQLITE_EXTRA_IFNULLROW
- else if( iFrom>0 && !isAgg ){
- /* Setting isLeftJoin to -1 causes OP_IfNullRow opcodes to be generated for
- ** every reference to any result column from subquery in a join, even
- ** though they are not necessary. This will stress-test the OP_IfNullRow
- ** opcode. */
- isLeftJoin = -1;
+
+ assert( pSubSrc->nSrc>0 ); /* True by restriction (7) */
+ if( iFrom>0 && (pSubSrc->a[0].fg.jointype & JT_LTORJ)!=0 ){
+ return 0; /* Restriction (27a) */
+ }
+ if( pSubitem->fg.isCte && pSubitem->u2.pCteUse->eM10d==M10d_Yes ){
+ return 0; /* (28) */
}
-#endif
/* Restriction (17): If the sub-query is a compound SELECT, then it must
** use only the UNION ALL operator. And none of the simple select queries
@@ -138437,10 +141678,11 @@ static int flattenSubquery(
** queries.
*/
if( pSub->pPrior ){
+ int ii;
if( pSub->pOrderBy ){
return 0; /* Restriction (20) */
}
- if( isAgg || (p->selFlags & SF_Distinct)!=0 || isLeftJoin>0 ){
+ if( isAgg || (p->selFlags & SF_Distinct)!=0 || isOuterJoin>0 ){
return 0; /* (17d1), (17d2), or (17f) */
}
for(pSub1=pSub; pSub1; pSub1=pSub1->pPrior){
@@ -138458,12 +141700,17 @@ static int flattenSubquery(
){
return 0;
}
+ if( iFrom>0 && (pSub1->pSrc->a[0].fg.jointype & JT_LTORJ)!=0 ){
+ /* Without this restriction, the JT_LTORJ flag would end up being
+ ** omitted on left-hand tables of the right join that is being
+ ** flattened. */
+ return 0; /* Restrictions (17g), (27b) */
+ }
testcase( pSub1->pSrc->nSrc>1 );
}
/* Restriction (18). */
if( p->pOrderBy ){
- int ii;
for(ii=0; ii<p->pOrderBy->nExpr; ii++){
if( p->pOrderBy->a[ii].u.x.iOrderByCol==0 ) return 0;
}
@@ -138472,8 +141719,24 @@ static int flattenSubquery(
/* Restriction (23) */
if( (p->selFlags & SF_Recursive) ) return 0;
+ /* Restriction (17h) */
+ for(ii=0; ii<pSub->pEList->nExpr; ii++){
+ char aff;
+ assert( pSub->pEList->a[ii].pExpr!=0 );
+ aff = sqlite3ExprAffinity(pSub->pEList->a[ii].pExpr);
+ for(pSub1=pSub->pPrior; pSub1; pSub1=pSub1->pPrior){
+ assert( pSub1->pEList!=0 );
+ assert( pSub1->pEList->nExpr>ii );
+ assert( pSub1->pEList->a[ii].pExpr!=0 );
+ if( sqlite3ExprAffinity(pSub1->pEList->a[ii].pExpr)!=aff ){
+ return 0;
+ }
+ }
+ }
+
if( pSrc->nSrc>1 ){
if( pParse->nSelect>500 ) return 0;
+ if( OptimizationDisabled(db, SQLITE_FlttnUnionAll) ) return 0;
aCsrMap = sqlite3DbMallocZero(db, ((i64)pParse->nTab+1)*sizeof(int));
if( aCsrMap ) aCsrMap[0] = pParse->nTab;
}
@@ -138498,7 +141761,7 @@ static int flattenSubquery(
pSubitem->zName = 0;
pSubitem->zAlias = 0;
pSubitem->pSelect = 0;
- assert( pSubitem->pOn==0 );
+ assert( pSubitem->fg.isUsing!=0 || pSubitem->u3.pOn==0 );
/* If the sub-query is a compound SELECT statement, then (by restrictions
** 17 and 18 above) it must be a UNION ALL and the parent query must
@@ -138608,6 +141871,7 @@ static int flattenSubquery(
for(pParent=p; pParent; pParent=pParent->pPrior, pSub=pSub->pPrior){
int nSubSrc;
u8 jointype = 0;
+ u8 ltorj = pSrc->a[iFrom].fg.jointype & JT_LTORJ;
assert( pSub!=0 );
pSubSrc = pSub->pSrc; /* FROM clause of subquery */
nSubSrc = pSubSrc->nSrc; /* Number of terms in subquery FROM clause */
@@ -138642,13 +141906,16 @@ static int flattenSubquery(
** outer query.
*/
for(i=0; i<nSubSrc; i++){
- sqlite3IdListDelete(db, pSrc->a[i+iFrom].pUsing);
- assert( pSrc->a[i+iFrom].fg.isTabFunc==0 );
- pSrc->a[i+iFrom] = pSubSrc->a[i];
+ SrcItem *pItem = &pSrc->a[i+iFrom];
+ if( pItem->fg.isUsing ) sqlite3IdListDelete(db, pItem->u3.pUsing);
+ assert( pItem->fg.isTabFunc==0 );
+ *pItem = pSubSrc->a[i];
+ pItem->fg.jointype |= ltorj;
iNewParent = pSubSrc->a[i].iCursor;
memset(&pSubSrc->a[i], 0, sizeof(pSubSrc->a[i]));
}
- pSrc->a[iFrom].fg.jointype = jointype;
+ pSrc->a[iFrom].fg.jointype &= JT_LTORJ;
+ pSrc->a[iFrom].fg.jointype |= jointype | ltorj;
/* Now begin substituting subquery result set expressions for
** references to the iParent in the outer query.
@@ -138683,8 +141950,8 @@ static int flattenSubquery(
}
pWhere = pSub->pWhere;
pSub->pWhere = 0;
- if( isLeftJoin>0 ){
- sqlite3SetJoinExpr(pWhere, iNewParent);
+ if( isOuterJoin>0 ){
+ sqlite3SetJoinExpr(pWhere, iNewParent, EP_OuterON);
}
if( pWhere ){
if( pParent->pWhere ){
@@ -138698,8 +141965,9 @@ static int flattenSubquery(
x.pParse = pParse;
x.iTable = iParent;
x.iNewTable = iNewParent;
- x.isLeftJoin = isLeftJoin;
+ x.isOuterJoin = isOuterJoin;
x.pEList = pSub->pEList;
+ x.pCList = findLeftmostExprlist(pSub);
substSelect(&x, pParent, 0);
}
@@ -138719,7 +141987,7 @@ static int flattenSubquery(
pSub->pLimit = 0;
}
- /* Recompute the SrcList_item.colUsed masks for the flattened
+ /* Recompute the SrcItem.colUsed masks for the flattened
** tables. */
for(i=0; i<nSubSrc; i++){
recomputeColumnsUsed(pParent, &pSrc->a[i+iFrom]);
@@ -138733,8 +142001,8 @@ static int flattenSubquery(
sqlite3WalkSelect(&w,pSub1);
sqlite3SelectDelete(db, pSub1);
-#if SELECTTRACE_ENABLED
- if( sqlite3SelectTrace & 0x100 ){
+#if TREETRACE_ENABLED
+ if( sqlite3TreeTrace & 0x100 ){
SELECTTRACE(0x100,pParse,p,("After flattening:\n"));
sqlite3TreeViewSelect(0, p, 0);
}
@@ -138755,6 +142023,8 @@ struct WhereConst {
int nConst; /* Number for COLUMN=CONSTANT terms */
int nChng; /* Number of times a constant is propagated */
int bHasAffBlob; /* At least one column in apExpr[] as affinity BLOB */
+ u32 mExcludeOn; /* Which ON expressions to exclude from considertion.
+ ** Either EP_OuterON or EP_InnerON|EP_OuterON */
Expr **apExpr; /* [i*2] is COLUMN and [i*2+1] is VALUE */
};
@@ -138817,7 +142087,11 @@ static void constInsert(
static void findConstInWhere(WhereConst *pConst, Expr *pExpr){
Expr *pRight, *pLeft;
if( NEVER(pExpr==0) ) return;
- if( ExprHasProperty(pExpr, EP_FromJoin) ) return;
+ if( ExprHasProperty(pExpr, pConst->mExcludeOn) ){
+ testcase( ExprHasProperty(pExpr, EP_OuterON) );
+ testcase( ExprHasProperty(pExpr, EP_InnerON) );
+ return;
+ }
if( pExpr->op==TK_AND ){
findConstInWhere(pConst, pExpr->pRight);
findConstInWhere(pConst, pExpr->pLeft);
@@ -138853,9 +142127,10 @@ static int propagateConstantExprRewriteOne(
int i;
if( pConst->pOomFault[0] ) return WRC_Prune;
if( pExpr->op!=TK_COLUMN ) return WRC_Continue;
- if( ExprHasProperty(pExpr, EP_FixedCol|EP_FromJoin) ){
+ if( ExprHasProperty(pExpr, EP_FixedCol|pConst->mExcludeOn) ){
testcase( ExprHasProperty(pExpr, EP_FixedCol) );
- testcase( ExprHasProperty(pExpr, EP_FromJoin) );
+ testcase( ExprHasProperty(pExpr, EP_OuterON) );
+ testcase( ExprHasProperty(pExpr, EP_InnerON) );
return WRC_Continue;
}
for(i=0; i<pConst->nConst; i++){
@@ -138979,6 +142254,17 @@ static int propagateConstants(
x.nChng = 0;
x.apExpr = 0;
x.bHasAffBlob = 0;
+ if( ALWAYS(p->pSrc!=0)
+ && p->pSrc->nSrc>0
+ && (p->pSrc->a[0].fg.jointype & JT_LTORJ)!=0
+ ){
+ /* Do not propagate constants on any ON clause if there is a
+ ** RIGHT JOIN anywhere in the query */
+ x.mExcludeOn = EP_InnerON | EP_OuterON;
+ }else{
+ /* Do not propagate constants through the ON clause of a LEFT JOIN */
+ x.mExcludeOn = EP_OuterON;
+ }
findConstInWhere(&x, p->pWhere);
if( x.nConst ){
memset(&w, 0, sizeof(w));
@@ -139091,6 +142377,13 @@ static int pushDownWindowCheck(Parse *pParse, Select *pSubq, Expr *pExpr){
** be materialized. (This restriction is implemented in the calling
** routine.)
**
+** (8) The subquery may not be a compound that uses UNION, INTERSECT,
+** or EXCEPT. (We could, perhaps, relax this restriction to allow
+** this case if none of the comparisons operators between left and
+** right arms of the compound use a collation other than BINARY.
+** But it is a lot of work to check that case for an obscure and
+** minor optimization, so we omit it for now.)
+**
** Return 0 if no changes are made and non-zero if one or more WHERE clause
** terms are duplicated into the subquery.
*/
@@ -139104,11 +142397,16 @@ static int pushDownWhereTerms(
int nChng = 0;
if( pWhere==0 ) return 0;
if( pSubq->selFlags & (SF_Recursive|SF_MultiPart) ) return 0;
+ if( pSrc->fg.jointype & (JT_LTORJ|JT_RIGHT) ) return 0;
#ifndef SQLITE_OMIT_WINDOWFUNC
if( pSubq->pPrior ){
Select *pSel;
for(pSel=pSubq; pSel; pSel=pSel->pPrior){
+ u8 op = pSel->op;
+ assert( op==TK_ALL || op==TK_SELECT
+ || op==TK_UNION || op==TK_INTERSECT || op==TK_EXCEPT );
+ if( op!=TK_ALL && op!=TK_SELECT ) return 0; /* restriction (8) */
if( pSel->pWin ) return 0; /* restriction (6b) */
}
}else{
@@ -139139,13 +142437,13 @@ static int pushDownWhereTerms(
#if 0 /* Legacy code. Checks now done by sqlite3ExprIsTableConstraint() */
if( isLeftJoin
- && (ExprHasProperty(pWhere,EP_FromJoin)==0
- || pWhere->w.iRightJoinTable!=iCursor)
+ && (ExprHasProperty(pWhere,EP_OuterON)==0
+ || pWhere->w.iJoin!=iCursor)
){
return 0; /* restriction (4) */
}
- if( ExprHasProperty(pWhere,EP_FromJoin)
- && pWhere->w.iRightJoinTable!=iCursor
+ if( ExprHasProperty(pWhere,EP_OuterON)
+ && pWhere->w.iJoin!=iCursor
){
return 0; /* restriction (5) */
}
@@ -139157,12 +142455,13 @@ static int pushDownWhereTerms(
while( pSubq ){
SubstContext x;
pNew = sqlite3ExprDup(pParse->db, pWhere, 0);
- unsetJoinExpr(pNew, -1);
+ unsetJoinExpr(pNew, -1, 1);
x.pParse = pParse;
x.iTable = pSrc->iCursor;
x.iNewTable = pSrc->iCursor;
- x.isLeftJoin = 0;
+ x.isOuterJoin = 0;
x.pEList = pSubq->pEList;
+ x.pCList = findLeftmostExprlist(pSubq);
pNew = substExpr(&x, pNew);
#ifndef SQLITE_OMIT_WINDOWFUNC
if( pSubq->pWin && 0==pushDownWindowCheck(pParse, pSubq, pNew) ){
@@ -139234,7 +142533,7 @@ static u8 minMaxQuery(sqlite3 *db, Expr *pFunc, ExprList **ppMinMax){
}
*ppMinMax = pOrderBy = sqlite3ExprListDup(db, pEList, 0);
assert( pOrderBy!=0 || db->mallocFailed );
- if( pOrderBy ) pOrderBy->a[0].sortFlags = sortFlags;
+ if( pOrderBy ) pOrderBy->a[0].fg.sortFlags = sortFlags;
return eRet;
}
@@ -139266,6 +142565,7 @@ static Table *isSimpleCount(Select *p, AggInfo *pAggInfo){
|| p->pSrc->nSrc!=1
|| p->pSrc->a[0].pSelect
|| pAggInfo->nFunc!=1
+ || p->pHaving
){
return 0;
}
@@ -139370,7 +142670,7 @@ static int convertCompoundSelectToSubquery(Walker *pWalker, Select *p){
pNew = sqlite3DbMallocZero(db, sizeof(*pNew) );
if( pNew==0 ) return WRC_Abort;
memset(&dummy, 0, sizeof(dummy));
- pNewSrc = sqlite3SrcListAppendFromTerm(pParse,0,0,0,&dummy,pNew,0,0);
+ pNewSrc = sqlite3SrcListAppendFromTerm(pParse,0,0,0,&dummy,pNew,0);
if( pNewSrc==0 ) return WRC_Abort;
*pNew = *p;
p->pSrc = pNewSrc;
@@ -139686,9 +142986,9 @@ SQLITE_PRIVATE void sqlite3SelectPopWith(Walker *pWalker, Select *p){
#endif
/*
-** The SrcList_item structure passed as the second argument represents a
+** The SrcItem structure passed as the second argument represents a
** sub-query in the FROM clause of a SELECT statement. This function
-** allocates and populates the SrcList_item.pTab object. If successful,
+** allocates and populates the SrcItem.pTab object. If successful,
** SQLITE_OK is returned. Otherwise, if an OOM error is encountered,
** SQLITE_NOMEM.
*/
@@ -139703,7 +143003,7 @@ SQLITE_PRIVATE int sqlite3ExpandSubquery(Parse *pParse, SrcItem *pFrom){
if( pFrom->zAlias ){
pTab->zName = sqlite3DbStrDup(pParse->db, pFrom->zAlias);
}else{
- pTab->zName = sqlite3MPrintf(pParse->db, "subquery_%u", pSel->selId);
+ pTab->zName = sqlite3MPrintf(pParse->db, "%!S", pFrom);
}
while( pSel->pPrior ){ pSel = pSel->pPrior; }
sqlite3ColumnsFromExprList(pParse, pSel->pEList,&pTab->nCol,&pTab->aCol);
@@ -139715,11 +143015,35 @@ SQLITE_PRIVATE int sqlite3ExpandSubquery(Parse *pParse, SrcItem *pFrom){
#else
pTab->tabFlags |= TF_Ephemeral; /* Legacy compatibility mode */
#endif
+ return pParse->nErr ? SQLITE_ERROR : SQLITE_OK;
+}
- return pParse->nErr ? SQLITE_ERROR : SQLITE_OK;
+/*
+** Check the N SrcItem objects to the right of pBase. (N might be zero!)
+** If any of those SrcItem objects have a USING clause containing zName
+** then return true.
+**
+** If N is zero, or none of the N SrcItem objects to the right of pBase
+** contains a USING clause, or if none of the USING clauses contain zName,
+** then return false.
+*/
+static int inAnyUsingClause(
+ const char *zName, /* Name we are looking for */
+ SrcItem *pBase, /* The base SrcItem. Looking at pBase[1] and following */
+ int N /* How many SrcItems to check */
+){
+ while( N>0 ){
+ N--;
+ pBase++;
+ if( pBase->fg.isUsing==0 ) continue;
+ if( NEVER(pBase->u3.pUsing==0) ) continue;
+ if( sqlite3IdListIndex(pBase->u3.pUsing, zName)>=0 ) return 1;
+ }
+ return 0;
}
+
/*
** This routine is a Walker callback for "expanding" a SELECT statement.
** "Expanding" means to do the following:
@@ -139869,7 +143193,7 @@ static int selectExpander(Walker *pWalker, Select *p){
/* Process NATURAL keywords, and ON and USING clauses of joins.
*/
assert( db->mallocFailed==0 || pParse->nErr!=0 );
- if( pParse->nErr || sqliteProcessJoin(pParse, p) ){
+ if( pParse->nErr || sqlite3ProcessJoin(pParse, p) ){
return WRC_Abort;
}
@@ -139917,7 +143241,7 @@ static int selectExpander(Walker *pWalker, Select *p){
pNew = sqlite3ExprListAppend(pParse, pNew, a[k].pExpr);
if( pNew ){
pNew->a[pNew->nExpr-1].zEName = a[k].zEName;
- pNew->a[pNew->nExpr-1].eEName = a[k].eEName;
+ pNew->a[pNew->nExpr-1].fg.eEName = a[k].fg.eEName;
a[k].zEName = 0;
}
a[k].pExpr = 0;
@@ -139932,32 +143256,60 @@ static int selectExpander(Walker *pWalker, Select *p){
zTName = pE->pLeft->u.zToken;
}
for(i=0, pFrom=pTabList->a; i<pTabList->nSrc; i++, pFrom++){
- Table *pTab = pFrom->pTab;
- Select *pSub = pFrom->pSelect;
- char *zTabName = pFrom->zAlias;
- const char *zSchemaName = 0;
- int iDb;
- if( zTabName==0 ){
+ Table *pTab = pFrom->pTab; /* Table for this data source */
+ ExprList *pNestedFrom; /* Result-set of a nested FROM clause */
+ char *zTabName; /* AS name for this data source */
+ const char *zSchemaName = 0; /* Schema name for this data source */
+ int iDb; /* Schema index for this data src */
+ IdList *pUsing; /* USING clause for pFrom[1] */
+
+ if( (zTabName = pFrom->zAlias)==0 ){
zTabName = pTab->zName;
}
if( db->mallocFailed ) break;
- if( pSub==0 || (pSub->selFlags & SF_NestedFrom)==0 ){
- pSub = 0;
+ assert( (int)pFrom->fg.isNestedFrom == IsNestedFrom(pFrom->pSelect) );
+ if( pFrom->fg.isNestedFrom ){
+ assert( pFrom->pSelect!=0 );
+ pNestedFrom = pFrom->pSelect->pEList;
+ assert( pNestedFrom!=0 );
+ assert( pNestedFrom->nExpr==pTab->nCol );
+ }else{
if( zTName && sqlite3StrICmp(zTName, zTabName)!=0 ){
continue;
}
+ pNestedFrom = 0;
iDb = sqlite3SchemaToIndex(db, pTab->pSchema);
zSchemaName = iDb>=0 ? db->aDb[iDb].zDbSName : "*";
}
+ if( i+1<pTabList->nSrc
+ && pFrom[1].fg.isUsing
+ && (selFlags & SF_NestedFrom)!=0
+ ){
+ int ii;
+ pUsing = pFrom[1].u3.pUsing;
+ for(ii=0; ii<pUsing->nId; ii++){
+ const char *zUName = pUsing->a[ii].zName;
+ pRight = sqlite3Expr(db, TK_ID, zUName);
+ pNew = sqlite3ExprListAppend(pParse, pNew, pRight);
+ if( pNew ){
+ struct ExprList_item *pX = &pNew->a[pNew->nExpr-1];
+ assert( pX->zEName==0 );
+ pX->zEName = sqlite3MPrintf(db,"..%s", zUName);
+ pX->fg.eEName = ENAME_TAB;
+ pX->fg.bUsingTerm = 1;
+ }
+ }
+ }else{
+ pUsing = 0;
+ }
for(j=0; j<pTab->nCol; j++){
char *zName = pTab->aCol[j].zCnName;
- char *zColname; /* The computed column name */
- char *zToFree; /* Malloced string that needs to be freed */
- Token sColname; /* Computed column name as a token */
+ struct ExprList_item *pX; /* Newly added ExprList term */
assert( zName );
- if( zTName && pSub
- && sqlite3MatchEName(&pSub->pEList->a[j], 0, zTName, 0)==0
+ if( zTName
+ && pNestedFrom
+ && sqlite3MatchEName(&pNestedFrom->a[j], 0, zTName, 0)==0
){
continue;
}
@@ -139971,57 +143323,75 @@ static int selectExpander(Walker *pWalker, Select *p){
){
continue;
}
+ if( (pTab->aCol[j].colFlags & COLFLAG_NOEXPAND)!=0
+ && zTName==0
+ && (selFlags & (SF_NestedFrom))==0
+ ){
+ continue;
+ }
tableSeen = 1;
- if( i>0 && zTName==0 ){
- if( (pFrom->fg.jointype & JT_NATURAL)!=0
- && tableAndColumnIndex(pTabList, i, zName, 0, 0, 1)
+ if( i>0 && zTName==0 && (selFlags & SF_NestedFrom)==0 ){
+ if( pFrom->fg.isUsing
+ && sqlite3IdListIndex(pFrom->u3.pUsing, zName)>=0
){
- /* In a NATURAL join, omit the join columns from the
- ** table to the right of the join */
- continue;
- }
- if( sqlite3IdListIndex(pFrom->pUsing, zName)>=0 ){
/* In a join with a USING clause, omit columns in the
** using clause from the table on the right. */
continue;
}
}
pRight = sqlite3Expr(db, TK_ID, zName);
- zColname = zName;
- zToFree = 0;
- if( longNames || pTabList->nSrc>1 ){
+ if( (pTabList->nSrc>1
+ && ( (pFrom->fg.jointype & JT_LTORJ)==0
+ || (selFlags & SF_NestedFrom)!=0
+ || !inAnyUsingClause(zName,pFrom,pTabList->nSrc-i-1)
+ )
+ )
+ || IN_RENAME_OBJECT
+ ){
Expr *pLeft;
pLeft = sqlite3Expr(db, TK_ID, zTabName);
pExpr = sqlite3PExpr(pParse, TK_DOT, pLeft, pRight);
+ if( IN_RENAME_OBJECT && pE->pLeft ){
+ sqlite3RenameTokenRemap(pParse, pLeft, pE->pLeft);
+ }
if( zSchemaName ){
pLeft = sqlite3Expr(db, TK_ID, zSchemaName);
pExpr = sqlite3PExpr(pParse, TK_DOT, pLeft, pExpr);
}
- if( longNames ){
- zColname = sqlite3MPrintf(db, "%s.%s", zTabName, zName);
- zToFree = zColname;
- }
}else{
pExpr = pRight;
}
pNew = sqlite3ExprListAppend(pParse, pNew, pExpr);
- sqlite3TokenInit(&sColname, zColname);
- sqlite3ExprListSetName(pParse, pNew, &sColname, 0);
- if( pNew && (p->selFlags & SF_NestedFrom)!=0 && !IN_RENAME_OBJECT ){
- struct ExprList_item *pX = &pNew->a[pNew->nExpr-1];
- sqlite3DbFree(db, pX->zEName);
- if( pSub ){
- pX->zEName = sqlite3DbStrDup(db, pSub->pEList->a[j].zEName);
+ if( pNew==0 ){
+ break; /* OOM */
+ }
+ pX = &pNew->a[pNew->nExpr-1];
+ assert( pX->zEName==0 );
+ if( (selFlags & SF_NestedFrom)!=0 && !IN_RENAME_OBJECT ){
+ if( pNestedFrom ){
+ pX->zEName = sqlite3DbStrDup(db, pNestedFrom->a[j].zEName);
testcase( pX->zEName==0 );
}else{
pX->zEName = sqlite3MPrintf(db, "%s.%s.%s",
- zSchemaName, zTabName, zColname);
+ zSchemaName, zTabName, zName);
testcase( pX->zEName==0 );
}
- pX->eEName = ENAME_TAB;
+ pX->fg.eEName = ENAME_TAB;
+ if( (pFrom->fg.isUsing
+ && sqlite3IdListIndex(pFrom->u3.pUsing, zName)>=0)
+ || (pUsing && sqlite3IdListIndex(pUsing, zName)>=0)
+ || (pTab->aCol[j].colFlags & COLFLAG_NOEXPAND)!=0
+ ){
+ pX->fg.bNoExpand = 1;
+ }
+ }else if( longNames ){
+ pX->zEName = sqlite3MPrintf(db, "%s.%s", zTabName, zName);
+ pX->fg.eEName = ENAME_NAME;
+ }else{
+ pX->zEName = sqlite3DbStrDup(db, zName);
+ pX->fg.eEName = ENAME_NAME;
}
- sqlite3DbFree(db, zToFree);
}
}
if( !tableSeen ){
@@ -140045,6 +143415,12 @@ static int selectExpander(Walker *pWalker, Select *p){
p->selFlags |= SF_ComplexResult;
}
}
+#if TREETRACE_ENABLED
+ if( sqlite3TreeTrace & 0x100 ){
+ SELECTTRACE(0x100,pParse,p,("After result-set wildcard expansion:\n"));
+ sqlite3TreeViewSelect(0, p, 0);
+ }
+#endif
return WRC_Continue;
}
@@ -140435,8 +143811,8 @@ static void havingToWhere(Parse *pParse, Select *p){
sWalker.xExprCallback = havingToWhereExprCb;
sWalker.u.pSelect = p;
sqlite3WalkExpr(&sWalker, p->pHaving);
-#if SELECTTRACE_ENABLED
- if( sWalker.eCode && (sqlite3SelectTrace & 0x100)!=0 ){
+#if TREETRACE_ENABLED
+ if( sWalker.eCode && (sqlite3TreeTrace & 0x100)!=0 ){
SELECTTRACE(0x100,pParse,p,("Move HAVING terms into WHERE:\n"));
sqlite3TreeViewSelect(0, p, 0);
}
@@ -140445,7 +143821,7 @@ static void havingToWhere(Parse *pParse, Select *p){
/*
** Check to see if the pThis entry of pTabList is a self-join of a prior view.
-** If it is, then return the SrcList_item for the prior view. If it is not,
+** If it is, then return the SrcItem for the prior view. If it is not,
** then return 0.
*/
static SrcItem *isSelfJoinView(
@@ -140568,8 +143944,8 @@ static int countOfViewOptimization(Parse *pParse, Select *p){
p->pEList->a[0].pExpr = pExpr;
p->selFlags &= ~SF_Aggregate;
-#if SELECTTRACE_ENABLED
- if( sqlite3SelectTrace & 0x400 ){
+#if TREETRACE_ENABLED
+ if( sqlite3TreeTrace & 0x400 ){
SELECTTRACE(0x400,pParse,p,("After count-of-view optimization:\n"));
sqlite3TreeViewSelect(0, p, 0);
}
@@ -140579,6 +143955,29 @@ static int countOfViewOptimization(Parse *pParse, Select *p){
#endif /* SQLITE_COUNTOFVIEW_OPTIMIZATION */
/*
+** If any term of pSrc, or any SF_NestedFrom sub-query, is not the same
+** as pSrcItem but has the same alias as p0, then return true.
+** Otherwise return false.
+*/
+static int sameSrcAlias(SrcItem *p0, SrcList *pSrc){
+ int i;
+ for(i=0; i<pSrc->nSrc; i++){
+ SrcItem *p1 = &pSrc->a[i];
+ if( p1==p0 ) continue;
+ if( p0->pTab==p1->pTab && 0==sqlite3_stricmp(p0->zAlias, p1->zAlias) ){
+ return 1;
+ }
+ if( p1->pSelect
+ && (p1->pSelect->selFlags & SF_NestedFrom)!=0
+ && sameSrcAlias(p0, p1->pSelect->pSrc)
+ ){
+ return 1;
+ }
+ }
+ return 0;
+}
+
+/*
** Generate code for the SELECT statement given in the p argument.
**
** The results are returned according to the SelectDest structure.
@@ -140622,10 +144021,14 @@ SQLITE_PRIVATE int sqlite3Select(
}
assert( db->mallocFailed==0 );
if( sqlite3AuthCheck(pParse, SQLITE_SELECT, 0, 0, 0) ) return 1;
-#if SELECTTRACE_ENABLED
+#if TREETRACE_ENABLED
SELECTTRACE(1,pParse,p, ("begin processing:\n", pParse->addrExplain));
- if( sqlite3SelectTrace & 0x100 ){
- sqlite3TreeViewSelect(0, p, 0);
+ if( sqlite3TreeTrace & 0x10100 ){
+ if( (sqlite3TreeTrace & 0x10001)==0x10000 ){
+ sqlite3TreeViewLine(0, "In sqlite3Select() at %s:%d",
+ __FILE__, __LINE__);
+ }
+ sqlite3ShowSelect(p);
}
#endif
@@ -140639,9 +144042,9 @@ SQLITE_PRIVATE int sqlite3Select(
pDest->eDest==SRT_DistQueue || pDest->eDest==SRT_DistFifo );
/* All of these destinations are also able to ignore the ORDER BY clause */
if( p->pOrderBy ){
-#if SELECTTRACE_ENABLED
+#if TREETRACE_ENABLED
SELECTTRACE(1,pParse,p, ("dropping superfluous ORDER BY:\n"));
- if( sqlite3SelectTrace & 0x100 ){
+ if( sqlite3TreeTrace & 0x100 ){
sqlite3TreeViewExprList(0, p->pOrderBy, 0, "ORDERBY");
}
#endif
@@ -140660,8 +144063,8 @@ SQLITE_PRIVATE int sqlite3Select(
}
assert( db->mallocFailed==0 );
assert( p->pEList!=0 );
-#if SELECTTRACE_ENABLED
- if( sqlite3SelectTrace & 0x104 ){
+#if TREETRACE_ENABLED
+ if( sqlite3TreeTrace & 0x104 ){
SELECTTRACE(0x104,pParse,p, ("after name resolution:\n"));
sqlite3TreeViewSelect(0, p, 0);
}
@@ -140678,15 +144081,12 @@ SQLITE_PRIVATE int sqlite3Select(
** disallow it altogether. */
if( p->selFlags & SF_UFSrcCheck ){
SrcItem *p0 = &p->pSrc->a[0];
- for(i=1; i<p->pSrc->nSrc; i++){
- SrcItem *p1 = &p->pSrc->a[i];
- if( p0->pTab==p1->pTab && 0==sqlite3_stricmp(p0->zAlias, p1->zAlias) ){
- sqlite3ErrorMsg(pParse,
- "target object/alias may not appear in FROM clause: %s",
- p0->zAlias ? p0->zAlias : p0->pTab->zName
- );
- goto select_end;
- }
+ if( sameSrcAlias(p0, p->pSrc) ){
+ sqlite3ErrorMsg(pParse,
+ "target object/alias may not appear in FROM clause: %s",
+ p0->zAlias ? p0->zAlias : p0->pTab->zName
+ );
+ goto select_end;
}
/* Clear the SF_UFSrcCheck flag. The check has already been performed,
@@ -140705,8 +144105,8 @@ SQLITE_PRIVATE int sqlite3Select(
assert( pParse->nErr );
goto select_end;
}
-#if SELECTTRACE_ENABLED
- if( p->pWin && (sqlite3SelectTrace & 0x108)!=0 ){
+#if TREETRACE_ENABLED
+ if( p->pWin && (sqlite3TreeTrace & 0x108)!=0 ){
SELECTTRACE(0x104,pParse,p, ("after window rewrite:\n"));
sqlite3TreeViewSelect(0, p, 0);
}
@@ -140734,14 +144134,16 @@ SQLITE_PRIVATE int sqlite3Select(
/* Convert LEFT JOIN into JOIN if there are terms of the right table
** of the LEFT JOIN used in the WHERE clause.
*/
- if( (pItem->fg.jointype & JT_LEFT)!=0
+ if( (pItem->fg.jointype & (JT_LEFT|JT_RIGHT))==JT_LEFT
&& sqlite3ExprImpliesNonNullRow(p->pWhere, pItem->iCursor)
&& OptimizationEnabled(db, SQLITE_SimplifyJoin)
){
SELECTTRACE(0x100,pParse,p,
("LEFT-JOIN simplifies to JOIN on term %d\n",i));
pItem->fg.jointype &= ~(JT_LEFT|JT_OUTER);
- unsetJoinExpr(p->pWhere, pItem->iCursor);
+ assert( pItem->iCursor>=0 );
+ unsetJoinExpr(p->pWhere, pItem->iCursor,
+ pTabList->a[0].fg.jointype & JT_LTORJ);
}
/* No futher action if this term of the FROM clause is no a subquery */
@@ -140794,7 +144196,9 @@ SQLITE_PRIVATE int sqlite3Select(
){
SELECTTRACE(0x100,pParse,p,
("omit superfluous ORDER BY on %r FROM-clause subquery\n",i+1));
- sqlite3ExprListDelete(db, pSub->pOrderBy);
+ sqlite3ParserAddCleanup(pParse,
+ (void(*)(sqlite3*,void*))sqlite3ExprListDelete,
+ pSub->pOrderBy);
pSub->pOrderBy = 0;
}
@@ -140820,7 +144224,7 @@ SQLITE_PRIVATE int sqlite3Select(
&& i==0
&& (p->selFlags & SF_ComplexResult)!=0
&& (pTabList->nSrc==1
- || (pTabList->a[1].fg.jointype&(JT_LEFT|JT_CROSS))!=0)
+ || (pTabList->a[1].fg.jointype&(JT_OUTER|JT_CROSS))!=0)
){
continue;
}
@@ -140844,9 +144248,9 @@ SQLITE_PRIVATE int sqlite3Select(
*/
if( p->pPrior ){
rc = multiSelect(pParse, p, pDest);
-#if SELECTTRACE_ENABLED
+#if TREETRACE_ENABLED
SELECTTRACE(0x1,pParse,p,("end compound-select processing\n"));
- if( (sqlite3SelectTrace & 0x2000)!=0 && ExplainQueryPlanParent(pParse)==0 ){
+ if( (sqlite3TreeTrace & 0x2000)!=0 && ExplainQueryPlanParent(pParse)==0 ){
sqlite3TreeViewSelect(0, p, 0);
}
#endif
@@ -140865,8 +144269,8 @@ SQLITE_PRIVATE int sqlite3Select(
&& OptimizationEnabled(db, SQLITE_PropagateConst)
&& propagateConstants(pParse, p)
){
-#if SELECTTRACE_ENABLED
- if( sqlite3SelectTrace & 0x100 ){
+#if TREETRACE_ENABLED
+ if( sqlite3TreeTrace & 0x100 ){
SELECTTRACE(0x100,pParse,p,("After constant propagation:\n"));
sqlite3TreeViewSelect(0, p, 0);
}
@@ -140944,8 +144348,8 @@ SQLITE_PRIVATE int sqlite3Select(
|| (pItem->u2.pCteUse->eM10d!=M10d_Yes && pItem->u2.pCteUse->nUse<2))
&& pushDownWhereTerms(pParse, pSub, p->pWhere, pItem)
){
-#if SELECTTRACE_ENABLED
- if( sqlite3SelectTrace & 0x100 ){
+#if TREETRACE_ENABLED
+ if( sqlite3TreeTrace & 0x100 ){
SELECTTRACE(0x100,pParse,p,
("After WHERE-clause push-down into subquery %d:\n", pSub->selId));
sqlite3TreeViewSelect(0, p, 0);
@@ -140961,18 +144365,19 @@ SQLITE_PRIVATE int sqlite3Select(
/* Generate code to implement the subquery
**
- ** The subquery is implemented as a co-routine if:
+ ** The subquery is implemented as a co-routine if all of the following are
+ ** true:
+ **
** (1) the subquery is guaranteed to be the outer loop (so that
** it does not need to be computed more than once), and
** (2) the subquery is not a CTE that should be materialized
- **
- ** TODO: Are there other reasons beside (1) and (2) to use a co-routine
- ** implementation?
+ ** (3) the subquery is not part of a left operand for a RIGHT JOIN
*/
if( i==0
&& (pTabList->nSrc==1
- || (pTabList->a[1].fg.jointype&(JT_LEFT|JT_CROSS))!=0) /* (1) */
- && (pItem->fg.isCte==0 || pItem->u2.pCteUse->eM10d!=M10d_Yes) /* (2) */
+ || (pTabList->a[1].fg.jointype&(JT_OUTER|JT_CROSS))!=0) /* (1) */
+ && (pItem->fg.isCte==0 || pItem->u2.pCteUse->eM10d!=M10d_Yes) /* (2) */
+ && (pTabList->a[0].fg.jointype & JT_LTORJ)==0 /* (3) */
){
/* Implement a co-routine that will return a single row of the result
** set on each invocation.
@@ -141018,11 +144423,11 @@ SQLITE_PRIVATE int sqlite3Select(
** the same view can reuse the materialization. */
int topAddr;
int onceAddr = 0;
- int retAddr;
pItem->regReturn = ++pParse->nMem;
- topAddr = sqlite3VdbeAddOp2(v, OP_Integer, 0, pItem->regReturn);
+ topAddr = sqlite3VdbeAddOp0(v, OP_Goto);
pItem->addrFillSub = topAddr+1;
+ pItem->fg.isMaterialized = 1;
if( pItem->fg.isCorrelated==0 ){
/* If the subquery is not correlated and if we are not inside of
** a trigger, then we only need to compute the value of the subquery
@@ -141034,12 +144439,15 @@ SQLITE_PRIVATE int sqlite3Select(
}
sqlite3SelectDestInit(&dest, SRT_EphemTab, pItem->iCursor);
ExplainQueryPlan((pParse, 1, "MATERIALIZE %!S", pItem));
+ dest.zAffSdst = sqlite3TableAffinityStr(db, pItem->pTab);
sqlite3Select(pParse, pSub, &dest);
+ sqlite3DbFree(db, dest.zAffSdst);
+ dest.zAffSdst = 0;
pItem->pTab->nRowLogEst = pSub->nSelectRow;
if( onceAddr ) sqlite3VdbeJumpHere(v, onceAddr);
- retAddr = sqlite3VdbeAddOp1(v, OP_Return, pItem->regReturn);
+ sqlite3VdbeAddOp2(v, OP_Return, pItem->regReturn, topAddr+1);
VdbeComment((v, "end %!S", pItem));
- sqlite3VdbeChangeP1(v, topAddr, retAddr);
+ sqlite3VdbeJumpHere(v, topAddr);
sqlite3ClearTempRegCache(pParse);
if( pItem->fg.isCte && pItem->fg.isCorrelated==0 ){
CteUse *pCteUse = pItem->u2.pCteUse;
@@ -141063,8 +144471,8 @@ SQLITE_PRIVATE int sqlite3Select(
pHaving = p->pHaving;
sDistinct.isTnct = (p->selFlags & SF_Distinct)!=0;
-#if SELECTTRACE_ENABLED
- if( sqlite3SelectTrace & 0x400 ){
+#if TREETRACE_ENABLED
+ if( sqlite3TreeTrace & 0x400 ){
SELECTTRACE(0x400,pParse,p,("After all FROM-clause analysis:\n"));
sqlite3TreeViewSelect(0, p, 0);
}
@@ -141098,9 +144506,10 @@ SQLITE_PRIVATE int sqlite3Select(
** the sDistinct.isTnct is still set. Hence, isTnct represents the
** original setting of the SF_Distinct flag, not the current setting */
assert( sDistinct.isTnct );
+ sDistinct.isTnct = 2;
-#if SELECTTRACE_ENABLED
- if( sqlite3SelectTrace & 0x400 ){
+#if TREETRACE_ENABLED
+ if( sqlite3TreeTrace & 0x400 ){
SELECTTRACE(0x400,pParse,p,("Transform DISTINCT into GROUP BY:\n"));
sqlite3TreeViewSelect(0, p, 0);
}
@@ -141133,6 +144542,18 @@ SQLITE_PRIVATE int sqlite3Select(
*/
if( pDest->eDest==SRT_EphemTab ){
sqlite3VdbeAddOp2(v, OP_OpenEphemeral, pDest->iSDParm, pEList->nExpr);
+ if( p->selFlags & SF_NestedFrom ){
+ /* Delete or NULL-out result columns that will never be used */
+ int ii;
+ for(ii=pEList->nExpr-1; ii>0 && pEList->a[ii].fg.bUsed==0; ii--){
+ sqlite3ExprDelete(db, pEList->a[ii].pExpr);
+ sqlite3DbFree(db, pEList->a[ii].zEName);
+ pEList->nExpr--;
+ }
+ for(ii=0; ii<pEList->nExpr; ii++){
+ if( pEList->a[ii].fg.bUsed==0 ) pEList->a[ii].pExpr->op = TK_NULL;
+ }
+ }
}
/* Set the limiter.
@@ -141141,7 +144562,7 @@ SQLITE_PRIVATE int sqlite3Select(
if( (p->selFlags & SF_FixedLimit)==0 ){
p->nSelectRow = 320; /* 4 billion rows */
}
- computeLimitRegisters(pParse, p, iEnd);
+ if( p->pLimit ) computeLimitRegisters(pParse, p, iEnd);
if( p->iLimit==0 && sSort.addrSortIndex>=0 ){
sqlite3VdbeChangeOpcode(v, sSort.addrSortIndex, OP_SorterOpen);
sSort.sortFlags |= SORTFLAG_UseSorter;
@@ -141282,8 +144703,9 @@ SQLITE_PRIVATE int sqlite3Select(
** ORDER BY to maximize the chances of rows being delivered in an
** order that makes the ORDER BY redundant. */
for(ii=0; ii<pGroupBy->nExpr; ii++){
- u8 sortFlags = sSort.pOrderBy->a[ii].sortFlags & KEYINFO_ORDER_DESC;
- pGroupBy->a[ii].sortFlags = sortFlags;
+ u8 sortFlags;
+ sortFlags = sSort.pOrderBy->a[ii].fg.sortFlags & KEYINFO_ORDER_DESC;
+ pGroupBy->a[ii].fg.sortFlags = sortFlags;
}
if( sqlite3ExprListCompare(pGroupBy, sSort.pOrderBy, -1)==0 ){
orderByGrp = 1;
@@ -141352,8 +144774,8 @@ SQLITE_PRIVATE int sqlite3Select(
}
pAggInfo->mxReg = pParse->nMem;
if( db->mallocFailed ) goto select_end;
-#if SELECTTRACE_ENABLED
- if( sqlite3SelectTrace & 0x400 ){
+#if TREETRACE_ENABLED
+ if( sqlite3TreeTrace & 0x400 ){
int ii;
SELECTTRACE(0x400,pParse,p,("After aggregate analysis %p:\n", pAggInfo));
sqlite3TreeViewSelect(0, p, 0);
@@ -141362,8 +144784,13 @@ SQLITE_PRIVATE int sqlite3Select(
sqlite3TreeViewExprList(0, pMinMaxOrderBy, 0, "ORDERBY");
}
for(ii=0; ii<pAggInfo->nColumn; ii++){
- sqlite3DebugPrintf("agg-column[%d] iMem=%d\n",
- ii, pAggInfo->aCol[ii].iMem);
+ struct AggInfo_col *pCol = &pAggInfo->aCol[ii];
+ sqlite3DebugPrintf(
+ "agg-column[%d] pTab=%s iTable=%d iColumn=%d iMem=%d"
+ " iSorterColumn=%d\n",
+ ii, pCol->pTab ? pCol->pTab->zName : "NULL",
+ pCol->iTable, pCol->iColumn, pCol->iMem,
+ pCol->iSorterColumn);
sqlite3TreeViewExpr(0, pAggInfo->aCol[ii].pCExpr, 0);
}
for(ii=0; ii<pAggInfo->nFunc; ii++){
@@ -141441,7 +144868,8 @@ SQLITE_PRIVATE int sqlite3Select(
sqlite3VdbeAddOp2(v, OP_Gosub, regReset, addrReset);
SELECTTRACE(1,pParse,p,("WhereBegin\n"));
pWInfo = sqlite3WhereBegin(pParse, pTabList, pWhere, pGroupBy, pDistinct,
- 0, (WHERE_GROUPBY|(orderByGrp ? WHERE_SORTBYGROUP : 0)|distFlag), 0
+ p, (sDistinct.isTnct==2 ? WHERE_DISTINCTBY : WHERE_GROUPBY)
+ | (orderByGrp ? WHERE_SORTBYGROUP : 0) | distFlag, 0
);
if( pWInfo==0 ){
sqlite3ExprListDelete(db, pDistinct);
@@ -141483,15 +144911,15 @@ SQLITE_PRIVATE int sqlite3Select(
regBase = sqlite3GetTempRange(pParse, nCol);
sqlite3ExprCodeExprList(pParse, pGroupBy, regBase, 0, 0);
j = nGroupBy;
+ pAggInfo->directMode = 1;
for(i=0; i<pAggInfo->nColumn; i++){
struct AggInfo_col *pCol = &pAggInfo->aCol[i];
if( pCol->iSorterColumn>=j ){
- int r1 = j + regBase;
- sqlite3ExprCodeGetColumnOfTable(v,
- pCol->pTab, pCol->iTable, pCol->iColumn, r1);
+ sqlite3ExprCode(pParse, pCol->pCExpr, j + regBase);
j++;
}
}
+ pAggInfo->directMode = 0;
regRecord = sqlite3GetTempReg(pParse);
sqlite3VdbeAddOp3(v, OP_MakeRecord, regBase, nCol, regRecord);
sqlite3VdbeAddOp2(v, OP_SorterInsert, pAggInfo->sortingIdx, regRecord);
@@ -141623,7 +145051,7 @@ SQLITE_PRIVATE int sqlite3Select(
VdbeComment((v, "indicate accumulator empty"));
sqlite3VdbeAddOp1(v, OP_Return, regReset);
- if( eDist!=WHERE_DISTINCT_NOOP ){
+ if( distFlag!=0 && eDist!=WHERE_DISTINCT_NOOP ){
struct AggInfo_func *pF = &pAggInfo->aFunc[0];
fixDistinctOpenEph(pParse, eDist, pF->iDistinct, pF->iDistAddr);
}
@@ -141739,7 +145167,7 @@ SQLITE_PRIVATE int sqlite3Select(
SELECTTRACE(1,pParse,p,("WhereBegin\n"));
pWInfo = sqlite3WhereBegin(pParse, pTabList, pWhere, pMinMaxOrderBy,
- pDistinct, 0, minMaxFlag|distFlag, 0);
+ pDistinct, p, minMaxFlag|distFlag, 0);
if( pWInfo==0 ){
goto select_end;
}
@@ -141747,8 +145175,10 @@ SQLITE_PRIVATE int sqlite3Select(
eDist = sqlite3WhereIsDistinct(pWInfo);
updateAccumulator(pParse, regAcc, pAggInfo, eDist);
if( eDist!=WHERE_DISTINCT_NOOP ){
- struct AggInfo_func *pF = &pAggInfo->aFunc[0];
- fixDistinctOpenEph(pParse, eDist, pF->iDistinct, pF->iDistAddr);
+ struct AggInfo_func *pF = pAggInfo->aFunc;
+ if( pF ){
+ fixDistinctOpenEph(pParse, eDist, pF->iDistinct, pF->iDistAddr);
+ }
}
if( regAcc ) sqlite3VdbeAddOp2(v, OP_Integer, 1, regAcc);
@@ -141815,9 +145245,9 @@ select_end:
}
#endif
-#if SELECTTRACE_ENABLED
+#if TREETRACE_ENABLED
SELECTTRACE(0x1,pParse,p,("end processing\n"));
- if( (sqlite3SelectTrace & 0x2000)!=0 && ExplainQueryPlanParent(pParse)==0 ){
+ if( (sqlite3TreeTrace & 0x2000)!=0 && ExplainQueryPlanParent(pParse)==0 ){
sqlite3TreeViewSelect(0, p, 0);
}
#endif
@@ -142082,9 +145512,7 @@ SQLITE_PRIVATE Trigger *sqlite3TriggerList(Parse *pParse, Table *pTab){
Trigger *pList; /* List of triggers to return */
HashElem *p; /* Loop variable for TEMP triggers */
- if( pParse->disableTriggers ){
- return 0;
- }
+ assert( pParse->disableTriggers==0 );
pTmpSchema = pParse->db->aDb[1].pSchema;
p = sqliteHashFirst(&pTmpSchema->trigHash);
pList = pTab->pTrigger;
@@ -142093,15 +145521,14 @@ SQLITE_PRIVATE Trigger *sqlite3TriggerList(Parse *pParse, Table *pTab){
if( pTrig->pTabSchema==pTab->pSchema
&& pTrig->table
&& 0==sqlite3StrICmp(pTrig->table, pTab->zName)
- && pTrig->pTabSchema!=pTmpSchema
+ && (pTrig->pTabSchema!=pTmpSchema || pTrig->bReturning)
){
pTrig->pNext = pList;
pList = pTrig;
- }else if( pTrig->op==TK_RETURNING
+ }else if( pTrig->op==TK_RETURNING ){
#ifndef SQLITE_OMIT_VIRTUALTABLE
- && pParse->db->pVtabCtx==0
+ assert( pParse->db->pVtabCtx==0 );
#endif
- ){
assert( pParse->bReturning );
assert( &(pParse->u1.pReturning->retTrig) == pTrig );
pTrig->table = pTab->zName;
@@ -142384,6 +145811,23 @@ SQLITE_PRIVATE void sqlite3FinishTrigger(
Vdbe *v;
char *z;
+ /* If this is a new CREATE TABLE statement, and if shadow tables
+ ** are read-only, and the trigger makes a change to a shadow table,
+ ** then raise an error - do not allow the trigger to be created. */
+ if( sqlite3ReadOnlyShadowTables(db) ){
+ TriggerStep *pStep;
+ for(pStep=pTrig->step_list; pStep; pStep=pStep->pNext){
+ if( pStep->zTarget!=0
+ && sqlite3ShadowTableName(db, pStep->zTarget)
+ ){
+ sqlite3ErrorMsg(pParse,
+ "trigger \"%s\" may not write to shadow table \"%s\"",
+ pTrig->zName, pStep->zTarget);
+ goto triggerfinish_cleanup;
+ }
+ }
+ }
+
/* Make an entry in the sqlite_schema table */
v = sqlite3GetVdbe(pParse);
if( v==0 ) goto triggerfinish_cleanup;
@@ -142547,7 +145991,7 @@ SQLITE_PRIVATE TriggerStep *sqlite3TriggerInsertStep(
SQLITE_PRIVATE TriggerStep *sqlite3TriggerUpdateStep(
Parse *pParse, /* Parser */
Token *pTableName, /* Name of the table to be updated */
- SrcList *pFrom,
+ SrcList *pFrom, /* FROM clause for an UPDATE-FROM, or NULL */
ExprList *pEList, /* The SET clause: list of column and new values */
Expr *pWhere, /* The WHERE clause */
u8 orconf, /* The conflict algorithm. (OE_Abort, OE_Ignore, etc) */
@@ -142761,12 +146205,21 @@ static int checkColumnOverlap(IdList *pIdList, ExprList *pEList){
}
/*
+** Return true if any TEMP triggers exist
+*/
+static int tempTriggersExist(sqlite3 *db){
+ if( NEVER(db->aDb[1].pSchema==0) ) return 0;
+ if( sqliteHashFirst(&db->aDb[1].pSchema->trigHash)==0 ) return 0;
+ return 1;
+}
+
+/*
** Return a list of all triggers on table pTab if there exists at least
** one trigger that must be fired when an operation of type 'op' is
** performed on the table, and, if that operation is an UPDATE, if at
** least one of the columns in pChanges is being modified.
*/
-SQLITE_PRIVATE Trigger *sqlite3TriggersExist(
+static SQLITE_NOINLINE Trigger *triggersReallyExist(
Parse *pParse, /* Parse context */
Table *pTab, /* The table the contains the triggers */
int op, /* one of TK_DELETE, TK_INSERT, TK_UPDATE */
@@ -142829,6 +146282,22 @@ exit_triggers_exist:
}
return (mask ? pList : 0);
}
+SQLITE_PRIVATE Trigger *sqlite3TriggersExist(
+ Parse *pParse, /* Parse context */
+ Table *pTab, /* The table the contains the triggers */
+ int op, /* one of TK_DELETE, TK_INSERT, TK_UPDATE */
+ ExprList *pChanges, /* Columns that change in an UPDATE statement */
+ int *pMask /* OUT: Mask of TRIGGER_BEFORE|TRIGGER_AFTER */
+){
+ assert( pTab!=0 );
+ if( (pTab->pTrigger==0 && !tempTriggersExist(pParse->db))
+ || pParse->disableTriggers
+ ){
+ if( pMask ) *pMask = 0;
+ return 0;
+ }
+ return triggersReallyExist(pParse,pTab,op,pChanges,pMask);
+}
/*
** Convert the pStep->zTarget string into a SrcList and return a pointer
@@ -142858,6 +146327,14 @@ SQLITE_PRIVATE SrcList *sqlite3TriggerStepSrc(
}
if( pStep->pFrom ){
SrcList *pDup = sqlite3SrcListDup(db, pStep->pFrom, 0);
+ if( pDup && pDup->nSrc>1 && !IN_RENAME_OBJECT ){
+ Select *pSubquery;
+ Token as;
+ pSubquery = sqlite3SelectNew(pParse,0,pDup,0,0,0,0,SF_NestedFrom,0);
+ as.n = 0;
+ as.z = 0;
+ pDup = sqlite3SrcListAppendFromTerm(pParse,0,0,0,&as,pSubquery,0);
+ }
pSrc = sqlite3SrcListAppendList(pParse, pSrc, pDup);
}
}else{
@@ -142913,7 +146390,7 @@ static ExprList *sqlite3ExpandReturning(
if( !db->mallocFailed ){
struct ExprList_item *pItem = &pNew->a[pNew->nExpr-1];
pItem->zEName = sqlite3DbStrDup(db, pTab->aCol[jj].zCnName);
- pItem->eEName = ENAME_NAME;
+ pItem->fg.eEName = ENAME_NAME;
}
}
}else{
@@ -142922,7 +146399,7 @@ static ExprList *sqlite3ExpandReturning(
if( !db->mallocFailed && ALWAYS(pList->a[i].zEName!=0) ){
struct ExprList_item *pItem = &pNew->a[pNew->nExpr-1];
pItem->zEName = sqlite3DbStrDup(db, pList->a[i].zEName);
- pItem->eEName = pList->a[i].eEName;
+ pItem->fg.eEName = pList->a[i].fg.eEName;
}
}
}
@@ -143174,7 +146651,7 @@ static TriggerPrg *codeRowTrigger(
sSubParse.zAuthContext = pTrigger->zName;
sSubParse.eTriggerOp = pTrigger->op;
sSubParse.nQueryLoop = pParse->nQueryLoop;
- sSubParse.disableVtab = pParse->disableVtab;
+ sSubParse.prepFlags = pParse->prepFlags;
v = sqlite3GetVdbe(&sSubParse);
if( v ){
@@ -143520,11 +146997,14 @@ static void updateVirtualTable(
** it has been converted into REAL.
*/
SQLITE_PRIVATE void sqlite3ColumnDefault(Vdbe *v, Table *pTab, int i, int iReg){
+ Column *pCol;
assert( pTab!=0 );
- if( !IsView(pTab) ){
+ assert( pTab->nCol>i );
+ pCol = &pTab->aCol[i];
+ if( pCol->iDflt ){
sqlite3_value *pValue = 0;
u8 enc = ENC(sqlite3VdbeDb(v));
- Column *pCol = &pTab->aCol[i];
+ assert( !IsView(pTab) );
VdbeComment((v, "%s.%s", pTab->zName, pCol->zCnName));
assert( i<pTab->nCol );
sqlite3ValueFromExpr(sqlite3VdbeDb(v),
@@ -143535,7 +147015,7 @@ SQLITE_PRIVATE void sqlite3ColumnDefault(Vdbe *v, Table *pTab, int i, int iReg){
}
}
#ifndef SQLITE_OMIT_FLOATING_POINT
- if( pTab->aCol[i].affinity==SQLITE_AFF_REAL && !IsVirtual(pTab) ){
+ if( pCol->affinity==SQLITE_AFF_REAL && !IsVirtual(pTab) ){
sqlite3VdbeAddOp1(v, OP_RealAffinity, iReg);
}
#endif
@@ -143837,6 +147317,14 @@ SQLITE_PRIVATE void sqlite3Update(
# define isView 0
#endif
+#if TREETRACE_ENABLED
+ if( sqlite3TreeTrace & 0x10000 ){
+ sqlite3TreeViewLine(0, "In sqlite3Update() at %s:%d", __FILE__, __LINE__);
+ sqlite3TreeViewUpdate(pParse->pWith, pTabList, pChanges, pWhere,
+ onError, pOrderBy, pLimit, pUpsert, pTrigger);
+ }
+#endif
+
/* If there was a FROM clause, set nChangeFrom to the number of expressions
** in the change-list. Otherwise, set it to 0. There cannot be a FROM
** clause if this function is being called to generate code for part of
@@ -144481,7 +147969,7 @@ SQLITE_PRIVATE void sqlite3Update(
}else{
sqlite3VdbeAddOp3(v, OP_NotExists, iDataCur, labelContinue,regOldRowid);
}
- VdbeCoverageNeverTaken(v);
+ VdbeCoverage(v);
}
/* Do FK constraint checks. */
@@ -144967,6 +148455,7 @@ SQLITE_PRIVATE int sqlite3UpsertAnalyzeTarget(
if( pIdx->aiColumn[ii]==XN_EXPR ){
assert( pIdx->aColExpr!=0 );
assert( pIdx->aColExpr->nExpr>ii );
+ assert( pIdx->bHasExpr );
pExpr = pIdx->aColExpr->a[ii].pExpr;
if( pExpr->op!=TK_COLLATE ){
sCol[0].pLeft = pExpr;
@@ -145280,6 +148769,7 @@ SQLITE_PRIVATE SQLITE_NOINLINE int sqlite3RunVacuum(
int nDb; /* Number of attached databases */
const char *zDbMain; /* Schema name of database to vacuum */
const char *zOut; /* Name of output file */
+ u32 pgflags = PAGER_SYNCHRONOUS_OFF; /* sync flags for output db */
if( !db->autoCommit ){
sqlite3SetString(pzErrMsg, db, "cannot VACUUM from within a transaction");
@@ -145351,12 +148841,17 @@ SQLITE_PRIVATE SQLITE_NOINLINE int sqlite3RunVacuum(
goto end_of_vacuum;
}
db->mDbFlags |= DBFLAG_VacuumInto;
+
+ /* For a VACUUM INTO, the pager-flags are set to the same values as
+ ** they are for the database being vacuumed, except that PAGER_CACHESPILL
+ ** is always set. */
+ pgflags = db->aDb[iDb].safety_level | (db->flags & PAGER_FLAGS_MASK);
}
nRes = sqlite3BtreeGetRequestedReserve(pMain);
sqlite3BtreeSetCacheSize(pTemp, db->aDb[iDb].pSchema->cache_size);
sqlite3BtreeSetSpillSize(pTemp, sqlite3BtreeSetSpillSize(pMain,0));
- sqlite3BtreeSetPagerFlags(pTemp, PAGER_SYNCHRONOUS_OFF|PAGER_CACHESPILL);
+ sqlite3BtreeSetPagerFlags(pTemp, pgflags|PAGER_CACHESPILL);
/* Begin a transaction and take an exclusive lock on the main database
** file. This is done before the sqlite3BtreeGetPageSize(pMain) call below,
@@ -145487,6 +148982,7 @@ SQLITE_PRIVATE SQLITE_NOINLINE int sqlite3RunVacuum(
assert( rc==SQLITE_OK );
if( pOut==0 ){
+ nRes = sqlite3BtreeGetRequestedReserve(pTemp);
rc = sqlite3BtreeSetPageSize(pMain, sqlite3BtreeGetPageSize(pTemp), nRes,1);
}
@@ -145868,7 +149364,8 @@ SQLITE_PRIVATE void sqlite3VtabUnlockList(sqlite3 *db){
*/
SQLITE_PRIVATE void sqlite3VtabClear(sqlite3 *db, Table *p){
assert( IsVirtual(p) );
- if( !db || db->pnBytesFreed==0 ) vtabDisconnectAll(0, p);
+ assert( db!=0 );
+ if( db->pnBytesFreed==0 ) vtabDisconnectAll(0, p);
if( p->u.vtab.azArg ){
int i;
for(i=0; i<p->u.vtab.nArg; i++){
@@ -146668,7 +150165,7 @@ SQLITE_PRIVATE FuncDef *sqlite3VtabOverloadFunction(
if( pExpr->op!=TK_COLUMN ) return pDef;
assert( ExprUseYTab(pExpr) );
pTab = pExpr->y.pTab;
- if( pTab==0 ) return pDef;
+ if( NEVER(pTab==0) ) return pDef;
if( !IsVirtual(pTab) ) return pDef;
pVtab = sqlite3GetVTable(db, pTab)->pVtab;
assert( pVtab!=0 );
@@ -146929,6 +150426,28 @@ typedef struct WhereLoopBuilder WhereLoopBuilder;
typedef struct WhereScan WhereScan;
typedef struct WhereOrCost WhereOrCost;
typedef struct WhereOrSet WhereOrSet;
+typedef struct WhereMemBlock WhereMemBlock;
+typedef struct WhereRightJoin WhereRightJoin;
+
+/*
+** This object is a header on a block of allocated memory that will be
+** automatically freed when its WInfo oject is destructed.
+*/
+struct WhereMemBlock {
+ WhereMemBlock *pNext; /* Next block in the chain */
+ u64 sz; /* Bytes of space */
+};
+
+/*
+** Extra information attached to a WhereLevel that is a RIGHT JOIN.
+*/
+struct WhereRightJoin {
+ int iMatch; /* Cursor used to determine prior matched rows */
+ int regBloom; /* Bloom filter for iRJMatch */
+ int regReturn; /* Return register for the interior subroutine */
+ int addrSubrtn; /* Starting address for the interior subroutine */
+ int endSubrtn; /* The last opcode in the interior subroutine */
+};
/*
** This object contains information needed to implement a single nested
@@ -146962,6 +150481,7 @@ struct WhereLevel {
int addrLikeRep; /* LIKE range processing address */
#endif
int regFilter; /* Bloom filter */
+ WhereRightJoin *pRJ; /* Extra information for RIGHT JOIN */
u8 iFrom; /* Which entry in the FROM clause */
u8 op, p3, p5; /* Opcode, P3 & P5 of the opcode that ends the loop */
int p1, p2; /* Operands of the opcode used to end the loop */
@@ -147252,7 +150772,7 @@ struct WhereAndInfo {
** between VDBE cursor numbers and bits of the bitmasks in WhereTerm.
**
** The VDBE cursor numbers are small integers contained in
-** SrcList_item.iCursor and Expr.iTable fields. For any given WHERE
+** SrcItem.iCursor and Expr.iTable fields. For any given WHERE
** clause, the cursor numbers might not begin with 0 and they might
** contain gaps in the numbering sequence. But we want to make maximum
** use of the bits in our bitmasks. This structure provides a mapping
@@ -147324,20 +150844,6 @@ struct WhereLoopBuilder {
#endif
/*
-** Each instance of this object records a change to a single node
-** in an expression tree to cause that node to point to a column
-** of an index rather than an expression or a virtual column. All
-** such transformations need to be undone at the end of WHERE clause
-** processing.
-*/
-typedef struct WhereExprMod WhereExprMod;
-struct WhereExprMod {
- WhereExprMod *pNext; /* Next translation on a list of them all */
- Expr *pExpr; /* The Expr node that was transformed */
- Expr orig; /* Original value of the Expr node */
-};
-
-/*
** The WHERE clause processing routine has two halves. The
** first part does the start of the WHERE loop and the second
** half does the tail of the WHERE loop. An instance of
@@ -147352,10 +150858,10 @@ struct WhereInfo {
SrcList *pTabList; /* List of tables in the join */
ExprList *pOrderBy; /* The ORDER BY clause or NULL */
ExprList *pResultSet; /* Result set of the query */
+#if WHERETRACE_ENABLED
Expr *pWhere; /* The complete WHERE clause */
-#ifndef SQLITE_OMIT_VIRTUALTABLE
- Select *pLimit; /* Used to access LIMIT expr/registers for vtabs */
#endif
+ Select *pSelect; /* The entire SELECT statement containing WHERE */
int aiCurOnePass[2]; /* OP_OpenWrite cursors for the ONEPASS opt */
int iContinue; /* Jump here to continue with next record */
int iBreak; /* Jump here to break out of the loop */
@@ -147374,7 +150880,7 @@ struct WhereInfo {
int iTop; /* The very beginning of the WHERE loop */
int iEndWhere; /* End of the WHERE clause itself */
WhereLoop *pLoops; /* List of all WhereLoop objects */
- WhereExprMod *pExprMods; /* Expression modifications */
+ WhereMemBlock *pMemToFree;/* Memory to free when this object destroyed */
Bitmask revMask; /* Mask of ORDER BY terms that need reversing */
WhereClause sWC; /* Decomposition of the WHERE clause */
WhereMaskSet sMaskSet; /* Map cursor numbers to bitmasks */
@@ -147400,6 +150906,8 @@ SQLITE_PRIVATE WhereTerm *sqlite3WhereFindTerm(
u32 op, /* Mask of WO_xx values describing operator */
Index *pIdx /* Must be compatible with this index, if not NULL */
);
+SQLITE_PRIVATE void *sqlite3WhereMalloc(WhereInfo *pWInfo, u64 nByte);
+SQLITE_PRIVATE void *sqlite3WhereRealloc(WhereInfo *pWInfo, void *pOld, u64 nByte);
/* wherecode.c: */
#ifndef SQLITE_OMIT_EXPLAIN
@@ -147436,6 +150944,11 @@ SQLITE_PRIVATE Bitmask sqlite3WhereCodeOneLoopStart(
WhereLevel *pLevel, /* The current level pointer */
Bitmask notReady /* Which tables are currently available */
);
+SQLITE_PRIVATE SQLITE_NOINLINE void sqlite3WhereRightJoinLoop(
+ WhereInfo *pWInfo,
+ int iLevel,
+ WhereLevel *pLevel
+);
/* whereexpr.c: */
SQLITE_PRIVATE void sqlite3WhereClauseInit(WhereClause*,WhereInfo*);
@@ -147478,8 +150991,9 @@ SQLITE_PRIVATE void sqlite3WhereTabFuncArgs(Parse*, SrcItem*, WhereClause*);
#define WO_AND 0x0400 /* Two or more AND-connected terms */
#define WO_EQUIV 0x0800 /* Of the form A==B, both columns */
#define WO_NOOP 0x1000 /* This term does not restrict search space */
+#define WO_ROWVAL 0x2000 /* A row-value term */
-#define WO_ALL 0x1fff /* Mask of all possible WO_* values */
+#define WO_ALL 0x3fff /* Mask of all possible WO_* values */
#define WO_SINGLE 0x01ff /* Mask of all non-compound WO_* values */
/*
@@ -147513,6 +151027,7 @@ SQLITE_PRIVATE void sqlite3WhereTabFuncArgs(Parse*, SrcItem*, WhereClause*);
#define WHERE_BLOOMFILTER 0x00400000 /* Consider using a Bloom-filter */
#define WHERE_SELFCULL 0x00800000 /* nOut reduced by extra WHERE terms */
#define WHERE_OMIT_OFFSET 0x01000000 /* Set offset counter to zero */
+#define WHERE_VIEWSCAN 0x02000000 /* A full-scan of a VIEW or subquery */
#endif /* !defined(SQLITE_WHEREINT_H) */
@@ -147703,6 +151218,9 @@ SQLITE_PRIVATE int sqlite3WhereExplainOneScan(
pLoop->u.vtab.idxNum, pLoop->u.vtab.idxStr);
}
#endif
+ if( pItem->fg.jointype & JT_LEFT ){
+ sqlite3_str_appendf(&str, " LEFT-JOIN");
+ }
#ifdef SQLITE_EXPLAIN_ESTIMATED_ROWS
if( pLoop->nOut>=10 ){
sqlite3_str_appendf(&str, " (~%llu rows)",
@@ -147846,7 +151364,7 @@ static void disableTerm(WhereLevel *pLevel, WhereTerm *pTerm){
int nLoop = 0;
assert( pTerm!=0 );
while( (pTerm->wtFlags & TERM_CODED)==0
- && (pLevel->iLeftJoin==0 || ExprHasProperty(pTerm->pExpr, EP_FromJoin))
+ && (pLevel->iLeftJoin==0 || ExprHasProperty(pTerm->pExpr, EP_OuterON))
&& (pLevel->notReady & pTerm->prereqAll)==0
){
if( nLoop && (pTerm->wtFlags & TERM_LIKE)!=0 ){
@@ -148107,16 +151625,22 @@ static int codeEqualityTerm(
if( !ExprUseXSelect(pX) || pX->x.pSelect->pEList->nExpr==1 ){
eType = sqlite3FindInIndex(pParse, pX, IN_INDEX_LOOP, 0, 0, &iTab);
}else{
- sqlite3 *db = pParse->db;
- pX = removeUnindexableInClauseTerms(pParse, iEq, pLoop, pX);
-
- if( !db->mallocFailed ){
- aiMap = (int*)sqlite3DbMallocZero(pParse->db, sizeof(int)*nEq);
+ Expr *pExpr = pTerm->pExpr;
+ if( pExpr->iTable==0 || !ExprHasProperty(pExpr, EP_Subrtn) ){
+ sqlite3 *db = pParse->db;
+ pX = removeUnindexableInClauseTerms(pParse, iEq, pLoop, pX);
+ if( !db->mallocFailed ){
+ aiMap = (int*)sqlite3DbMallocZero(pParse->db, sizeof(int)*nEq);
+ eType = sqlite3FindInIndex(pParse, pX, IN_INDEX_LOOP, 0, aiMap,&iTab);
+ pExpr->iTable = iTab;
+ }
+ sqlite3ExprDelete(db, pX);
+ }else{
+ int n = sqlite3ExprVectorSize(pX->pLeft);
+ aiMap = (int*)sqlite3DbMallocZero(pParse->db, sizeof(int)*MAX(nEq,n));
eType = sqlite3FindInIndex(pParse, pX, IN_INDEX_LOOP, 0, aiMap, &iTab);
- pTerm->pExpr->iTable = iTab;
}
- sqlite3ExprDelete(db, pX);
- pX = pTerm->pExpr;
+ pX = pExpr;
}
if( eType==IN_INDEX_INDEX_DESC ){
@@ -148139,8 +151663,9 @@ static int codeEqualityTerm(
i = pLevel->u.in.nIn;
pLevel->u.in.nIn += nEq;
pLevel->u.in.aInLoop =
- sqlite3DbReallocOrFree(pParse->db, pLevel->u.in.aInLoop,
- sizeof(pLevel->u.in.aInLoop[0])*pLevel->u.in.nIn);
+ sqlite3WhereRealloc(pTerm->pWC->pWInfo,
+ pLevel->u.in.aInLoop,
+ sizeof(pLevel->u.in.aInLoop[0])*pLevel->u.in.nIn);
pIn = pLevel->u.in.aInLoop;
if( pIn ){
int iMap = 0; /* Index in aiMap[] */
@@ -148382,7 +151907,7 @@ static void whereLikeOptimizationStringFixup(
if( pTerm->wtFlags & TERM_LIKEOPT ){
VdbeOp *pOp;
assert( pLevel->iLikeRepCntr>0 );
- pOp = sqlite3VdbeGetOp(v, -1);
+ pOp = sqlite3VdbeGetLastOp(v);
assert( pOp!=0 );
assert( pOp->opcode==OP_String8
|| pTerm->pWC->pWInfo->pParse->db->mallocFailed );
@@ -148561,8 +152086,8 @@ static void codeCursorHint(
*/
if( pTabItem->fg.jointype & JT_LEFT ){
Expr *pExpr = pTerm->pExpr;
- if( !ExprHasProperty(pExpr, EP_FromJoin)
- || pExpr->w.iRightJoinTable!=pTabItem->iCursor
+ if( !ExprHasProperty(pExpr, EP_OuterON)
+ || pExpr->w.iJoin!=pTabItem->iCursor
){
sWalker.eCode = 0;
sWalker.xExprCallback = codeCursorHintIsOrFunction;
@@ -148570,7 +152095,7 @@ static void codeCursorHint(
if( sWalker.eCode ) continue;
}
}else{
- if( ExprHasProperty(pTerm->pExpr, EP_FromJoin) ) continue;
+ if( ExprHasProperty(pTerm->pExpr, EP_OuterON) ) continue;
}
/* All terms in pWLoop->aLTerm[] except pEndRange are used to initialize
@@ -148618,13 +152143,21 @@ static void codeCursorHint(
**
** OP_DeferredSeek $iCur $iRowid
**
+** Which causes a seek on $iCur to the row with rowid $iRowid.
+**
** However, if the scan currently being coded is a branch of an OR-loop and
-** the statement currently being coded is a SELECT, then P3 of OP_DeferredSeek
-** is set to iIdxCur and P4 is set to point to an array of integers
-** containing one entry for each column of the table cursor iCur is open
-** on. For each table column, if the column is the i'th column of the
-** index, then the corresponding array entry is set to (i+1). If the column
-** does not appear in the index at all, the array entry is set to 0.
+** the statement currently being coded is a SELECT, then additional information
+** is added that might allow OP_Column to omit the seek and instead do its
+** lookup on the index, thus avoiding an expensive seek operation. To
+** enable this optimization, the P3 of OP_DeferredSeek is set to iIdxCur
+** and P4 is set to an array of integers containing one entry for each column
+** in the table. For each table column, if the column is the i'th
+** column of the index, then the corresponding array entry is set to (i+1).
+** If the column does not appear in the index at all, the array entry is set
+** to 0. The OP_Column opcode can check this array to see if the column it
+** wants is in the index and if it is, it will substitute the index cursor
+** and column number and continue with those new values, rather than seeking
+** the table cursor.
*/
static void codeDeferredSeek(
WhereInfo *pWInfo, /* Where clause context */
@@ -148640,7 +152173,7 @@ static void codeDeferredSeek(
pWInfo->bDeferredSeek = 1;
sqlite3VdbeAddOp3(v, OP_DeferredSeek, iIdxCur, 0, iCur);
- if( (pWInfo->wctrlFlags & WHERE_OR_SUBCLAUSE)
+ if( (pWInfo->wctrlFlags & (WHERE_OR_SUBCLAUSE|WHERE_RIGHT_JOIN))
&& DbMaskAllZero(sqlite3ParseToplevel(pParse)->writeMask)
){
int i;
@@ -148698,144 +152231,6 @@ static void codeExprOrVector(Parse *pParse, Expr *p, int iReg, int nReg){
}
}
-/* An instance of the IdxExprTrans object carries information about a
-** mapping from an expression on table columns into a column in an index
-** down through the Walker.
-*/
-typedef struct IdxExprTrans {
- Expr *pIdxExpr; /* The index expression */
- int iTabCur; /* The cursor of the corresponding table */
- int iIdxCur; /* The cursor for the index */
- int iIdxCol; /* The column for the index */
- int iTabCol; /* The column for the table */
- WhereInfo *pWInfo; /* Complete WHERE clause information */
- sqlite3 *db; /* Database connection (for malloc()) */
-} IdxExprTrans;
-
-/*
-** Preserve pExpr on the WhereETrans list of the WhereInfo.
-*/
-static void preserveExpr(IdxExprTrans *pTrans, Expr *pExpr){
- WhereExprMod *pNew;
- pNew = sqlite3DbMallocRaw(pTrans->db, sizeof(*pNew));
- if( pNew==0 ) return;
- pNew->pNext = pTrans->pWInfo->pExprMods;
- pTrans->pWInfo->pExprMods = pNew;
- pNew->pExpr = pExpr;
- memcpy(&pNew->orig, pExpr, sizeof(*pExpr));
-}
-
-/* The walker node callback used to transform matching expressions into
-** a reference to an index column for an index on an expression.
-**
-** If pExpr matches, then transform it into a reference to the index column
-** that contains the value of pExpr.
-*/
-static int whereIndexExprTransNode(Walker *p, Expr *pExpr){
- IdxExprTrans *pX = p->u.pIdxTrans;
- if( sqlite3ExprCompare(0, pExpr, pX->pIdxExpr, pX->iTabCur)==0 ){
- pExpr = sqlite3ExprSkipCollate(pExpr);
- preserveExpr(pX, pExpr);
- pExpr->affExpr = sqlite3ExprAffinity(pExpr);
- pExpr->op = TK_COLUMN;
- pExpr->iTable = pX->iIdxCur;
- pExpr->iColumn = pX->iIdxCol;
- testcase( ExprHasProperty(pExpr, EP_Skip) );
- testcase( ExprHasProperty(pExpr, EP_Unlikely) );
- ExprClearProperty(pExpr, EP_Skip|EP_Unlikely|EP_WinFunc|EP_Subrtn);
- pExpr->y.pTab = 0;
- return WRC_Prune;
- }else{
- return WRC_Continue;
- }
-}
-
-#ifndef SQLITE_OMIT_GENERATED_COLUMNS
-/* A walker node callback that translates a column reference to a table
-** into a corresponding column reference of an index.
-*/
-static int whereIndexExprTransColumn(Walker *p, Expr *pExpr){
- if( pExpr->op==TK_COLUMN ){
- IdxExprTrans *pX = p->u.pIdxTrans;
- if( pExpr->iTable==pX->iTabCur && pExpr->iColumn==pX->iTabCol ){
- assert( ExprUseYTab(pExpr) && pExpr->y.pTab!=0 );
- preserveExpr(pX, pExpr);
- pExpr->affExpr = sqlite3TableColumnAffinity(pExpr->y.pTab,pExpr->iColumn);
- pExpr->iTable = pX->iIdxCur;
- pExpr->iColumn = pX->iIdxCol;
- pExpr->y.pTab = 0;
- }
- }
- return WRC_Continue;
-}
-#endif /* SQLITE_OMIT_GENERATED_COLUMNS */
-
-/*
-** For an indexes on expression X, locate every instance of expression X
-** in pExpr and change that subexpression into a reference to the appropriate
-** column of the index.
-**
-** 2019-10-24: Updated to also translate references to a VIRTUAL column in
-** the table into references to the corresponding (stored) column of the
-** index.
-*/
-static void whereIndexExprTrans(
- Index *pIdx, /* The Index */
- int iTabCur, /* Cursor of the table that is being indexed */
- int iIdxCur, /* Cursor of the index itself */
- WhereInfo *pWInfo /* Transform expressions in this WHERE clause */
-){
- int iIdxCol; /* Column number of the index */
- ExprList *aColExpr; /* Expressions that are indexed */
- Table *pTab;
- Walker w;
- IdxExprTrans x;
- aColExpr = pIdx->aColExpr;
- if( aColExpr==0 && !pIdx->bHasVCol ){
- /* The index does not reference any expressions or virtual columns
- ** so no translations are needed. */
- return;
- }
- pTab = pIdx->pTable;
- memset(&w, 0, sizeof(w));
- w.u.pIdxTrans = &x;
- x.iTabCur = iTabCur;
- x.iIdxCur = iIdxCur;
- x.pWInfo = pWInfo;
- x.db = pWInfo->pParse->db;
- for(iIdxCol=0; iIdxCol<pIdx->nColumn; iIdxCol++){
- i16 iRef = pIdx->aiColumn[iIdxCol];
- if( iRef==XN_EXPR ){
- assert( aColExpr!=0 && aColExpr->a[iIdxCol].pExpr!=0 );
- x.pIdxExpr = aColExpr->a[iIdxCol].pExpr;
- if( sqlite3ExprIsConstant(x.pIdxExpr) ) continue;
- w.xExprCallback = whereIndexExprTransNode;
-#ifndef SQLITE_OMIT_GENERATED_COLUMNS
- }else if( iRef>=0
- && (pTab->aCol[iRef].colFlags & COLFLAG_VIRTUAL)!=0
- && ((pTab->aCol[iRef].colFlags & COLFLAG_HASCOLL)==0
- || sqlite3StrICmp(sqlite3ColumnColl(&pTab->aCol[iRef]),
- sqlite3StrBINARY)==0)
- ){
- /* Check to see if there are direct references to generated columns
- ** that are contained in the index. Pulling the generated column
- ** out of the index is an optimization only - the main table is always
- ** available if the index cannot be used. To avoid unnecessary
- ** complication, omit this optimization if the collating sequence for
- ** the column is non-standard */
- x.iTabCol = iRef;
- w.xExprCallback = whereIndexExprTransColumn;
-#endif /* SQLITE_OMIT_GENERATED_COLUMNS */
- }else{
- continue;
- }
- x.iIdxCol = iIdxCol;
- sqlite3WalkExpr(&w, pWInfo->pWhere);
- sqlite3WalkExprList(&w, pWInfo->pOrderBy);
- sqlite3WalkExprList(&w, pWInfo->pResultSet);
- }
-}
-
/*
** The pTruth expression is always true because it is the WHERE clause
** a partial index that is driving a query loop. Look through all of the
@@ -148904,6 +152299,8 @@ static SQLITE_NOINLINE void filterPullDown(
testcase( pTerm->wtFlags & TERM_VIRTUAL );
regRowid = sqlite3GetTempReg(pParse);
regRowid = codeEqualityTerm(pParse, pTerm, pLevel, 0, 0, regRowid);
+ sqlite3VdbeAddOp2(pParse->pVdbe, OP_MustBeInt, regRowid, addrNxt);
+ VdbeCoverage(pParse->pVdbe);
sqlite3VdbeAddOp4Int(pParse->pVdbe, OP_Filter, pLevel->regFilter,
addrNxt, regRowid, 1);
VdbeCoverage(pParse->pVdbe);
@@ -148996,7 +152393,7 @@ SQLITE_PRIVATE Bitmask sqlite3WhereCodeOneLoopStart(
** initialize a memory cell that records if this table matches any
** row of the left table of the join.
*/
- assert( (pWInfo->wctrlFlags & WHERE_OR_SUBCLAUSE)
+ assert( (pWInfo->wctrlFlags & (WHERE_OR_SUBCLAUSE|WHERE_RIGHT_JOIN))
|| pLevel->iFrom>0 || (pTabItem[0].fg.jointype & JT_LEFT)==0
);
if( pLevel->iFrom>0 && (pTabItem[0].fg.jointype & JT_LEFT)!=0 ){
@@ -149007,7 +152404,10 @@ SQLITE_PRIVATE Bitmask sqlite3WhereCodeOneLoopStart(
/* Compute a safe address to jump to if we discover that the table for
** this loop is empty and can never contribute content. */
- for(j=iLevel; j>0 && pWInfo->a[j].iLeftJoin==0; j--){}
+ for(j=iLevel; j>0; j--){
+ if( pWInfo->a[j].iLeftJoin ) break;
+ if( pWInfo->a[j].pRJ ) break;
+ }
addrHalt = pWInfo->a[j].addrBrk;
/* Special case of a FROM clause subquery implemented as a co-routine */
@@ -149052,9 +152452,9 @@ SQLITE_PRIVATE Bitmask sqlite3WhereCodeOneLoopStart(
&& pLoop->u.vtab.bOmitOffset
){
assert( pTerm->eOperator==WO_AUX );
- assert( pWInfo->pLimit!=0 );
- assert( pWInfo->pLimit->iOffset>0 );
- sqlite3VdbeAddOp2(v, OP_Integer, 0, pWInfo->pLimit->iOffset);
+ assert( pWInfo->pSelect!=0 );
+ assert( pWInfo->pSelect->iOffset>0 );
+ sqlite3VdbeAddOp2(v, OP_Integer, 0, pWInfo->pSelect->iOffset);
VdbeComment((v,"Zero OFFSET counter"));
}
}
@@ -149162,6 +152562,8 @@ SQLITE_PRIVATE Bitmask sqlite3WhereCodeOneLoopStart(
if( iRowidReg!=iReleaseReg ) sqlite3ReleaseTempReg(pParse, iReleaseReg);
addrNxt = pLevel->addrNxt;
if( pLevel->regFilter ){
+ sqlite3VdbeAddOp2(v, OP_MustBeInt, iRowidReg, addrNxt);
+ VdbeCoverage(v);
sqlite3VdbeAddOp4Int(v, OP_Filter, pLevel->regFilter, addrNxt,
iRowidReg, 1);
VdbeCoverage(v);
@@ -149513,6 +152915,11 @@ SQLITE_PRIVATE Bitmask sqlite3WhereCodeOneLoopStart(
** guess. */
addrSeekScan = sqlite3VdbeAddOp1(v, OP_SeekScan,
(pIdx->aiRowLogEst[0]+9)/10);
+ if( pRangeStart ){
+ sqlite3VdbeChangeP5(v, 1);
+ sqlite3VdbeChangeP2(v, addrSeekScan, sqlite3VdbeCurrentAddr(v)+1);
+ addrSeekScan = 0;
+ }
VdbeCoverage(v);
}
sqlite3VdbeAddOp4Int(v, op, iIdxCur, addrNxt, regBase, nConstraint);
@@ -149588,8 +152995,8 @@ SQLITE_PRIVATE Bitmask sqlite3WhereCodeOneLoopStart(
}
nConstraint++;
}
- sqlite3DbFree(db, zStartAff);
- sqlite3DbFree(db, zEndAff);
+ if( zStartAff ) sqlite3DbNNFreeNN(db, zStartAff);
+ if( zEndAff ) sqlite3DbNNFreeNN(db, zEndAff);
/* Top of the loop body */
if( pLevel->p2==0 ) pLevel->p2 = sqlite3VdbeCurrentAddr(v);
@@ -149634,7 +153041,7 @@ SQLITE_PRIVATE Bitmask sqlite3WhereCodeOneLoopStart(
/* Seek the table cursor, if required */
omitTable = (pLoop->wsFlags & WHERE_IDX_ONLY)!=0
- && (pWInfo->wctrlFlags & WHERE_OR_SUBCLAUSE)==0;
+ && (pWInfo->wctrlFlags & (WHERE_OR_SUBCLAUSE|WHERE_RIGHT_JOIN))==0;
if( omitTable ){
/* pIdx is a covering index. No need to access the main table. */
}else if( HasRowid(pIdx->pTable) ){
@@ -149651,27 +153058,6 @@ SQLITE_PRIVATE Bitmask sqlite3WhereCodeOneLoopStart(
}
if( pLevel->iLeftJoin==0 ){
- /* If pIdx is an index on one or more expressions, then look through
- ** all the expressions in pWInfo and try to transform matching expressions
- ** into reference to index columns. Also attempt to translate references
- ** to virtual columns in the table into references to (stored) columns
- ** of the index.
- **
- ** Do not do this for the RHS of a LEFT JOIN. This is because the
- ** expression may be evaluated after OP_NullRow has been executed on
- ** the cursor. In this case it is important to do the full evaluation,
- ** as the result of the expression may not be NULL, even if all table
- ** column values are. https://www.sqlite.org/src/info/7fa8049685b50b5a
- **
- ** Also, do not do this when processing one index an a multi-index
- ** OR clause, since the transformation will become invalid once we
- ** move forward to the next index.
- ** https://sqlite.org/src/info/4e8e4857d32d401f
- */
- if( (pWInfo->wctrlFlags & WHERE_OR_SUBCLAUSE)==0 ){
- whereIndexExprTrans(pIdx, iCur, iIdxCur, pWInfo);
- }
-
/* If a partial index is driving the loop, try to eliminate WHERE clause
** terms from the query that must be true due to the WHERE clause of
** the partial index.
@@ -149687,7 +153073,7 @@ SQLITE_PRIVATE Bitmask sqlite3WhereCodeOneLoopStart(
/* The following assert() is not a requirement, merely an observation:
** The OR-optimization doesn't work for the right hand table of
** a LEFT JOIN: */
- assert( (pWInfo->wctrlFlags & WHERE_OR_SUBCLAUSE)==0 );
+ assert( (pWInfo->wctrlFlags & (WHERE_OR_SUBCLAUSE|WHERE_RIGHT_JOIN))==0 );
}
/* Record the instruction used to terminate the loop. */
@@ -149784,7 +153170,7 @@ SQLITE_PRIVATE Bitmask sqlite3WhereCodeOneLoopStart(
int nNotReady; /* The number of notReady tables */
SrcItem *origSrc; /* Original list of tables */
nNotReady = pWInfo->nLevel - iLevel - 1;
- pOrTab = sqlite3StackAllocRaw(db,
+ pOrTab = sqlite3DbMallocRawNN(db,
sizeof(*pOrTab)+ nNotReady*sizeof(pOrTab->a[0]));
if( pOrTab==0 ) return notReady;
pOrTab->nAlloc = (u8)(nNotReady + 1);
@@ -149891,7 +153277,7 @@ SQLITE_PRIVATE Bitmask sqlite3WhereCodeOneLoopStart(
Expr *pDelete; /* Local copy of OR clause term */
int jmp1 = 0; /* Address of jump operation */
testcase( (pTabItem[0].fg.jointype & JT_LEFT)!=0
- && !ExprHasProperty(pOrExpr, EP_FromJoin)
+ && !ExprHasProperty(pOrExpr, EP_OuterON)
); /* See TH3 vtab25.400 and ticket 614b25314c766238 */
pDelete = pOrExpr = sqlite3ExprDup(db, pOrExpr, 0);
if( db->mallocFailed ){
@@ -150029,7 +153415,15 @@ SQLITE_PRIVATE Bitmask sqlite3WhereCodeOneLoopStart(
sqlite3VdbeGoto(v, pLevel->addrBrk);
sqlite3VdbeResolveLabel(v, iLoopBody);
- if( pWInfo->nLevel>1 ){ sqlite3StackFree(db, pOrTab); }
+ /* Set the P2 operand of the OP_Return opcode that will end the current
+ ** loop to point to this spot, which is the top of the next containing
+ ** loop. The byte-code formatter will use that P2 value as a hint to
+ ** indent everything in between the this point and the final OP_Return.
+ ** See tag-20220407a in vdbe.c and shell.c */
+ assert( pLevel->op==OP_Return );
+ pLevel->p2 = sqlite3VdbeCurrentAddr(v);
+
+ if( pWInfo->nLevel>1 ){ sqlite3DbFreeNN(db, pOrTab); }
if( !untestedTerms ) disableTerm(pLevel, pTerm);
}else
#endif /* SQLITE_OMIT_OR_OPTIMIZATION */
@@ -150091,10 +153485,22 @@ SQLITE_PRIVATE Bitmask sqlite3WhereCodeOneLoopStart(
}
pE = pTerm->pExpr;
assert( pE!=0 );
- if( (pTabItem->fg.jointype&JT_LEFT) && !ExprHasProperty(pE,EP_FromJoin) ){
- continue;
+ if( pTabItem->fg.jointype & (JT_LEFT|JT_LTORJ|JT_RIGHT) ){
+ if( !ExprHasProperty(pE,EP_OuterON|EP_InnerON) ){
+ /* Defer processing WHERE clause constraints until after outer
+ ** join processing. tag-20220513a */
+ continue;
+ }else if( (pTabItem->fg.jointype & JT_LEFT)==JT_LEFT
+ && !ExprHasProperty(pE,EP_OuterON) ){
+ continue;
+ }else{
+ Bitmask m = sqlite3WhereGetMask(&pWInfo->sMaskSet, pE->w.iJoin);
+ if( m & pLevel->notReady ){
+ /* An ON clause that is not ripe */
+ continue;
+ }
+ }
}
-
if( iLoop==1 && !sqlite3ExprCoveredByIndex(pE, pLevel->iTabCur, pIdx) ){
iNext = 2;
continue;
@@ -150153,7 +153559,7 @@ SQLITE_PRIVATE Bitmask sqlite3WhereCodeOneLoopStart(
if( (pTerm->eOperator & (WO_EQ|WO_IS))==0 ) continue;
if( (pTerm->eOperator & WO_EQUIV)==0 ) continue;
if( pTerm->leftCursor!=iCur ) continue;
- if( pTabItem->fg.jointype & JT_LEFT ) continue;
+ if( pTabItem->fg.jointype & (JT_LEFT|JT_LTORJ|JT_RIGHT) ) continue;
pE = pTerm->pExpr;
#ifdef WHERETRACE_ENABLED /* 0x800 */
if( sqlite3WhereTrace & 0x800 ){
@@ -150161,7 +153567,7 @@ SQLITE_PRIVATE Bitmask sqlite3WhereCodeOneLoopStart(
sqlite3WhereTermPrint(pTerm, pWC->nTerm-j);
}
#endif
- assert( !ExprHasProperty(pE, EP_FromJoin) );
+ assert( !ExprHasProperty(pE, EP_OuterON) );
assert( (pTerm->prereqRight & pLevel->notReady)!=0 );
assert( (pTerm->eOperator & (WO_OR|WO_AND))==0 );
pAlt = sqlite3WhereFindTerm(pWC, iCur, pTerm->u.x.leftColumn, notReady,
@@ -150184,6 +153590,47 @@ SQLITE_PRIVATE Bitmask sqlite3WhereCodeOneLoopStart(
pAlt->wtFlags |= TERM_CODED;
}
+ /* For a RIGHT OUTER JOIN, record the fact that the current row has
+ ** been matched at least once.
+ */
+ if( pLevel->pRJ ){
+ Table *pTab;
+ int nPk;
+ int r;
+ int jmp1 = 0;
+ WhereRightJoin *pRJ = pLevel->pRJ;
+
+ /* pTab is the right-hand table of the RIGHT JOIN. Generate code that
+ ** will record that the current row of that table has been matched at
+ ** least once. This is accomplished by storing the PK for the row in
+ ** both the iMatch index and the regBloom Bloom filter.
+ */
+ pTab = pWInfo->pTabList->a[pLevel->iFrom].pTab;
+ if( HasRowid(pTab) ){
+ r = sqlite3GetTempRange(pParse, 2);
+ sqlite3ExprCodeGetColumnOfTable(v, pTab, pLevel->iTabCur, -1, r+1);
+ nPk = 1;
+ }else{
+ int iPk;
+ Index *pPk = sqlite3PrimaryKeyIndex(pTab);
+ nPk = pPk->nKeyCol;
+ r = sqlite3GetTempRange(pParse, nPk+1);
+ for(iPk=0; iPk<nPk; iPk++){
+ int iCol = pPk->aiColumn[iPk];
+ sqlite3ExprCodeGetColumnOfTable(v, pTab, iCur, iCol,r+1+iPk);
+ }
+ }
+ jmp1 = sqlite3VdbeAddOp4Int(v, OP_Found, pRJ->iMatch, 0, r+1, nPk);
+ VdbeCoverage(v);
+ VdbeComment((v, "match against %s", pTab->zName));
+ sqlite3VdbeAddOp3(v, OP_MakeRecord, r+1, nPk, r);
+ sqlite3VdbeAddOp4Int(v, OP_IdxInsert, pRJ->iMatch, r, r+1, nPk);
+ sqlite3VdbeAddOp4Int(v, OP_FilterAdd, pRJ->regBloom, 0, r+1, nPk);
+ sqlite3VdbeChangeP5(v, OPFLAG_USESEEKRESULT);
+ sqlite3VdbeJumpHere(v, jmp1);
+ sqlite3ReleaseTempRange(pParse, r, nPk+1);
+ }
+
/* For a LEFT OUTER JOIN, generate code that will record the fact that
** at least one row of the right table has matched the left table.
*/
@@ -150191,6 +153638,30 @@ SQLITE_PRIVATE Bitmask sqlite3WhereCodeOneLoopStart(
pLevel->addrFirst = sqlite3VdbeCurrentAddr(v);
sqlite3VdbeAddOp2(v, OP_Integer, 1, pLevel->iLeftJoin);
VdbeComment((v, "record LEFT JOIN hit"));
+ if( pLevel->pRJ==0 ){
+ goto code_outer_join_constraints; /* WHERE clause constraints */
+ }
+ }
+
+ if( pLevel->pRJ ){
+ /* Create a subroutine used to process all interior loops and code
+ ** of the RIGHT JOIN. During normal operation, the subroutine will
+ ** be in-line with the rest of the code. But at the end, a separate
+ ** loop will run that invokes this subroutine for unmatched rows
+ ** of pTab, with all tables to left begin set to NULL.
+ */
+ WhereRightJoin *pRJ = pLevel->pRJ;
+ sqlite3VdbeAddOp2(v, OP_BeginSubrtn, 0, pRJ->regReturn);
+ pRJ->addrSubrtn = sqlite3VdbeCurrentAddr(v);
+ assert( pParse->withinRJSubrtn < 255 );
+ pParse->withinRJSubrtn++;
+
+ /* WHERE clause constraints must be deferred until after outer join
+ ** row elimination has completed, since WHERE clause constraints apply
+ ** to the results of the OUTER JOIN. The following loop generates the
+ ** appropriate WHERE clause constraint checks. tag-20220513a.
+ */
+ code_outer_join_constraints:
for(pTerm=pWC->a, j=0; j<pWC->nBase; j++, pTerm++){
testcase( pTerm->wtFlags & TERM_VIRTUAL );
testcase( pTerm->wtFlags & TERM_CODED );
@@ -150199,6 +153670,7 @@ SQLITE_PRIVATE Bitmask sqlite3WhereCodeOneLoopStart(
assert( pWInfo->untestedTerms );
continue;
}
+ if( pTabItem->fg.jointype & JT_LTORJ ) continue;
assert( pTerm->pExpr );
sqlite3ExprIfFalse(pParse, pTerm->pExpr, addrCont, SQLITE_JUMPIFNULL);
pTerm->wtFlags |= TERM_CODED;
@@ -150219,6 +153691,96 @@ SQLITE_PRIVATE Bitmask sqlite3WhereCodeOneLoopStart(
return pLevel->notReady;
}
+/*
+** Generate the code for the loop that finds all non-matched terms
+** for a RIGHT JOIN.
+*/
+SQLITE_PRIVATE SQLITE_NOINLINE void sqlite3WhereRightJoinLoop(
+ WhereInfo *pWInfo,
+ int iLevel,
+ WhereLevel *pLevel
+){
+ Parse *pParse = pWInfo->pParse;
+ Vdbe *v = pParse->pVdbe;
+ WhereRightJoin *pRJ = pLevel->pRJ;
+ Expr *pSubWhere = 0;
+ WhereClause *pWC = &pWInfo->sWC;
+ WhereInfo *pSubWInfo;
+ WhereLoop *pLoop = pLevel->pWLoop;
+ SrcItem *pTabItem = &pWInfo->pTabList->a[pLevel->iFrom];
+ SrcList sFrom;
+ Bitmask mAll = 0;
+ int k;
+
+ ExplainQueryPlan((pParse, 1, "RIGHT-JOIN %s", pTabItem->pTab->zName));
+ sqlite3VdbeNoJumpsOutsideSubrtn(v, pRJ->addrSubrtn, pRJ->endSubrtn,
+ pRJ->regReturn);
+ for(k=0; k<iLevel; k++){
+ int iIdxCur;
+ mAll |= pWInfo->a[k].pWLoop->maskSelf;
+ sqlite3VdbeAddOp1(v, OP_NullRow, pWInfo->a[k].iTabCur);
+ iIdxCur = pWInfo->a[k].iIdxCur;
+ if( iIdxCur ){
+ sqlite3VdbeAddOp1(v, OP_NullRow, iIdxCur);
+ }
+ }
+ if( (pTabItem->fg.jointype & JT_LTORJ)==0 ){
+ mAll |= pLoop->maskSelf;
+ for(k=0; k<pWC->nTerm; k++){
+ WhereTerm *pTerm = &pWC->a[k];
+ if( (pTerm->wtFlags & (TERM_VIRTUAL|TERM_SLICE))!=0
+ && pTerm->eOperator!=WO_ROWVAL
+ ){
+ break;
+ }
+ if( pTerm->prereqAll & ~mAll ) continue;
+ if( ExprHasProperty(pTerm->pExpr, EP_OuterON|EP_InnerON) ) continue;
+ pSubWhere = sqlite3ExprAnd(pParse, pSubWhere,
+ sqlite3ExprDup(pParse->db, pTerm->pExpr, 0));
+ }
+ }
+ sFrom.nSrc = 1;
+ sFrom.nAlloc = 1;
+ memcpy(&sFrom.a[0], pTabItem, sizeof(SrcItem));
+ sFrom.a[0].fg.jointype = 0;
+ assert( pParse->withinRJSubrtn < 100 );
+ pParse->withinRJSubrtn++;
+ pSubWInfo = sqlite3WhereBegin(pParse, &sFrom, pSubWhere, 0, 0, 0,
+ WHERE_RIGHT_JOIN, 0);
+ if( pSubWInfo ){
+ int iCur = pLevel->iTabCur;
+ int r = ++pParse->nMem;
+ int nPk;
+ int jmp;
+ int addrCont = sqlite3WhereContinueLabel(pSubWInfo);
+ Table *pTab = pTabItem->pTab;
+ if( HasRowid(pTab) ){
+ sqlite3ExprCodeGetColumnOfTable(v, pTab, iCur, -1, r);
+ nPk = 1;
+ }else{
+ int iPk;
+ Index *pPk = sqlite3PrimaryKeyIndex(pTab);
+ nPk = pPk->nKeyCol;
+ pParse->nMem += nPk - 1;
+ for(iPk=0; iPk<nPk; iPk++){
+ int iCol = pPk->aiColumn[iPk];
+ sqlite3ExprCodeGetColumnOfTable(v, pTab, iCur, iCol,r+iPk);
+ }
+ }
+ jmp = sqlite3VdbeAddOp4Int(v, OP_Filter, pRJ->regBloom, 0, r, nPk);
+ VdbeCoverage(v);
+ sqlite3VdbeAddOp4Int(v, OP_Found, pRJ->iMatch, addrCont, r, nPk);
+ VdbeCoverage(v);
+ sqlite3VdbeJumpHere(v, jmp);
+ sqlite3VdbeAddOp2(v, OP_Gosub, pRJ->regReturn, pRJ->addrSubrtn);
+ sqlite3WhereEnd(pSubWInfo);
+ }
+ sqlite3ExprDelete(pParse->db, pSubWhere);
+ ExplainQueryPlanPop(pParse);
+ assert( pParse->withinRJSubrtn>0 );
+ pParse->withinRJSubrtn--;
+}
+
/************** End of wherecode.c *******************************************/
/************** Begin file whereexpr.c ***************************************/
/*
@@ -150287,7 +153849,7 @@ static int whereClauseInsert(WhereClause *pWC, Expr *p, u16 wtFlags){
if( pWC->nTerm>=pWC->nSlot ){
WhereTerm *pOld = pWC->a;
sqlite3 *db = pWC->pWInfo->pParse->db;
- pWC->a = sqlite3DbMallocRawNN(db, sizeof(pWC->a[0])*pWC->nSlot*2 );
+ pWC->a = sqlite3WhereMalloc(pWC->pWInfo, sizeof(pWC->a[0])*pWC->nSlot*2 );
if( pWC->a==0 ){
if( wtFlags & TERM_DYNAMIC ){
sqlite3ExprDelete(db, p);
@@ -150296,10 +153858,7 @@ static int whereClauseInsert(WhereClause *pWC, Expr *p, u16 wtFlags){
return 0;
}
memcpy(pWC->a, pOld, sizeof(pWC->a[0])*pWC->nTerm);
- if( pOld!=pWC->aStatic ){
- sqlite3DbFree(db, pOld);
- }
- pWC->nSlot = sqlite3DbMallocSize(db, pWC->a)/sizeof(pWC->a[0]);
+ pWC->nSlot = pWC->nSlot*2;
}
pTerm = &pWC->a[idx = pWC->nTerm++];
if( (wtFlags & TERM_VIRTUAL)==0 ) pWC->nBase = pWC->nTerm;
@@ -150492,7 +154051,7 @@ static int isLikeOrGlob(
if( pLeft->op!=TK_COLUMN
|| sqlite3ExprAffinity(pLeft)!=SQLITE_AFF_TEXT
|| (ALWAYS( ExprUseYTab(pLeft) )
- && pLeft->y.pTab
+ && ALWAYS(pLeft->y.pTab)
&& IsVirtual(pLeft->y.pTab)) /* Might be numeric */
){
int isNum;
@@ -150609,8 +154168,7 @@ static int isAuxiliaryVtabOperator(
** MATCH(expression,vtab_column)
*/
pCol = pList->a[1].pExpr;
- assert( pCol->op!=TK_COLUMN || ExprUseYTab(pCol) );
- testcase( pCol->op==TK_COLUMN && pCol->y.pTab==0 );
+ assert( pCol->op!=TK_COLUMN || (ExprUseYTab(pCol) && pCol->y.pTab!=0) );
if( ExprIsVtab(pCol) ){
for(i=0; i<ArraySize(aOp); i++){
assert( !ExprHasProperty(pExpr, EP_IntValue) );
@@ -150635,7 +154193,7 @@ static int isAuxiliaryVtabOperator(
*/
pCol = pList->a[0].pExpr;
assert( pCol->op!=TK_COLUMN || ExprUseYTab(pCol) );
- testcase( pCol->op==TK_COLUMN && pCol->y.pTab==0 );
+ assert( pCol->op!=TK_COLUMN || (ExprUseYTab(pCol) && pCol->y.pTab!=0) );
if( ExprIsVtab(pCol) ){
sqlite3_vtab *pVtab;
sqlite3_module *pMod;
@@ -150660,13 +154218,12 @@ static int isAuxiliaryVtabOperator(
int res = 0;
Expr *pLeft = pExpr->pLeft;
Expr *pRight = pExpr->pRight;
- assert( pLeft->op!=TK_COLUMN || ExprUseYTab(pLeft) );
- testcase( pLeft->op==TK_COLUMN && pLeft->y.pTab==0 );
+ assert( pLeft->op!=TK_COLUMN || (ExprUseYTab(pLeft) && pLeft->y.pTab!=0) );
if( ExprIsVtab(pLeft) ){
res++;
}
- assert( pRight==0 || pRight->op!=TK_COLUMN || ExprUseYTab(pRight) );
- testcase( pRight && pRight->op==TK_COLUMN && pRight->y.pTab==0 );
+ assert( pRight==0 || pRight->op!=TK_COLUMN
+ || (ExprUseYTab(pRight) && pRight->y.pTab!=0) );
if( pRight && ExprIsVtab(pRight) ){
res++;
SWAP(Expr*, pLeft, pRight);
@@ -150687,9 +154244,9 @@ static int isAuxiliaryVtabOperator(
** a join, then transfer the appropriate markings over to derived.
*/
static void transferJoinMarkings(Expr *pDerived, Expr *pBase){
- if( pDerived ){
- pDerived->flags |= pBase->flags & EP_FromJoin;
- pDerived->w.iRightJoinTable = pBase->w.iRightJoinTable;
+ if( pDerived && ExprHasProperty(pBase, EP_OuterON|EP_InnerON) ){
+ pDerived->flags |= pBase->flags & (EP_OuterON|EP_InnerON);
+ pDerived->w.iJoin = pBase->w.iJoin;
}
}
@@ -151143,7 +154700,7 @@ static int termIsEquivalence(Parse *pParse, Expr *pExpr){
CollSeq *pColl;
if( !OptimizationEnabled(pParse->db, SQLITE_Transitive) ) return 0;
if( pExpr->op!=TK_EQ && pExpr->op!=TK_IS ) return 0;
- if( ExprHasProperty(pExpr, EP_FromJoin) ) return 0;
+ if( ExprHasProperty(pExpr, EP_OuterON) ) return 0;
aff1 = sqlite3ExprAffinity(pExpr->pLeft);
aff2 = sqlite3ExprAffinity(pExpr->pRight);
if( aff1!=aff2
@@ -151174,7 +154731,9 @@ static Bitmask exprSelectUsage(WhereMaskSet *pMaskSet, Select *pS){
int i;
for(i=0; i<pSrc->nSrc; i++){
mask |= exprSelectUsage(pMaskSet, pSrc->a[i].pSelect);
- mask |= sqlite3WhereExprUsage(pMaskSet, pSrc->a[i].pOn);
+ if( pSrc->a[i].fg.isUsing==0 ){
+ mask |= sqlite3WhereExprUsage(pMaskSet, pSrc->a[i].u3.pOn);
+ }
if( pSrc->a[i].fg.isTabFunc ){
mask |= sqlite3WhereExprListUsage(pMaskSet, pSrc->a[i].u1.pFuncArg);
}
@@ -151213,6 +154772,7 @@ static SQLITE_NOINLINE int exprMightBeIndexed2(
if( pIdx->aColExpr==0 ) continue;
for(i=0; i<pIdx->nKeyCol; i++){
if( pIdx->aiColumn[i]!=XN_EXPR ) continue;
+ assert( pIdx->bHasExpr );
if( sqlite3ExprCompareSkip(pExpr, pIdx->aColExpr->a[i].pExpr, iCur)==0 ){
aiCurCol[0] = iCur;
aiCurCol[1] = XN_EXPR;
@@ -151329,18 +154889,32 @@ static void exprAnalyze(
if( prereqAll!=sqlite3WhereExprUsageNN(pMaskSet, pExpr) ){
printf("\n*** Incorrect prereqAll computed for:\n");
sqlite3TreeViewExpr(0,pExpr,0);
- abort();
+ assert( 0 );
}
#endif
- if( ExprHasProperty(pExpr, EP_FromJoin) ){
- Bitmask x = sqlite3WhereGetMask(pMaskSet, pExpr->w.iRightJoinTable);
- prereqAll |= x;
- extraRight = x-1; /* ON clause terms may not be used with an index
- ** on left table of a LEFT JOIN. Ticket #3015 */
- if( (prereqAll>>1)>=x ){
- sqlite3ErrorMsg(pParse, "ON clause references tables to its right");
- return;
+ if( ExprHasProperty(pExpr, EP_OuterON|EP_InnerON) ){
+ Bitmask x = sqlite3WhereGetMask(pMaskSet, pExpr->w.iJoin);
+ if( ExprHasProperty(pExpr, EP_OuterON) ){
+ prereqAll |= x;
+ extraRight = x-1; /* ON clause terms may not be used with an index
+ ** on left table of a LEFT JOIN. Ticket #3015 */
+ if( (prereqAll>>1)>=x ){
+ sqlite3ErrorMsg(pParse, "ON clause references tables to its right");
+ return;
+ }
+ }else if( (prereqAll>>1)>=x ){
+ /* The ON clause of an INNER JOIN references a table to its right.
+ ** Most other SQL database engines raise an error. But SQLite versions
+ ** 3.0 through 3.38 just put the ON clause constraint into the WHERE
+ ** clause and carried on. Beginning with 3.39, raise an error only
+ ** if there is a RIGHT or FULL JOIN in the query. This makes SQLite
+ ** more like other systems, and also preserves legacy. */
+ if( ALWAYS(pSrc->nSrc>0) && (pSrc->a[0].fg.jointype & JT_LTORJ)!=0 ){
+ sqlite3ErrorMsg(pParse, "ON clause references tables to its right");
+ return;
+ }
+ ExprClearProperty(pExpr, EP_InnerON);
}
}
pTerm->prereqAll = prereqAll;
@@ -151408,7 +154982,7 @@ static void exprAnalyze(
pNew->eOperator = (operatorMask(pDup->op) + eExtraOp) & opMask;
}else
if( op==TK_ISNULL
- && !ExprHasProperty(pExpr,EP_FromJoin)
+ && !ExprHasProperty(pExpr,EP_OuterON)
&& 0==sqlite3ExprCanBeNull(pLeft)
){
assert( !ExprHasProperty(pExpr, EP_IntValue) );
@@ -151479,7 +155053,7 @@ static void exprAnalyze(
else if( pExpr->op==TK_NOTNULL ){
if( pExpr->pLeft->op==TK_COLUMN
&& pExpr->pLeft->iColumn>=0
- && !ExprHasProperty(pExpr, EP_FromJoin)
+ && !ExprHasProperty(pExpr, EP_OuterON)
){
Expr *pNewExpr;
Expr *pLeft = pExpr->pLeft;
@@ -151627,7 +155201,7 @@ static void exprAnalyze(
}
pTerm = &pWC->a[idxTerm];
pTerm->wtFlags |= TERM_CODED|TERM_VIRTUAL; /* Disable the original */
- pTerm->eOperator = 0;
+ pTerm->eOperator = WO_ROWVAL;
}
/* If there is a vector IN term - e.g. "(a, b) IN (SELECT ...)" - create
@@ -151683,9 +155257,9 @@ static void exprAnalyze(
Expr *pNewExpr;
pNewExpr = sqlite3PExpr(pParse, TK_MATCH,
0, sqlite3ExprDup(db, pRight, 0));
- if( ExprHasProperty(pExpr, EP_FromJoin) && pNewExpr ){
- ExprSetProperty(pNewExpr, EP_FromJoin);
- pNewExpr->w.iRightJoinTable = pExpr->w.iRightJoinTable;
+ if( ExprHasProperty(pExpr, EP_OuterON) && pNewExpr ){
+ ExprSetProperty(pNewExpr, EP_OuterON);
+ pNewExpr->w.iJoin = pExpr->w.iJoin;
}
idxNew = whereClauseInsert(pWC, pNewExpr, TERM_VIRTUAL|TERM_DYNAMIC);
testcase( idxNew==0 );
@@ -151812,9 +155386,9 @@ static void whereAddLimitExpr(
** exist only so that they may be passed to the xBestIndex method of the
** single virtual table in the FROM clause of the SELECT.
*/
-SQLITE_PRIVATE void sqlite3WhereAddLimit(WhereClause *pWC, Select *p){
- assert( p==0 || (p->pGroupBy==0 && (p->selFlags & SF_Aggregate)==0) );
- if( (p && p->pLimit) /* 1 */
+SQLITE_PRIVATE void SQLITE_NOINLINE sqlite3WhereAddLimit(WhereClause *pWC, Select *p){
+ assert( p!=0 && p->pLimit!=0 ); /* 1 -- checked by caller */
+ if( p->pGroupBy==0
&& (p->selFlags & (SF_Distinct|SF_Aggregate))==0 /* 2 */
&& (p->pSrc->nSrc==1 && IsVirtual(p->pSrc->a[0].pTab)) /* 3 */
){
@@ -151828,7 +155402,7 @@ SQLITE_PRIVATE void sqlite3WhereAddLimit(WhereClause *pWC, Select *p){
/* This term is a vector operation that has been decomposed into
** other, subsequent terms. It can be ignored. See tag-20220128a */
assert( pWC->a[ii].wtFlags & TERM_VIRTUAL );
- assert( pWC->a[ii].eOperator==0 );
+ assert( pWC->a[ii].eOperator==WO_ROWVAL );
continue;
}
if( pWC->a[ii].leftCursor!=iCsr ) return;
@@ -151840,7 +155414,7 @@ SQLITE_PRIVATE void sqlite3WhereAddLimit(WhereClause *pWC, Select *p){
Expr *pExpr = pOrderBy->a[ii].pExpr;
if( pExpr->op!=TK_COLUMN ) return;
if( pExpr->iTable!=iCsr ) return;
- if( pOrderBy->a[ii].sortFlags & KEYINFO_ORDER_BIGNULL ) return;
+ if( pOrderBy->a[ii].fg.sortFlags & KEYINFO_ORDER_BIGNULL ) return;
}
}
@@ -151907,9 +155481,6 @@ SQLITE_PRIVATE void sqlite3WhereClauseClear(WhereClause *pWC){
a++;
}
}
- if( pWC->a!=pWC->aStatic ){
- sqlite3DbFree(db, pWC->a);
- }
}
@@ -152036,6 +155607,7 @@ SQLITE_PRIVATE void sqlite3WhereTabFuncArgs(
if( pArgs==0 ) return;
for(j=k=0; j<pArgs->nExpr; j++){
Expr *pRhs;
+ u32 joinType;
while( k<pTab->nCol && (pTab->aCol[k].colFlags & COLFLAG_HIDDEN)==0 ){k++;}
if( k>=pTab->nCol ){
sqlite3ErrorMsg(pParse, "too many arguments on %s() - max %d",
@@ -152052,9 +155624,12 @@ SQLITE_PRIVATE void sqlite3WhereTabFuncArgs(
pRhs = sqlite3PExpr(pParse, TK_UPLUS,
sqlite3ExprDup(pParse->db, pArgs->a[j].pExpr, 0), 0);
pTerm = sqlite3PExpr(pParse, TK_EQ, pColRef, pRhs);
- if( pItem->fg.jointype & JT_LEFT ){
- sqlite3SetJoinExpr(pTerm, pItem->iCursor);
+ if( pItem->fg.jointype & (JT_LEFT|JT_LTORJ) ){
+ joinType = EP_OuterON;
+ }else{
+ joinType = EP_InnerON;
}
+ sqlite3SetJoinExpr(pTerm, pItem->iCursor, joinType);
whereClauseInsert(pWC, pTerm, TERM_DYNAMIC);
}
}
@@ -152130,7 +155705,7 @@ SQLITE_PRIVATE int sqlite3WhereIsDistinct(WhereInfo *pWInfo){
** block sorting is required.
*/
SQLITE_PRIVATE int sqlite3WhereIsOrdered(WhereInfo *pWInfo){
- return pWInfo->nOBSat;
+ return pWInfo->nOBSat<0 ? 0 : pWInfo->nOBSat;
}
/*
@@ -152165,7 +155740,7 @@ SQLITE_PRIVATE int sqlite3WhereOrderByLimitOptLabel(WhereInfo *pWInfo){
}
pInner = &pWInfo->a[pWInfo->nLevel-1];
assert( pInner->addrNxt!=0 );
- return pInner->addrNxt;
+ return pInner->pRJ ? pWInfo->iContinue : pInner->addrNxt;
}
/*
@@ -152316,6 +155891,30 @@ SQLITE_PRIVATE Bitmask sqlite3WhereGetMask(WhereMaskSet *pMaskSet, int iCursor){
return 0;
}
+/* Allocate memory that is automatically freed when pWInfo is freed.
+*/
+SQLITE_PRIVATE void *sqlite3WhereMalloc(WhereInfo *pWInfo, u64 nByte){
+ WhereMemBlock *pBlock;
+ pBlock = sqlite3DbMallocRawNN(pWInfo->pParse->db, nByte+sizeof(*pBlock));
+ if( pBlock ){
+ pBlock->pNext = pWInfo->pMemToFree;
+ pBlock->sz = nByte;
+ pWInfo->pMemToFree = pBlock;
+ pBlock++;
+ }
+ return (void*)pBlock;
+}
+SQLITE_PRIVATE void *sqlite3WhereRealloc(WhereInfo *pWInfo, void *pOld, u64 nByte){
+ void *pNew = sqlite3WhereMalloc(pWInfo, nByte);
+ if( pNew && pOld ){
+ WhereMemBlock *pOldBlk = (WhereMemBlock*)pOld;
+ pOldBlk--;
+ assert( pOldBlk->sz<nByte );
+ memcpy(pNew, pOld, pOldBlk->sz);
+ }
+ return pNew;
+}
+
/*
** Create a new mask for cursor iCursor.
**
@@ -152369,7 +155968,7 @@ static WhereTerm *whereScanNext(WhereScan *pScan){
&& (iColumn!=XN_EXPR
|| sqlite3ExprCompareSkip(pTerm->pExpr->pLeft,
pScan->pIdxExpr,iCur)==0)
- && (pScan->iEquiv<=1 || !ExprHasProperty(pTerm->pExpr, EP_FromJoin))
+ && (pScan->iEquiv<=1 || !ExprHasProperty(pTerm->pExpr, EP_OuterON))
){
if( (pTerm->eOperator & WO_EQUIV)!=0
&& pScan->nEquiv<ArraySize(pScan->aiCur)
@@ -152721,6 +156320,7 @@ static void translateColumnToCopy(
pOp->p1 = pOp->p2 + iRegister;
pOp->p2 = pOp->p3;
pOp->p3 = 0;
+ pOp->p5 = 2; /* Cause the MEM_Subtype flag to be cleared */
}else if( pOp->opcode==OP_Rowid ){
pOp->opcode = OP_Sequence;
pOp->p1 = iAutoidxCur;
@@ -152781,6 +156381,43 @@ static void whereTraceIndexInfoOutputs(sqlite3_index_info *p){
#define whereTraceIndexInfoOutputs(A)
#endif
+/*
+** We know that pSrc is an operand of an outer join. Return true if
+** pTerm is a constraint that is compatible with that join.
+**
+** pTerm must be EP_OuterON if pSrc is the right operand of an
+** outer join. pTerm can be either EP_OuterON or EP_InnerON if pSrc
+** is the left operand of a RIGHT join.
+**
+** See https://sqlite.org/forum/forumpost/206d99a16dd9212f
+** for an example of a WHERE clause constraints that may not be used on
+** the right table of a RIGHT JOIN because the constraint implies a
+** not-NULL condition on the left table of the RIGHT JOIN.
+*/
+static int constraintCompatibleWithOuterJoin(
+ const WhereTerm *pTerm, /* WHERE clause term to check */
+ const SrcItem *pSrc /* Table we are trying to access */
+){
+ assert( (pSrc->fg.jointype&(JT_LEFT|JT_LTORJ|JT_RIGHT))!=0 ); /* By caller */
+ testcase( (pSrc->fg.jointype & (JT_LEFT|JT_LTORJ|JT_RIGHT))==JT_LEFT );
+ testcase( (pSrc->fg.jointype & (JT_LEFT|JT_LTORJ|JT_RIGHT))==JT_LTORJ );
+ testcase( ExprHasProperty(pTerm->pExpr, EP_OuterON) )
+ testcase( ExprHasProperty(pTerm->pExpr, EP_InnerON) );
+ if( !ExprHasProperty(pTerm->pExpr, EP_OuterON|EP_InnerON)
+ || pTerm->pExpr->w.iJoin != pSrc->iCursor
+ ){
+ return 0;
+ }
+ if( (pSrc->fg.jointype & (JT_LEFT|JT_RIGHT))!=0
+ && ExprHasProperty(pTerm->pExpr, EP_InnerON)
+ ){
+ return 0;
+ }
+ return 1;
+}
+
+
+
#ifndef SQLITE_OMIT_AUTOMATIC_INDEX
/*
** Return TRUE if the WHERE clause term pTerm is of a form where it
@@ -152795,14 +156432,11 @@ static int termCanDriveIndex(
char aff;
if( pTerm->leftCursor!=pSrc->iCursor ) return 0;
if( (pTerm->eOperator & (WO_EQ|WO_IS))==0 ) return 0;
- if( (pSrc->fg.jointype & JT_LEFT)
- && !ExprHasProperty(pTerm->pExpr, EP_FromJoin)
- && (pTerm->eOperator & WO_IS)
+ assert( (pSrc->fg.jointype & JT_RIGHT)==0 );
+ if( (pSrc->fg.jointype & (JT_LEFT|JT_LTORJ|JT_RIGHT))!=0
+ && !constraintCompatibleWithOuterJoin(pTerm,pSrc)
){
- /* Cannot use an IS term from the WHERE clause as an index driver for
- ** the RHS of a LEFT JOIN. Such a term can only be used if it is from
- ** the ON clause. */
- return 0;
+ return 0; /* See https://sqlite.org/forum/forumpost/51e6959f61 */
}
if( (pTerm->prereqRight & notReady)!=0 ) return 0;
assert( (pTerm->eOperator & (WO_OR|WO_AND))==0 );
@@ -153143,7 +156777,7 @@ static SQLITE_NOINLINE void sqlite3ConstructBloomFilter(
const SrcItem *pTabItem;
pLevel = &pWInfo->a[iLevel];
pTabItem = &pWInfo->pTabList->a[pLevel->iFrom];
- if( pTabItem->fg.jointype & JT_LEFT ) continue;
+ if( pTabItem->fg.jointype & (JT_LEFT|JT_LTORJ) ) continue;
pLoop = pLevel->pWLoop;
if( NEVER(pLoop==0) ) continue;
if( pLoop->prereq & notReady ) continue;
@@ -153214,12 +156848,8 @@ static sqlite3_index_info *allocateIndexInfo(
assert( (pTerm->eOperator & (WO_OR|WO_AND))==0 );
assert( pTerm->u.x.leftColumn>=XN_ROWID );
assert( pTerm->u.x.leftColumn<pTab->nCol );
-
- /* tag-20191211-002: WHERE-clause constraints are not useful to the
- ** right-hand table of a LEFT JOIN. See tag-20191211-001 for the
- ** equivalent restriction for ordinary tables. */
- if( (pSrc->fg.jointype & JT_LEFT)!=0
- && !ExprHasProperty(pTerm->pExpr, EP_FromJoin)
+ if( (pSrc->fg.jointype & (JT_LEFT|JT_LTORJ|JT_RIGHT))!=0
+ && !constraintCompatibleWithOuterJoin(pTerm,pSrc)
){
continue;
}
@@ -153244,7 +156874,7 @@ static sqlite3_index_info *allocateIndexInfo(
}
/* Virtual tables are unable to deal with NULLS FIRST */
- if( pOrderBy->a[i].sortFlags & KEYINFO_ORDER_BIGNULL ) break;
+ if( pOrderBy->a[i].fg.sortFlags & KEYINFO_ORDER_BIGNULL ) break;
/* First case - a direct column references without a COLLATE operator */
if( pExpr->op==TK_COLUMN && pExpr->iTable==pSrc->iCursor ){
@@ -153274,8 +156904,10 @@ static sqlite3_index_info *allocateIndexInfo(
}
if( i==n ){
nOrderBy = n;
- if( (pWInfo->wctrlFlags & (WHERE_GROUPBY|WHERE_DISTINCTBY)) ){
- eDistinct = 1 + ((pWInfo->wctrlFlags & WHERE_DISTINCTBY)!=0);
+ if( (pWInfo->wctrlFlags & WHERE_DISTINCTBY) ){
+ eDistinct = 2 + ((pWInfo->wctrlFlags & WHERE_SORTBYGROUP)!=0);
+ }else if( pWInfo->wctrlFlags & WHERE_GROUPBY ){
+ eDistinct = 1;
}
}
}
@@ -153354,7 +156986,7 @@ static sqlite3_index_info *allocateIndexInfo(
|| (pExpr->op==TK_COLLATE && pExpr->pLeft->op==TK_COLUMN
&& pExpr->iColumn==pExpr->pLeft->iColumn) );
pIdxOrderBy[j].iColumn = pExpr->iColumn;
- pIdxOrderBy[j].desc = pOrderBy->a[i].sortFlags & KEYINFO_ORDER_DESC;
+ pIdxOrderBy[j].desc = pOrderBy->a[i].fg.sortFlags & KEYINFO_ORDER_DESC;
j++;
}
pIdxInfo->nOrderBy = j;
@@ -153458,7 +157090,7 @@ static int whereKeyStats(
#endif
assert( pRec!=0 );
assert( pIdx->nSample>0 );
- assert( pRec->nField>0 && pRec->nField<=pIdx->nSampleCol );
+ assert( pRec->nField>0 );
/* Do a binary search to find the first sample greater than or equal
** to pRec. If pRec contains a single field, the set of samples to search
@@ -153504,7 +157136,7 @@ static int whereKeyStats(
** it is extended to two fields. The duplicates that this creates do not
** cause any problems.
*/
- nField = pRec->nField;
+ nField = MIN(pRec->nField, pIdx->nSample);
iCol = 0;
iSample = pIdx->nSample * nField;
do{
@@ -153592,7 +157224,7 @@ static int whereKeyStats(
** is larger than all samples in the array. */
tRowcnt iUpper, iGap;
if( i>=pIdx->nSample ){
- iUpper = sqlite3LogEstToInt(pIdx->aiRowLogEst[0]);
+ iUpper = pIdx->nRowEst0;
}else{
iUpper = aSample[i].anLt[iCol];
}
@@ -154095,7 +157727,7 @@ SQLITE_PRIVATE void sqlite3WhereTermPrint(WhereTerm *pTerm, int iTerm){
memcpy(zType, "....", 5);
if( pTerm->wtFlags & TERM_VIRTUAL ) zType[0] = 'V';
if( pTerm->eOperator & WO_EQUIV ) zType[1] = 'E';
- if( ExprHasProperty(pTerm->pExpr, EP_FromJoin) ) zType[2] = 'L';
+ if( ExprHasProperty(pTerm->pExpr, EP_OuterON) ) zType[2] = 'L';
if( pTerm->wtFlags & TERM_CODED ) zType[3] = 'C';
if( pTerm->eOperator & WO_SINGLE ){
assert( (pTerm->eOperator & (WO_OR|WO_AND))==0 );
@@ -154221,12 +157853,18 @@ static void whereLoopClearUnion(sqlite3 *db, WhereLoop *p){
}
/*
-** Deallocate internal memory used by a WhereLoop object
+** Deallocate internal memory used by a WhereLoop object. Leave the
+** object in an initialized state, as if it had been newly allocated.
*/
static void whereLoopClear(sqlite3 *db, WhereLoop *p){
- if( p->aLTerm!=p->aLTermSpace ) sqlite3DbFreeNN(db, p->aLTerm);
+ if( p->aLTerm!=p->aLTermSpace ){
+ sqlite3DbFreeNN(db, p->aLTerm);
+ p->aLTerm = p->aLTermSpace;
+ p->nLSlot = ArraySize(p->aLTermSpace);
+ }
whereLoopClearUnion(db, p);
- whereLoopInit(p);
+ p->nLTerm = 0;
+ p->wsFlags = 0;
}
/*
@@ -154250,7 +157888,9 @@ static int whereLoopResize(sqlite3 *db, WhereLoop *p, int n){
*/
static int whereLoopXfer(sqlite3 *db, WhereLoop *pTo, WhereLoop *pFrom){
whereLoopClearUnion(db, pTo);
- if( whereLoopResize(db, pTo, pFrom->nLTerm) ){
+ if( pFrom->nLTerm > pTo->nLSlot
+ && whereLoopResize(db, pTo, pFrom->nLTerm)
+ ){
memset(pTo, 0, WHERE_LOOP_XFER_SZ);
return SQLITE_NOMEM_BKPT;
}
@@ -154268,42 +157908,29 @@ static int whereLoopXfer(sqlite3 *db, WhereLoop *pTo, WhereLoop *pFrom){
** Delete a WhereLoop object
*/
static void whereLoopDelete(sqlite3 *db, WhereLoop *p){
+ assert( db!=0 );
whereLoopClear(db, p);
- sqlite3DbFreeNN(db, p);
+ sqlite3DbNNFreeNN(db, p);
}
/*
** Free a WhereInfo structure
*/
static void whereInfoFree(sqlite3 *db, WhereInfo *pWInfo){
- int i;
assert( pWInfo!=0 );
- for(i=0; i<pWInfo->nLevel; i++){
- WhereLevel *pLevel = &pWInfo->a[i];
- if( pLevel->pWLoop && (pLevel->pWLoop->wsFlags & WHERE_IN_ABLE)!=0 ){
- assert( (pLevel->pWLoop->wsFlags & WHERE_MULTI_OR)==0 );
- sqlite3DbFree(db, pLevel->u.in.aInLoop);
- }
- }
+ assert( db!=0 );
sqlite3WhereClauseClear(&pWInfo->sWC);
while( pWInfo->pLoops ){
WhereLoop *p = pWInfo->pLoops;
pWInfo->pLoops = p->pNextLoop;
whereLoopDelete(db, p);
}
- assert( pWInfo->pExprMods==0 );
- sqlite3DbFreeNN(db, pWInfo);
-}
-
-/* Undo all Expr node modifications
-*/
-static void whereUndoExprMods(WhereInfo *pWInfo){
- while( pWInfo->pExprMods ){
- WhereExprMod *p = pWInfo->pExprMods;
- pWInfo->pExprMods = p->pNext;
- memcpy(p->pExpr, &p->orig, sizeof(p->orig));
- sqlite3DbFree(pWInfo->pParse->db, p);
+ while( pWInfo->pMemToFree ){
+ WhereMemBlock *pNext = pWInfo->pMemToFree->pNext;
+ sqlite3DbNNFreeNN(db, pWInfo->pMemToFree);
+ pWInfo->pMemToFree = pNext;
}
+ sqlite3DbNNFreeNN(db, pWInfo);
}
/*
@@ -154660,10 +158287,11 @@ static void whereLoopOutputAdjust(
**
** 2022-03-24: Self-culling only applies if either the extra terms
** are straight comparison operators that are non-true with NULL
- ** operand, or if the loop is not a LEFT JOIN.
+ ** operand, or if the loop is not an OUTER JOIN.
*/
if( (pTerm->eOperator & 0x3f)!=0
- || (pWC->pWInfo->pTabList->a[pLoop->iTab].fg.jointype & JT_LEFT)==0
+ || (pWC->pWInfo->pTabList->a[pLoop->iTab].fg.jointype
+ & (JT_LEFT|JT_LTORJ))==0
){
pLoop->wsFlags |= WHERE_SELFCULL;
}
@@ -154869,15 +158497,11 @@ static int whereLoopAddBtreeIndex(
** to mix with a lower range bound from some other source */
if( pTerm->wtFlags & TERM_LIKEOPT && pTerm->eOperator==WO_LT ) continue;
- /* tag-20191211-001: Do not allow constraints from the WHERE clause to
- ** be used by the right table of a LEFT JOIN. Only constraints in the
- ** ON clause are allowed. See tag-20191211-002 for the vtab equivalent. */
- if( (pSrc->fg.jointype & JT_LEFT)!=0
- && !ExprHasProperty(pTerm->pExpr, EP_FromJoin)
+ if( (pSrc->fg.jointype & (JT_LEFT|JT_LTORJ|JT_RIGHT))!=0
+ && !constraintCompatibleWithOuterJoin(pTerm,pSrc)
){
continue;
}
-
if( IsUniqueIndex(pProbe) && saved_nEq==pProbe->nKeyCol-1 ){
pBuilder->bldFlags1 |= SQLITE_BLDF1_UNIQUE;
}else{
@@ -154888,7 +158512,11 @@ static int whereLoopAddBtreeIndex(
pNew->u.btree.nBtm = saved_nBtm;
pNew->u.btree.nTop = saved_nTop;
pNew->nLTerm = saved_nLTerm;
- if( whereLoopResize(db, pNew, pNew->nLTerm+1) ) break; /* OOM */
+ if( pNew->nLTerm>=pNew->nLSlot
+ && whereLoopResize(db, pNew, pNew->nLTerm+1)
+ ){
+ break; /* OOM while trying to enlarge the pNew->aLTerm array */
+ }
pNew->aLTerm[pNew->nLTerm++] = pTerm;
pNew->prereq = (saved_prereq | pTerm->prereqRight) & ~pNew->maskSelf;
@@ -154981,38 +158609,39 @@ static int whereLoopAddBtreeIndex(
if( scan.iEquiv>1 ) pNew->wsFlags |= WHERE_TRANSCONS;
}else if( eOp & WO_ISNULL ){
pNew->wsFlags |= WHERE_COLUMN_NULL;
- }else if( eOp & (WO_GT|WO_GE) ){
- testcase( eOp & WO_GT );
- testcase( eOp & WO_GE );
- pNew->wsFlags |= WHERE_COLUMN_RANGE|WHERE_BTM_LIMIT;
- pNew->u.btree.nBtm = whereRangeVectorLen(
- pParse, pSrc->iCursor, pProbe, saved_nEq, pTerm
- );
- pBtm = pTerm;
- pTop = 0;
- if( pTerm->wtFlags & TERM_LIKEOPT ){
- /* Range constraints that come from the LIKE optimization are
- ** always used in pairs. */
- pTop = &pTerm[1];
- assert( (pTop-(pTerm->pWC->a))<pTerm->pWC->nTerm );
- assert( pTop->wtFlags & TERM_LIKEOPT );
- assert( pTop->eOperator==WO_LT );
- if( whereLoopResize(db, pNew, pNew->nLTerm+1) ) break; /* OOM */
- pNew->aLTerm[pNew->nLTerm++] = pTop;
- pNew->wsFlags |= WHERE_TOP_LIMIT;
- pNew->u.btree.nTop = 1;
- }
- }else{
- assert( eOp & (WO_LT|WO_LE) );
- testcase( eOp & WO_LT );
- testcase( eOp & WO_LE );
- pNew->wsFlags |= WHERE_COLUMN_RANGE|WHERE_TOP_LIMIT;
- pNew->u.btree.nTop = whereRangeVectorLen(
+ }else{
+ int nVecLen = whereRangeVectorLen(
pParse, pSrc->iCursor, pProbe, saved_nEq, pTerm
);
- pTop = pTerm;
- pBtm = (pNew->wsFlags & WHERE_BTM_LIMIT)!=0 ?
- pNew->aLTerm[pNew->nLTerm-2] : 0;
+ if( eOp & (WO_GT|WO_GE) ){
+ testcase( eOp & WO_GT );
+ testcase( eOp & WO_GE );
+ pNew->wsFlags |= WHERE_COLUMN_RANGE|WHERE_BTM_LIMIT;
+ pNew->u.btree.nBtm = nVecLen;
+ pBtm = pTerm;
+ pTop = 0;
+ if( pTerm->wtFlags & TERM_LIKEOPT ){
+ /* Range constraints that come from the LIKE optimization are
+ ** always used in pairs. */
+ pTop = &pTerm[1];
+ assert( (pTop-(pTerm->pWC->a))<pTerm->pWC->nTerm );
+ assert( pTop->wtFlags & TERM_LIKEOPT );
+ assert( pTop->eOperator==WO_LT );
+ if( whereLoopResize(db, pNew, pNew->nLTerm+1) ) break; /* OOM */
+ pNew->aLTerm[pNew->nLTerm++] = pTop;
+ pNew->wsFlags |= WHERE_TOP_LIMIT;
+ pNew->u.btree.nTop = 1;
+ }
+ }else{
+ assert( eOp & (WO_LT|WO_LE) );
+ testcase( eOp & WO_LT );
+ testcase( eOp & WO_LE );
+ pNew->wsFlags |= WHERE_COLUMN_RANGE|WHERE_TOP_LIMIT;
+ pNew->u.btree.nTop = nVecLen;
+ pTop = pTerm;
+ pBtm = (pNew->wsFlags & WHERE_BTM_LIMIT)!=0 ?
+ pNew->aLTerm[pNew->nLTerm-2] : 0;
+ }
}
/* At this point pNew->nOut is set to the number of rows expected to
@@ -155227,23 +158856,26 @@ static int indexMightHelpWithOrderBy(
*/
static int whereUsablePartialIndex(
int iTab, /* The table for which we want an index */
- int isLeft, /* True if iTab is the right table of a LEFT JOIN */
+ u8 jointype, /* The JT_* flags on the join */
WhereClause *pWC, /* The WHERE clause of the query */
Expr *pWhere /* The WHERE clause from the partial index */
){
int i;
WhereTerm *pTerm;
- Parse *pParse = pWC->pWInfo->pParse;
+ Parse *pParse;
+
+ if( jointype & JT_LTORJ ) return 0;
+ pParse = pWC->pWInfo->pParse;
while( pWhere->op==TK_AND ){
- if( !whereUsablePartialIndex(iTab,isLeft,pWC,pWhere->pLeft) ) return 0;
+ if( !whereUsablePartialIndex(iTab,jointype,pWC,pWhere->pLeft) ) return 0;
pWhere = pWhere->pRight;
}
if( pParse->db->flags & SQLITE_EnableQPSG ) pParse = 0;
for(i=0, pTerm=pWC->a; i<pWC->nTerm; i++, pTerm++){
Expr *pExpr;
pExpr = pTerm->pExpr;
- if( (!ExprHasProperty(pExpr, EP_FromJoin) || pExpr->w.iRightJoinTable==iTab)
- && (isLeft==0 || ExprHasProperty(pExpr, EP_FromJoin))
+ if( (!ExprHasProperty(pExpr, EP_OuterON) || pExpr->w.iJoin==iTab)
+ && ((jointype & JT_OUTER)==0 || ExprHasProperty(pExpr, EP_OuterON))
&& sqlite3ExprImpliesExpr(pParse, pExpr, pWhere, iTab)
&& (pTerm->wtFlags & TERM_VNULL)==0
){
@@ -155254,6 +158886,94 @@ static int whereUsablePartialIndex(
}
/*
+** Structure passed to the whereIsCoveringIndex Walker callback.
+*/
+struct CoveringIndexCheck {
+ Index *pIdx; /* The index */
+ int iTabCur; /* Cursor number for the corresponding table */
+};
+
+/*
+** Information passed in is pWalk->u.pCovIdxCk. Call is pCk.
+**
+** If the Expr node references the table with cursor pCk->iTabCur, then
+** make sure that column is covered by the index pCk->pIdx. We know that
+** all columns less than 63 (really BMS-1) are covered, so we don't need
+** to check them. But we do need to check any column at 63 or greater.
+**
+** If the index does not cover the column, then set pWalk->eCode to
+** non-zero and return WRC_Abort to stop the search.
+**
+** If this node does not disprove that the index can be a covering index,
+** then just return WRC_Continue, to continue the search.
+*/
+static int whereIsCoveringIndexWalkCallback(Walker *pWalk, Expr *pExpr){
+ int i; /* Loop counter */
+ const Index *pIdx; /* The index of interest */
+ const i16 *aiColumn; /* Columns contained in the index */
+ u16 nColumn; /* Number of columns in the index */
+ if( pExpr->op!=TK_COLUMN && pExpr->op!=TK_AGG_COLUMN ) return WRC_Continue;
+ if( pExpr->iColumn<(BMS-1) ) return WRC_Continue;
+ if( pExpr->iTable!=pWalk->u.pCovIdxCk->iTabCur ) return WRC_Continue;
+ pIdx = pWalk->u.pCovIdxCk->pIdx;
+ aiColumn = pIdx->aiColumn;
+ nColumn = pIdx->nColumn;
+ for(i=0; i<nColumn; i++){
+ if( aiColumn[i]==pExpr->iColumn ) return WRC_Continue;
+ }
+ pWalk->eCode = 1;
+ return WRC_Abort;
+}
+
+
+/*
+** pIdx is an index that covers all of the low-number columns used by
+** pWInfo->pSelect (columns from 0 through 62). But there are columns
+** in pWInfo->pSelect beyond 62. This routine tries to answer the question
+** of whether pIdx covers *all* columns in the query.
+**
+** Return 0 if pIdx is a covering index. Return non-zero if pIdx is
+** not a covering index or if we are unable to determine if pIdx is a
+** covering index.
+**
+** This routine is an optimization. It is always safe to return non-zero.
+** But returning zero when non-zero should have been returned can lead to
+** incorrect bytecode and assertion faults.
+*/
+static SQLITE_NOINLINE u32 whereIsCoveringIndex(
+ WhereInfo *pWInfo, /* The WHERE clause context */
+ Index *pIdx, /* Index that is being tested */
+ int iTabCur /* Cursor for the table being indexed */
+){
+ int i;
+ struct CoveringIndexCheck ck;
+ Walker w;
+ if( pWInfo->pSelect==0 ){
+ /* We don't have access to the full query, so we cannot check to see
+ ** if pIdx is covering. Assume it is not. */
+ return 1;
+ }
+ for(i=0; i<pIdx->nColumn; i++){
+ if( pIdx->aiColumn[i]>=BMS-1 ) break;
+ }
+ if( i>=pIdx->nColumn ){
+ /* pIdx does not index any columns greater than 62, but we know from
+ ** colMask that columns greater than 62 are used, so this is not a
+ ** covering index */
+ return 1;
+ }
+ ck.pIdx = pIdx;
+ ck.iTabCur = iTabCur;
+ memset(&w, 0, sizeof(w));
+ w.xExprCallback = whereIsCoveringIndexWalkCallback;
+ w.xSelectCallback = sqlite3SelectWalkNoop;
+ w.u.pCovIdxCk = &ck;
+ w.eCode = 0;
+ sqlite3WalkSelect(&w, pWInfo->pSelect);
+ return w.eCode;
+}
+
+/*
** Add all WhereLoop objects for a single table of the join where the table
** is identified by pBuilder->pNew->iTab. That table is guaranteed to be
** a b-tree table, not a virtual table.
@@ -155352,13 +159072,14 @@ static int whereLoopAddBtree(
#ifndef SQLITE_OMIT_AUTOMATIC_INDEX
/* Automatic indexes */
if( !pBuilder->pOrSet /* Not part of an OR optimization */
- && (pWInfo->wctrlFlags & WHERE_OR_SUBCLAUSE)==0
+ && (pWInfo->wctrlFlags & (WHERE_RIGHT_JOIN|WHERE_OR_SUBCLAUSE))==0
&& (pWInfo->pParse->db->flags & SQLITE_AutoIndex)!=0
&& !pSrc->fg.isIndexedBy /* Has no INDEXED BY clause */
&& !pSrc->fg.notIndexed /* Has no NOT INDEXED clause */
&& HasRowid(pTab) /* Not WITHOUT ROWID table. (FIXME: Why not?) */
&& !pSrc->fg.isCorrelated /* Not a correlated subquery */
&& !pSrc->fg.isRecursive /* Not a recursive common table expression. */
+ && (pSrc->fg.jointype & JT_RIGHT)==0 /* Not the right tab of a RIGHT JOIN */
){
/* Generate auto-index WhereLoops */
LogEst rLogSize; /* Logarithm of the number of rows in the table */
@@ -155408,9 +159129,8 @@ static int whereLoopAddBtree(
for(; rc==SQLITE_OK && pProbe;
pProbe=(pSrc->fg.isIndexedBy ? 0 : pProbe->pNext), iSortIdx++
){
- int isLeft = (pSrc->fg.jointype & JT_OUTER)!=0;
if( pProbe->pPartIdxWhere!=0
- && !whereUsablePartialIndex(pSrc->iCursor, isLeft, pWC,
+ && !whereUsablePartialIndex(pSrc->iCursor, pSrc->fg.jointype, pWC,
pProbe->pPartIdxWhere)
){
testcase( pNew->iTab!=pSrc->iCursor ); /* See ticket [98d973b8f5] */
@@ -155455,6 +159175,9 @@ static int whereLoopAddBtree(
#else
pNew->rRun = rSize + 16;
#endif
+ if( IsView(pTab) || (pTab->tabFlags & TF_Ephemeral)!=0 ){
+ pNew->wsFlags |= WHERE_VIEWSCAN;
+ }
ApplyCostMultiplier(pNew->rRun, pTab->costMult);
whereLoopOutputAdjust(pWC, pNew, rSize);
rc = whereLoopInsert(pBuilder, pNew);
@@ -155467,6 +159190,9 @@ static int whereLoopAddBtree(
m = 0;
}else{
m = pSrc->colUsed & pProbe->colNotIdxed;
+ if( m==TOPBIT ){
+ m = whereIsCoveringIndex(pWInfo, pProbe, pSrc->iCursor);
+ }
pNew->wsFlags = (m==0) ? (WHERE_IDX_ONLY|WHERE_INDEXED) : WHERE_INDEXED;
}
@@ -155518,7 +159244,14 @@ static int whereLoopAddBtree(
}
ApplyCostMultiplier(pNew->rRun, pTab->costMult);
whereLoopOutputAdjust(pWC, pNew, rSize);
- rc = whereLoopInsert(pBuilder, pNew);
+ if( (pSrc->fg.jointype & JT_RIGHT)!=0 && pProbe->aColExpr ){
+ /* Do not do an SCAN of a index-on-expression in a RIGHT JOIN
+ ** because the cursor used to access the index might not be
+ ** positioned to the correct row during the right-join no-match
+ ** loop. */
+ }else{
+ rc = whereLoopInsert(pBuilder, pNew);
+ }
pNew->nOut = rSize;
if( rc ) break;
}
@@ -155693,6 +159426,7 @@ static int whereLoopAddVirtualOne(
*pbIn = 1; assert( (mExclude & WO_IN)==0 );
}
+ assert( pbRetryLimit || !isLimitTerm(pTerm) );
if( isLimitTerm(pTerm) && *pbIn ){
/* If there is an IN(...) term handled as an == (separate call to
** xFilter for each value on the RHS of the IN) and a LIMIT or
@@ -155840,9 +159574,7 @@ SQLITE_API int sqlite3_vtab_rhs_value(
*/
SQLITE_API int sqlite3_vtab_distinct(sqlite3_index_info *pIdxInfo){
HiddenIndexInfo *pHidden = (HiddenIndexInfo*)&pIdxInfo[1];
- assert( pHidden->eDistinct==0
- || pHidden->eDistinct==1
- || pHidden->eDistinct==2 );
+ assert( pHidden->eDistinct>=0 && pHidden->eDistinct<=3 );
return pHidden->eDistinct;
}
@@ -155850,15 +159582,26 @@ SQLITE_API int sqlite3_vtab_distinct(sqlite3_index_info *pIdxInfo){
&& !defined(SQLITE_OMIT_VIRTUALTABLE)
/*
** Cause the prepared statement that is associated with a call to
-** xBestIndex to open write transactions on all attached schemas.
+** xBestIndex to potentiall use all schemas. If the statement being
+** prepared is read-only, then just start read transactions on all
+** schemas. But if this is a write operation, start writes on all
+** schemas.
+**
** This is used by the (built-in) sqlite_dbpage virtual table.
*/
-SQLITE_PRIVATE void sqlite3VtabWriteAll(sqlite3_index_info *pIdxInfo){
+SQLITE_PRIVATE void sqlite3VtabUsesAllSchemas(sqlite3_index_info *pIdxInfo){
HiddenIndexInfo *pHidden = (HiddenIndexInfo*)&pIdxInfo[1];
Parse *pParse = pHidden->pParse;
int nDb = pParse->db->nDb;
int i;
- for(i=0; i<nDb; i++) sqlite3BeginWriteOperation(pParse, 0, i);
+ for(i=0; i<nDb; i++){
+ sqlite3CodeVerifySchema(pParse, i);
+ }
+ if( pParse->writeMask ){
+ for(i=0; i<nDb; i++){
+ sqlite3BeginWriteOperation(pParse, 0, i);
+ }
+ }
}
#endif
@@ -156041,6 +159784,9 @@ static int whereLoopAddOr(
pItem = pWInfo->pTabList->a + pNew->iTab;
iCur = pItem->iCursor;
+ /* The multi-index OR optimization does not work for RIGHT and FULL JOIN */
+ if( pItem->fg.jointype & JT_RIGHT ) return SQLITE_OK;
+
for(pTerm=pWC->a; pTerm<pWCEnd && rc==SQLITE_OK; pTerm++){
if( (pTerm->eOperator & WO_OR)!=0
&& (pTerm->u.pOrInfo->indexable & pNew->maskSelf)!=0
@@ -156154,29 +159900,50 @@ static int whereLoopAddAll(WhereLoopBuilder *pBuilder){
SrcItem *pEnd = &pTabList->a[pWInfo->nLevel];
sqlite3 *db = pWInfo->pParse->db;
int rc = SQLITE_OK;
+ int bFirstPastRJ = 0;
+ int hasRightJoin = 0;
WhereLoop *pNew;
+
/* Loop over the tables in the join, from left to right */
pNew = pBuilder->pNew;
- whereLoopInit(pNew);
+
+ /* Verify that pNew has already been initialized */
+ assert( pNew->nLTerm==0 );
+ assert( pNew->wsFlags==0 );
+ assert( pNew->nLSlot>=ArraySize(pNew->aLTermSpace) );
+ assert( pNew->aLTerm!=0 );
+
pBuilder->iPlanLimit = SQLITE_QUERY_PLANNER_LIMIT;
for(iTab=0, pItem=pTabList->a; pItem<pEnd; iTab++, pItem++){
Bitmask mUnusable = 0;
pNew->iTab = iTab;
pBuilder->iPlanLimit += SQLITE_QUERY_PLANNER_LIMIT_INCR;
pNew->maskSelf = sqlite3WhereGetMask(&pWInfo->sMaskSet, pItem->iCursor);
- if( (pItem->fg.jointype & (JT_LEFT|JT_CROSS))!=0 ){
- /* This condition is true when pItem is the FROM clause term on the
- ** right-hand-side of a LEFT or CROSS JOIN. */
- mPrereq = mPrior;
- }else{
+ if( bFirstPastRJ
+ || (pItem->fg.jointype & (JT_OUTER|JT_CROSS|JT_LTORJ))!=0
+ ){
+ /* Add prerequisites to prevent reordering of FROM clause terms
+ ** across CROSS joins and outer joins. The bFirstPastRJ boolean
+ ** prevents the right operand of a RIGHT JOIN from being swapped with
+ ** other elements even further to the right.
+ **
+ ** The JT_LTORJ case and the hasRightJoin flag work together to
+ ** prevent FROM-clause terms from moving from the right side of
+ ** a LEFT JOIN over to the left side of that join if the LEFT JOIN
+ ** is itself on the left side of a RIGHT JOIN.
+ */
+ if( pItem->fg.jointype & JT_LTORJ ) hasRightJoin = 1;
+ mPrereq |= mPrior;
+ bFirstPastRJ = (pItem->fg.jointype & JT_RIGHT)!=0;
+ }else if( !hasRightJoin ){
mPrereq = 0;
}
#ifndef SQLITE_OMIT_VIRTUALTABLE
if( IsVirtual(pItem->pTab) ){
SrcItem *p;
for(p=&pItem[1]; p<pEnd; p++){
- if( mUnusable || (p->fg.jointype & (JT_LEFT|JT_CROSS)) ){
+ if( mUnusable || (p->fg.jointype & (JT_OUTER|JT_CROSS)) ){
mUnusable |= sqlite3WhereGetMask(&pWInfo->sMaskSet, p->iCursor);
}
}
@@ -156301,7 +160068,9 @@ static i8 wherePathSatisfiesOrderBy(
pLoop = pLast;
}
if( pLoop->wsFlags & WHERE_VIRTUALTABLE ){
- if( pLoop->u.vtab.isOrdered && (wctrlFlags & WHERE_DISTINCTBY)==0 ){
+ if( pLoop->u.vtab.isOrdered
+ && ((wctrlFlags&(WHERE_DISTINCTBY|WHERE_SORTBYGROUP))!=WHERE_DISTINCTBY)
+ ){
obSat = obDone;
}
break;
@@ -156479,16 +160248,18 @@ static i8 wherePathSatisfiesOrderBy(
/* Make sure the sort order is compatible in an ORDER BY clause.
** Sort order is irrelevant for a GROUP BY clause. */
if( revSet ){
- if( (rev ^ revIdx)!=(pOrderBy->a[i].sortFlags&KEYINFO_ORDER_DESC) ){
+ if( (rev ^ revIdx)
+ != (pOrderBy->a[i].fg.sortFlags&KEYINFO_ORDER_DESC)
+ ){
isMatch = 0;
}
}else{
- rev = revIdx ^ (pOrderBy->a[i].sortFlags & KEYINFO_ORDER_DESC);
+ rev = revIdx ^ (pOrderBy->a[i].fg.sortFlags & KEYINFO_ORDER_DESC);
if( rev ) *pRevMask |= MASKBIT(iLoop);
revSet = 1;
}
}
- if( isMatch && (pOrderBy->a[i].sortFlags & KEYINFO_ORDER_BIGNULL) ){
+ if( isMatch && (pOrderBy->a[i].fg.sortFlags & KEYINFO_ORDER_BIGNULL) ){
if( j==pLoop->u.btree.nEq ){
pLoop->wsFlags |= WHERE_BIGNULL_SORT;
}else{
@@ -156568,7 +160339,7 @@ static i8 wherePathSatisfiesOrderBy(
** SELECT * FROM t1 GROUP BY y,x ORDER BY y,x; -- IsSorted()==0
*/
SQLITE_PRIVATE int sqlite3WhereIsSorted(WhereInfo *pWInfo){
- assert( pWInfo->wctrlFlags & WHERE_GROUPBY );
+ assert( pWInfo->wctrlFlags & (WHERE_GROUPBY|WHERE_DISTINCTBY) );
assert( pWInfo->wctrlFlags & WHERE_SORTBYGROUP );
return pWInfo->sorted;
}
@@ -156647,7 +160418,6 @@ static int wherePathSolver(WhereInfo *pWInfo, LogEst nRowEst){
int mxChoice; /* Maximum number of simultaneous paths tracked */
int nLoop; /* Number of terms in the join */
Parse *pParse; /* Parsing context */
- sqlite3 *db; /* The database connection */
int iLoop; /* Loop counter over the terms of the join */
int ii, jj; /* Loop counters */
int mxI = 0; /* Index of next entry to replace */
@@ -156666,7 +160436,6 @@ static int wherePathSolver(WhereInfo *pWInfo, LogEst nRowEst){
int nSpace; /* Bytes of space allocated at pSpace */
pParse = pWInfo->pParse;
- db = pParse->db;
nLoop = pWInfo->nLevel;
/* TUNING: For simple queries, only the best path is tracked.
** For 2-way joins, the 5 best paths are followed.
@@ -156689,7 +160458,7 @@ static int wherePathSolver(WhereInfo *pWInfo, LogEst nRowEst){
/* Allocate and initialize space for aTo, aFrom and aSortCost[] */
nSpace = (sizeof(WherePath)+sizeof(WhereLoop*)*nLoop)*mxChoice*2;
nSpace += sizeof(LogEst) * nOrderBy;
- pSpace = sqlite3DbMallocRawNN(db, nSpace);
+ pSpace = sqlite3StackAllocRawNN(pParse->db, nSpace);
if( pSpace==0 ) return SQLITE_NOMEM_BKPT;
aTo = (WherePath*)pSpace;
aFrom = aTo+mxChoice;
@@ -156739,9 +160508,9 @@ static int wherePathSolver(WhereInfo *pWInfo, LogEst nRowEst){
LogEst nOut; /* Rows visited by (pFrom+pWLoop) */
LogEst rCost; /* Cost of path (pFrom+pWLoop) */
LogEst rUnsorted; /* Unsorted cost of (pFrom+pWLoop) */
- i8 isOrdered = pFrom->isOrdered; /* isOrdered for (pFrom+pWLoop) */
+ i8 isOrdered; /* isOrdered for (pFrom+pWLoop) */
Bitmask maskNew; /* Mask of src visited by (..) */
- Bitmask revMask = 0; /* Mask of rev-order loops for (..) */
+ Bitmask revMask; /* Mask of rev-order loops for (..) */
if( (pWLoop->prereq & ~pFrom->maskLoop)!=0 ) continue;
if( (pWLoop->maskSelf & pFrom->maskLoop)!=0 ) continue;
@@ -156760,7 +160529,9 @@ static int wherePathSolver(WhereInfo *pWInfo, LogEst nRowEst){
rUnsorted = sqlite3LogEstAdd(rUnsorted, pFrom->rUnsorted);
nOut = pFrom->nRow + pWLoop->nOut;
maskNew = pFrom->maskLoop | pWLoop->maskSelf;
+ isOrdered = pFrom->isOrdered;
if( isOrdered<0 ){
+ revMask = 0;
isOrdered = wherePathSatisfiesOrderBy(pWInfo,
pWInfo->pOrderBy, pFrom, pWInfo->wctrlFlags,
iLoop, pWLoop, &revMask);
@@ -156788,6 +160559,13 @@ static int wherePathSolver(WhereInfo *pWInfo, LogEst nRowEst){
rUnsorted -= 2; /* TUNING: Slight bias in favor of no-sort plans */
}
+ /* TUNING: A full-scan of a VIEW or subquery in the outer loop
+ ** is not so bad. */
+ if( iLoop==0 && (pWLoop->wsFlags & WHERE_VIEWSCAN)!=0 ){
+ rCost += -10;
+ nOut += -30;
+ }
+
/* Check to see if pWLoop should be added to the set of
** mxChoice best-so-far paths.
**
@@ -156938,7 +160716,7 @@ static int wherePathSolver(WhereInfo *pWInfo, LogEst nRowEst){
if( nFrom==0 ){
sqlite3ErrorMsg(pParse, "no query solution");
- sqlite3DbFreeNN(db, pSpace);
+ sqlite3StackFreeNN(pParse->db, pSpace);
return SQLITE_ERROR;
}
@@ -156969,12 +160747,12 @@ static int wherePathSolver(WhereInfo *pWInfo, LogEst nRowEst){
}
pWInfo->bOrderedInnerLoop = 0;
if( pWInfo->pOrderBy ){
+ pWInfo->nOBSat = pFrom->isOrdered;
if( pWInfo->wctrlFlags & WHERE_DISTINCTBY ){
if( pFrom->isOrdered==pWInfo->pOrderBy->nExpr ){
pWInfo->eDistinct = WHERE_DISTINCT_ORDERED;
}
}else{
- pWInfo->nOBSat = pFrom->isOrdered;
pWInfo->revMask = pFrom->revLoop;
if( pWInfo->nOBSat<=0 ){
pWInfo->nOBSat = 0;
@@ -157020,7 +160798,7 @@ static int wherePathSolver(WhereInfo *pWInfo, LogEst nRowEst){
pWInfo->nRowOut = pFrom->nRow;
/* Free temporary memory and return success */
- sqlite3DbFreeNN(db, pSpace);
+ sqlite3StackFreeNN(pParse->db, pSpace);
return SQLITE_OK;
}
@@ -157053,7 +160831,11 @@ static int whereShortCut(WhereLoopBuilder *pBuilder){
pItem = pWInfo->pTabList->a;
pTab = pItem->pTab;
if( IsVirtual(pTab) ) return 0;
- if( pItem->fg.isIndexedBy ) return 0;
+ if( pItem->fg.isIndexedBy || pItem->fg.notIndexed ){
+ testcase( pItem->fg.isIndexedBy );
+ testcase( pItem->fg.notIndexed );
+ return 0;
+ }
iCur = pItem->iCursor;
pWC = &pWInfo->sWC;
pLoop = pBuilder->pNew;
@@ -157226,7 +161008,7 @@ static SQLITE_NOINLINE Bitmask whereOmitNoopJoin(
WhereLoop *pLoop;
pLoop = pWInfo->a[i].pWLoop;
pItem = &pWInfo->pTabList->a[pLoop->iTab];
- if( (pItem->fg.jointype & JT_LEFT)==0 ) continue;
+ if( (pItem->fg.jointype & (JT_LEFT|JT_RIGHT))!=JT_LEFT ) continue;
if( (pWInfo->wctrlFlags & WHERE_WANT_DISTINCT)==0
&& (pLoop->wsFlags & WHERE_ONEROW)==0
){
@@ -157236,8 +161018,8 @@ static SQLITE_NOINLINE Bitmask whereOmitNoopJoin(
pEnd = pWInfo->sWC.a + pWInfo->sWC.nTerm;
for(pTerm=pWInfo->sWC.a; pTerm<pEnd; pTerm++){
if( (pTerm->prereqAll & pLoop->maskSelf)!=0 ){
- if( !ExprHasProperty(pTerm->pExpr, EP_FromJoin)
- || pTerm->pExpr->w.iRightJoinTable!=pItem->iCursor
+ if( !ExprHasProperty(pTerm->pExpr, EP_OuterON)
+ || pTerm->pExpr->w.iJoin!=pItem->iCursor
){
break;
}
@@ -157316,6 +161098,77 @@ static SQLITE_NOINLINE void whereCheckIfBloomFilterIsUseful(
}
/*
+** This is an sqlite3ParserAddCleanup() callback that is invoked to
+** free the Parse->pIdxExpr list when the Parse object is destroyed.
+*/
+static void whereIndexedExprCleanup(sqlite3 *db, void *pObject){
+ Parse *pParse = (Parse*)pObject;
+ while( pParse->pIdxExpr!=0 ){
+ IndexedExpr *p = pParse->pIdxExpr;
+ pParse->pIdxExpr = p->pIENext;
+ sqlite3ExprDelete(db, p->pExpr);
+ sqlite3DbFreeNN(db, p);
+ }
+}
+
+/*
+** The index pIdx is used by a query and contains one or more expressions.
+** In other words pIdx is an index on an expression. iIdxCur is the cursor
+** number for the index and iDataCur is the cursor number for the corresponding
+** table.
+**
+** This routine adds IndexedExpr entries to the Parse->pIdxExpr field for
+** each of the expressions in the index so that the expression code generator
+** will know to replace occurrences of the indexed expression with
+** references to the corresponding column of the index.
+*/
+static SQLITE_NOINLINE void whereAddIndexedExpr(
+ Parse *pParse, /* Add IndexedExpr entries to pParse->pIdxExpr */
+ Index *pIdx, /* The index-on-expression that contains the expressions */
+ int iIdxCur, /* Cursor number for pIdx */
+ SrcItem *pTabItem /* The FROM clause entry for the table */
+){
+ int i;
+ IndexedExpr *p;
+ Table *pTab;
+ assert( pIdx->bHasExpr );
+ pTab = pIdx->pTable;
+ for(i=0; i<pIdx->nColumn; i++){
+ Expr *pExpr;
+ int j = pIdx->aiColumn[i];
+ int bMaybeNullRow;
+ if( j==XN_EXPR ){
+ pExpr = pIdx->aColExpr->a[i].pExpr;
+ testcase( pTabItem->fg.jointype & JT_LEFT );
+ testcase( pTabItem->fg.jointype & JT_RIGHT );
+ testcase( pTabItem->fg.jointype & JT_LTORJ );
+ bMaybeNullRow = (pTabItem->fg.jointype & (JT_LEFT|JT_LTORJ|JT_RIGHT))!=0;
+ }else if( j>=0 && (pTab->aCol[j].colFlags & COLFLAG_VIRTUAL)!=0 ){
+ pExpr = sqlite3ColumnExpr(pTab, &pTab->aCol[j]);
+ bMaybeNullRow = 0;
+ }else{
+ continue;
+ }
+ if( sqlite3ExprIsConstant(pExpr) ) continue;
+ p = sqlite3DbMallocRaw(pParse->db, sizeof(IndexedExpr));
+ if( p==0 ) break;
+ p->pIENext = pParse->pIdxExpr;
+ p->pExpr = sqlite3ExprDup(pParse->db, pExpr, 0);
+ p->iDataCur = pTabItem->iCursor;
+ p->iIdxCur = iIdxCur;
+ p->iIdxCol = i;
+ p->bMaybeNullRow = bMaybeNullRow;
+#ifdef SQLITE_ENABLE_EXPLAIN_COMMENTS
+ p->zIdxName = pIdx->zName;
+#endif
+ pParse->pIdxExpr = p;
+ if( p->pIENext==0 ){
+ sqlite3ParserAddCleanup(pParse, whereIndexedExprCleanup, pParse);
+ }
+ }
+}
+
+/*
** Generate the beginning of the loop used for WHERE clause processing.
** The return value is a pointer to an opaque structure that contains
** information needed to terminate the loop. Later, the calling routine
@@ -157409,7 +161262,7 @@ SQLITE_PRIVATE WhereInfo *sqlite3WhereBegin(
Expr *pWhere, /* The WHERE clause */
ExprList *pOrderBy, /* An ORDER BY (or GROUP BY) clause, or NULL */
ExprList *pResultSet, /* Query result set. Req'd for DISTINCT */
- Select *pLimit, /* Use this LIMIT/OFFSET clause, if any */
+ Select *pSelect, /* The entire SELECT statement */
u16 wctrlFlags, /* The WHERE_* flags defined in sqliteInt.h */
int iAuxArg /* If WHERE_OR_SUBCLAUSE is set, index cursor number
** If WHERE_USE_LIMIT, then the limit amount */
@@ -157468,7 +161321,7 @@ SQLITE_PRIVATE WhereInfo *sqlite3WhereBegin(
** field (type Bitmask) it must be aligned on an 8-byte boundary on
** some architectures. Hence the ROUND8() below.
*/
- nByteWInfo = ROUND8(sizeof(WhereInfo)+(nTabList-1)*sizeof(WhereLevel));
+ nByteWInfo = ROUND8P(sizeof(WhereInfo)+(nTabList-1)*sizeof(WhereLevel));
pWInfo = sqlite3DbMallocRawNN(db, nByteWInfo + sizeof(WhereLoop));
if( db->mallocFailed ){
sqlite3DbFree(db, pWInfo);
@@ -157478,7 +161331,9 @@ SQLITE_PRIVATE WhereInfo *sqlite3WhereBegin(
pWInfo->pParse = pParse;
pWInfo->pTabList = pTabList;
pWInfo->pOrderBy = pOrderBy;
+#if WHERETRACE_ENABLED
pWInfo->pWhere = pWhere;
+#endif
pWInfo->pResultSet = pResultSet;
pWInfo->aiCurOnePass[0] = pWInfo->aiCurOnePass[1] = -1;
pWInfo->nLevel = nTabList;
@@ -157486,9 +161341,7 @@ SQLITE_PRIVATE WhereInfo *sqlite3WhereBegin(
pWInfo->wctrlFlags = wctrlFlags;
pWInfo->iLimit = iAuxArg;
pWInfo->savedNQueryLoop = pParse->nQueryLoop;
-#ifndef SQLITE_OMIT_VIRTUALTABLE
- pWInfo->pLimit = pLimit;
-#endif
+ pWInfo->pSelect = pSelect;
memset(&pWInfo->nOBSat, 0,
offsetof(WhereInfo,sWC) - offsetof(WhereInfo,nOBSat));
memset(&pWInfo->a[0], 0, sizeof(WhereLoop)+nTabList*sizeof(WhereLevel));
@@ -157557,8 +161410,10 @@ SQLITE_PRIVATE WhereInfo *sqlite3WhereBegin(
/* Analyze all of the subexpressions. */
sqlite3WhereExprAnalyze(pTabList, &pWInfo->sWC);
- sqlite3WhereAddLimit(&pWInfo->sWC, pLimit);
- if( db->mallocFailed ) goto whereBeginError;
+ if( pSelect && pSelect->pLimit ){
+ sqlite3WhereAddLimit(&pWInfo->sWC, pSelect);
+ }
+ if( pParse->nErr ) goto whereBeginError;
/* Special case: WHERE terms that do not refer to any tables in the join
** (constant expressions). Evaluate each such term, and jump over all the
@@ -157790,8 +161645,10 @@ SQLITE_PRIVATE WhereInfo *sqlite3WhereBegin(
/* noop */
}else
#endif
- if( (pLoop->wsFlags & WHERE_IDX_ONLY)==0
- && (wctrlFlags & WHERE_OR_SUBCLAUSE)==0 ){
+ if( ((pLoop->wsFlags & WHERE_IDX_ONLY)==0
+ && (wctrlFlags & WHERE_OR_SUBCLAUSE)==0)
+ || (pTabItem->fg.jointype & (JT_LTORJ|JT_RIGHT))!=0
+ ){
int op = OP_OpenRead;
if( pWInfo->eOnePass!=ONEPASS_OFF ){
op = OP_OpenWrite;
@@ -157858,8 +161715,12 @@ SQLITE_PRIVATE WhereInfo *sqlite3WhereBegin(
op = OP_ReopenIdx;
}else{
iIndexCur = pParse->nTab++;
+ if( pIx->bHasExpr && OptimizationEnabled(db, SQLITE_IndexedExpr) ){
+ whereAddIndexedExpr(pParse, pIx, iIndexCur, pTabItem);
+ }
}
pLevel->iIdxCur = iIndexCur;
+ assert( pIx!=0 );
assert( pIx->pSchema==pTab->pSchema );
assert( iIndexCur>=0 );
if( op ){
@@ -157893,6 +161754,37 @@ SQLITE_PRIVATE WhereInfo *sqlite3WhereBegin(
}
}
if( iDb>=0 ) sqlite3CodeVerifySchema(pParse, iDb);
+ if( (pTabItem->fg.jointype & JT_RIGHT)!=0
+ && (pLevel->pRJ = sqlite3WhereMalloc(pWInfo, sizeof(WhereRightJoin)))!=0
+ ){
+ WhereRightJoin *pRJ = pLevel->pRJ;
+ pRJ->iMatch = pParse->nTab++;
+ pRJ->regBloom = ++pParse->nMem;
+ sqlite3VdbeAddOp2(v, OP_Blob, 65536, pRJ->regBloom);
+ pRJ->regReturn = ++pParse->nMem;
+ sqlite3VdbeAddOp2(v, OP_Null, 0, pRJ->regReturn);
+ assert( pTab==pTabItem->pTab );
+ if( HasRowid(pTab) ){
+ KeyInfo *pInfo;
+ sqlite3VdbeAddOp2(v, OP_OpenEphemeral, pRJ->iMatch, 1);
+ pInfo = sqlite3KeyInfoAlloc(pParse->db, 1, 0);
+ if( pInfo ){
+ pInfo->aColl[0] = 0;
+ pInfo->aSortFlags[0] = 0;
+ sqlite3VdbeAppendP4(v, pInfo, P4_KEYINFO);
+ }
+ }else{
+ Index *pPk = sqlite3PrimaryKeyIndex(pTab);
+ sqlite3VdbeAddOp2(v, OP_OpenEphemeral, pRJ->iMatch, pPk->nKeyCol);
+ sqlite3VdbeSetP4KeyInfo(pParse, pPk);
+ }
+ pLoop->wsFlags &= ~WHERE_IDX_ONLY;
+ /* The nature of RIGHT JOIN processing is such that it messes up
+ ** the output order. So omit any ORDER BY/GROUP BY elimination
+ ** optimizations. We need to do an actual sort for RIGHT JOIN. */
+ pWInfo->nOBSat = 0;
+ pWInfo->eDistinct = WHERE_DISTINCT_UNORDERED;
+ }
}
pWInfo->iTop = sqlite3VdbeCurrentAddr(v);
if( db->mallocFailed ) goto whereBeginError;
@@ -157904,9 +161796,20 @@ SQLITE_PRIVATE WhereInfo *sqlite3WhereBegin(
for(ii=0; ii<nTabList; ii++){
int addrExplain;
int wsFlags;
+ SrcItem *pSrc;
if( pParse->nErr ) goto whereBeginError;
pLevel = &pWInfo->a[ii];
wsFlags = pLevel->pWLoop->wsFlags;
+ pSrc = &pTabList->a[pLevel->iFrom];
+ if( pSrc->fg.isMaterialized ){
+ if( pSrc->fg.isCorrelated ){
+ sqlite3VdbeAddOp2(v, OP_Gosub, pSrc->regReturn, pSrc->addrFillSub);
+ }else{
+ int iOnce = sqlite3VdbeAddOp0(v, OP_Once); VdbeCoverage(v);
+ sqlite3VdbeAddOp2(v, OP_Gosub, pSrc->regReturn, pSrc->addrFillSub);
+ sqlite3VdbeJumpHere(v, iOnce);
+ }
+ }
if( (wsFlags & (WHERE_AUTO_INDEX|WHERE_BLOOMFILTER))!=0 ){
if( (wsFlags & WHERE_AUTO_INDEX)!=0 ){
#ifndef SQLITE_OMIT_AUTOMATIC_INDEX
@@ -157937,8 +161840,6 @@ SQLITE_PRIVATE WhereInfo *sqlite3WhereBegin(
/* Jump here if malloc fails */
whereBeginError:
if( pWInfo ){
- testcase( pWInfo->pExprMods!=0 );
- whereUndoExprMods(pWInfo);
pParse->nQueryLoop = pWInfo->savedNQueryLoop;
whereInfoFree(db, pWInfo);
}
@@ -157998,6 +161899,7 @@ SQLITE_PRIVATE void sqlite3WhereEnd(WhereInfo *pWInfo){
SrcList *pTabList = pWInfo->pTabList;
sqlite3 *db = pParse->db;
int iEnd = sqlite3VdbeCurrentAddr(v);
+ int nRJ = 0;
/* Generate loop termination code.
*/
@@ -158005,6 +161907,17 @@ SQLITE_PRIVATE void sqlite3WhereEnd(WhereInfo *pWInfo){
for(i=pWInfo->nLevel-1; i>=0; i--){
int addr;
pLevel = &pWInfo->a[i];
+ if( pLevel->pRJ ){
+ /* Terminate the subroutine that forms the interior of the loop of
+ ** the RIGHT JOIN table */
+ WhereRightJoin *pRJ = pLevel->pRJ;
+ sqlite3VdbeResolveLabel(v, pLevel->addrCont);
+ pLevel->addrCont = 0;
+ pRJ->endSubrtn = sqlite3VdbeCurrentAddr(v);
+ sqlite3VdbeAddOp3(v, OP_Return, pRJ->regReturn, pRJ->addrSubrtn, 1);
+ VdbeCoverage(v);
+ nRJ++;
+ }
pLoop = pLevel->pWLoop;
if( pLevel->op!=OP_Noop ){
#ifndef SQLITE_DISABLE_SKIPAHEAD_DISTINCT
@@ -158032,7 +161945,7 @@ SQLITE_PRIVATE void sqlite3WhereEnd(WhereInfo *pWInfo){
}
#endif /* SQLITE_DISABLE_SKIPAHEAD_DISTINCT */
/* The common case: Advance to the next row */
- sqlite3VdbeResolveLabel(v, pLevel->addrCont);
+ if( pLevel->addrCont ) sqlite3VdbeResolveLabel(v, pLevel->addrCont);
sqlite3VdbeAddOp3(v, pLevel->op, pLevel->p1, pLevel->p2, pLevel->p3);
sqlite3VdbeChangeP5(v, pLevel->p5);
VdbeCoverage(v);
@@ -158047,7 +161960,7 @@ SQLITE_PRIVATE void sqlite3WhereEnd(WhereInfo *pWInfo){
#ifndef SQLITE_DISABLE_SKIPAHEAD_DISTINCT
if( addrSeek ) sqlite3VdbeJumpHere(v, addrSeek);
#endif
- }else{
+ }else if( pLevel->addrCont ){
sqlite3VdbeResolveLabel(v, pLevel->addrCont);
}
if( (pLoop->wsFlags & WHERE_IN_ABLE)!=0 && pLevel->u.in.nIn>0 ){
@@ -158097,6 +162010,10 @@ SQLITE_PRIVATE void sqlite3WhereEnd(WhereInfo *pWInfo){
}
}
sqlite3VdbeResolveLabel(v, pLevel->addrBrk);
+ if( pLevel->pRJ ){
+ sqlite3VdbeAddOp3(v, OP_Return, pLevel->pRJ->regReturn, 0, 1);
+ VdbeCoverage(v);
+ }
if( pLevel->addrSkip ){
sqlite3VdbeGoto(v, pLevel->addrSkip);
VdbeComment((v, "next skip-scan on %s", pLoop->u.btree.pIndex->zName));
@@ -158140,11 +162057,6 @@ SQLITE_PRIVATE void sqlite3WhereEnd(WhereInfo *pWInfo){
pWInfo->pTabList->a[pLevel->iFrom].pTab->zName));
}
- /* The "break" point is here, just past the end of the outer loop.
- ** Set it.
- */
- sqlite3VdbeResolveLabel(v, pWInfo->iBreak);
-
assert( pWInfo->nLevel<=pTabList->nSrc );
for(i=0, pLevel=pWInfo->a; i<pWInfo->nLevel; i++, pLevel++){
int k, last;
@@ -158155,6 +162067,15 @@ SQLITE_PRIVATE void sqlite3WhereEnd(WhereInfo *pWInfo){
assert( pTab!=0 );
pLoop = pLevel->pWLoop;
+ /* Do RIGHT JOIN processing. Generate code that will output the
+ ** unmatched rows of the right operand of the RIGHT JOIN with
+ ** all of the columns of the left operand set to NULL.
+ */
+ if( pLevel->pRJ ){
+ sqlite3WhereRightJoinLoop(pWInfo, i, pLevel);
+ continue;
+ }
+
/* For a co-routine, change all OP_Column references to the table of
** the co-routine into OP_Copy of result contained in a register.
** OP_Rowid becomes OP_Null.
@@ -158166,29 +162087,6 @@ SQLITE_PRIVATE void sqlite3WhereEnd(WhereInfo *pWInfo){
continue;
}
-#ifdef SQLITE_ENABLE_EARLY_CURSOR_CLOSE
- /* Close all of the cursors that were opened by sqlite3WhereBegin.
- ** Except, do not close cursors that will be reused by the OR optimization
- ** (WHERE_OR_SUBCLAUSE). And do not close the OP_OpenWrite cursors
- ** created for the ONEPASS optimization.
- */
- if( (pTab->tabFlags & TF_Ephemeral)==0
- && !IsView(pTab)
- && (pWInfo->wctrlFlags & WHERE_OR_SUBCLAUSE)==0
- ){
- int ws = pLoop->wsFlags;
- if( pWInfo->eOnePass==ONEPASS_OFF && (ws & WHERE_IDX_ONLY)==0 ){
- sqlite3VdbeAddOp1(v, OP_Close, pTabItem->iCursor);
- }
- if( (ws & WHERE_INDEXED)!=0
- && (ws & (WHERE_IPK|WHERE_AUTO_INDEX))==0
- && pLevel->iIdxCur!=pWInfo->aiCurOnePass[1]
- ){
- sqlite3VdbeAddOp1(v, OP_Close, pLevel->iIdxCur);
- }
- }
-#endif
-
/* If this scan uses an index, make VDBE code substitutions to read data
** from the index instead of from the table where possible. In some cases
** this optimization prevents the table from ever being read, which can
@@ -158213,6 +162111,16 @@ SQLITE_PRIVATE void sqlite3WhereEnd(WhereInfo *pWInfo){
}else{
last = pWInfo->iEndWhere;
}
+ if( pIdx->bHasExpr ){
+ IndexedExpr *p = pParse->pIdxExpr;
+ while( p ){
+ if( p->iIdxCur==pLevel->iIdxCur ){
+ p->iDataCur = -1;
+ p->iIdxCur = -1;
+ }
+ p = p->pIENext;
+ }
+ }
k = pLevel->addrBody + 1;
#ifdef SQLITE_DEBUG
if( db->flags & SQLITE_VdbeAddopTrace ){
@@ -158289,11 +162197,16 @@ SQLITE_PRIVATE void sqlite3WhereEnd(WhereInfo *pWInfo){
}
}
+ /* The "break" point is here, just past the end of the outer loop.
+ ** Set it.
+ */
+ sqlite3VdbeResolveLabel(v, pWInfo->iBreak);
+
/* Final cleanup
*/
- if( pWInfo->pExprMods ) whereUndoExprMods(pWInfo);
pParse->nQueryLoop = pWInfo->savedNQueryLoop;
whereInfoFree(db, pWInfo);
+ pParse->withinRJSubrtn -= nRJ;
return;
}
@@ -159025,7 +162938,7 @@ SQLITE_PRIVATE void sqlite3WindowUpdate(
}
}
}
- pWin->pFunc = pFunc;
+ pWin->pWFunc = pFunc;
}
/*
@@ -159201,7 +163114,6 @@ static ExprList *exprListAppendList(
for(i=0; i<pAppend->nExpr; i++){
sqlite3 *db = pParse->db;
Expr *pDup = sqlite3ExprDup(db, pAppend->a[i].pExpr, 0);
- assert( pDup==0 || !ExprHasProperty(pDup, EP_MemToken) );
if( db->mallocFailed ){
sqlite3ExprDelete(db, pDup);
break;
@@ -159217,7 +163129,7 @@ static ExprList *exprListAppendList(
}
}
pList = sqlite3ExprListAppend(pParse, pList, pDup);
- if( pList ) pList->a[nInit+i].sortFlags = pAppend->a[i].sortFlags;
+ if( pList ) pList->a[nInit+i].fg.sortFlags = pAppend->a[i].fg.sortFlags;
}
}
return pList;
@@ -159337,9 +163249,9 @@ SQLITE_PRIVATE int sqlite3WindowRewrite(Parse *pParse, Select *p){
for(pWin=pMWin; pWin; pWin=pWin->pNextWin){
ExprList *pArgs;
assert( ExprUseXList(pWin->pOwner) );
- assert( pWin->pFunc!=0 );
+ assert( pWin->pWFunc!=0 );
pArgs = pWin->pOwner->x.pList;
- if( pWin->pFunc->funcFlags & SQLITE_FUNC_SUBTYPE ){
+ if( pWin->pWFunc->funcFlags & SQLITE_FUNC_SUBTYPE ){
selectWindowRewriteEList(pParse, pMWin, pSrc, pArgs, pTab, &pSublist);
pWin->iArgCol = (pSublist ? pSublist->nExpr : 0);
pWin->bExprArgs = 1;
@@ -159721,7 +163633,7 @@ SQLITE_PRIVATE void sqlite3WindowCodeInit(Parse *pParse, Select *pSelect){
}
for(pWin=pMWin; pWin; pWin=pWin->pNextWin){
- FuncDef *p = pWin->pFunc;
+ FuncDef *p = pWin->pWFunc;
if( (p->funcFlags & SQLITE_FUNC_MINMAX) && pWin->eStart!=TK_UNBOUNDED ){
/* The inline versions of min() and max() require a single ephemeral
** table and 3 registers. The registers are used as follows:
@@ -159738,7 +163650,7 @@ SQLITE_PRIVATE void sqlite3WindowCodeInit(Parse *pParse, Select *pSelect){
pWin->csrApp = pParse->nTab++;
pWin->regApp = pParse->nMem+1;
pParse->nMem += 3;
- if( pKeyInfo && pWin->pFunc->zName[1]=='i' ){
+ if( pKeyInfo && pWin->pWFunc->zName[1]=='i' ){
assert( pKeyInfo->aSortFlags[0]==0 );
pKeyInfo->aSortFlags[0] = KEYINFO_ORDER_DESC;
}
@@ -159961,7 +163873,7 @@ static void windowAggStep(
Vdbe *v = sqlite3GetVdbe(pParse);
Window *pWin;
for(pWin=pMWin; pWin; pWin=pWin->pNextWin){
- FuncDef *pFunc = pWin->pFunc;
+ FuncDef *pFunc = pWin->pWFunc;
int regArg;
int nArg = pWin->bExprArgs ? 0 : windowArgCount(pWin);
int i;
@@ -160075,7 +163987,7 @@ static void windowAggFinal(WindowCodeArg *p, int bFin){
for(pWin=pMWin; pWin; pWin=pWin->pNextWin){
if( pMWin->regStartRowid==0
- && (pWin->pFunc->funcFlags & SQLITE_FUNC_MINMAX)
+ && (pWin->pWFunc->funcFlags & SQLITE_FUNC_MINMAX)
&& (pWin->eStart!=TK_UNBOUNDED)
){
sqlite3VdbeAddOp2(v, OP_Null, 0, pWin->regResult);
@@ -160089,12 +164001,12 @@ static void windowAggFinal(WindowCodeArg *p, int bFin){
int nArg = windowArgCount(pWin);
if( bFin ){
sqlite3VdbeAddOp2(v, OP_AggFinal, pWin->regAccum, nArg);
- sqlite3VdbeAppendP4(v, pWin->pFunc, P4_FUNCDEF);
+ sqlite3VdbeAppendP4(v, pWin->pWFunc, P4_FUNCDEF);
sqlite3VdbeAddOp2(v, OP_Copy, pWin->regAccum, pWin->regResult);
sqlite3VdbeAddOp2(v, OP_Null, 0, pWin->regAccum);
}else{
sqlite3VdbeAddOp3(v, OP_AggValue,pWin->regAccum,nArg,pWin->regResult);
- sqlite3VdbeAppendP4(v, pWin->pFunc, P4_FUNCDEF);
+ sqlite3VdbeAppendP4(v, pWin->pWFunc, P4_FUNCDEF);
}
}
}
@@ -160223,7 +164135,7 @@ static void windowReturnOneRow(WindowCodeArg *p){
Window *pWin;
for(pWin=pMWin; pWin; pWin=pWin->pNextWin){
- FuncDef *pFunc = pWin->pFunc;
+ FuncDef *pFunc = pWin->pWFunc;
assert( ExprUseXList(pWin->pOwner) );
if( pFunc->zName==nth_valueName
|| pFunc->zName==first_valueName
@@ -160295,7 +164207,7 @@ static int windowInitAccum(Parse *pParse, Window *pMWin){
int nArg = 0;
Window *pWin;
for(pWin=pMWin; pWin; pWin=pWin->pNextWin){
- FuncDef *pFunc = pWin->pFunc;
+ FuncDef *pFunc = pWin->pWFunc;
assert( pWin->regAccum );
sqlite3VdbeAddOp2(v, OP_Null, 0, pWin->regAccum);
nArg = MAX(nArg, windowArgCount(pWin));
@@ -160325,7 +164237,7 @@ static int windowCacheFrame(Window *pMWin){
Window *pWin;
if( pMWin->regStartRowid ) return 1;
for(pWin=pMWin; pWin; pWin=pWin->pNextWin){
- FuncDef *pFunc = pWin->pFunc;
+ FuncDef *pFunc = pWin->pWFunc;
if( (pFunc->zName==nth_valueName)
|| (pFunc->zName==first_valueName)
|| (pFunc->zName==leadName)
@@ -160418,7 +164330,7 @@ static void windowCodeRangeTest(
assert( op==OP_Ge || op==OP_Gt || op==OP_Le );
assert( pOrderBy && pOrderBy->nExpr==1 );
- if( pOrderBy->a[0].sortFlags & KEYINFO_ORDER_DESC ){
+ if( pOrderBy->a[0].fg.sortFlags & KEYINFO_ORDER_DESC ){
switch( op ){
case OP_Ge: op = OP_Le; break;
case OP_Gt: op = OP_Lt; break;
@@ -160451,7 +164363,7 @@ static void windowCodeRangeTest(
** Additionally, if either reg1 or reg2 are NULL but the jump to lbl is
** not taken, control jumps over the comparison operator coded below this
** block. */
- if( pOrderBy->a[0].sortFlags & KEYINFO_ORDER_BIGNULL ){
+ if( pOrderBy->a[0].fg.sortFlags & KEYINFO_ORDER_BIGNULL ){
/* This block runs if reg1 contains a NULL. */
int addr = sqlite3VdbeAddOp1(v, OP_NotNull, reg1); VdbeCoverage(v);
switch( op ){
@@ -160472,10 +164384,9 @@ static void windowCodeRangeTest(
/* This block runs if reg1 is not NULL, but reg2 is. */
sqlite3VdbeJumpHere(v, addr);
- sqlite3VdbeAddOp2(v, OP_IsNull, reg2, lbl); VdbeCoverage(v);
- if( op==OP_Gt || op==OP_Ge ){
- sqlite3VdbeChangeP2(v, -1, addrDone);
- }
+ sqlite3VdbeAddOp2(v, OP_IsNull, reg2,
+ (op==OP_Gt || op==OP_Ge) ? addrDone : lbl);
+ VdbeCoverage(v);
}
/* Register reg1 currently contains csr1.peerVal (the peer-value from csr1).
@@ -160683,7 +164594,7 @@ SQLITE_PRIVATE Window *sqlite3WindowDup(sqlite3 *db, Expr *pOwner, Window *p){
pNew->zName = sqlite3DbStrDup(db, p->zName);
pNew->zBase = sqlite3DbStrDup(db, p->zBase);
pNew->pFilter = sqlite3ExprDup(db, p->pFilter, 0);
- pNew->pFunc = p->pFunc;
+ pNew->pWFunc = p->pWFunc;
pNew->pPartition = sqlite3ExprListDup(db, p->pPartition, 0);
pNew->pOrderBy = sqlite3ExprListDup(db, p->pOrderBy, 0);
pNew->eFrmType = p->eFrmType;
@@ -161569,7 +165480,7 @@ static void updateDeleteLimitError(
p->affExpr = 0;
p->flags = EP_Leaf;
ExprClearVVAProperties(p);
- p->iAgg = -1;
+ /* p->iAgg = -1; // Not required */
p->pLeft = p->pRight = 0;
p->pAggInfo = 0;
memset(&p->x, 0, sizeof(p->x));
@@ -161902,6 +165813,7 @@ typedef union {
With* yy521;
const char* yy522;
Expr* yy528;
+ OnOrUsing yy561;
struct FrameBound yy595;
} YYMINORTYPE;
#ifndef YYSTACKDEPTH
@@ -161918,18 +165830,18 @@ typedef union {
#define sqlite3ParserCTX_FETCH Parse *pParse=yypParser->pParse;
#define sqlite3ParserCTX_STORE yypParser->pParse=pParse;
#define YYFALLBACK 1
-#define YYNSTATE 578
-#define YYNRULE 402
-#define YYNRULE_WITH_ACTION 340
+#define YYNSTATE 580
+#define YYNRULE 405
+#define YYNRULE_WITH_ACTION 342
#define YYNTOKEN 185
-#define YY_MAX_SHIFT 577
-#define YY_MIN_SHIFTREDUCE 835
-#define YY_MAX_SHIFTREDUCE 1236
-#define YY_ERROR_ACTION 1237
-#define YY_ACCEPT_ACTION 1238
-#define YY_NO_ACTION 1239
-#define YY_MIN_REDUCE 1240
-#define YY_MAX_REDUCE 1641
+#define YY_MAX_SHIFT 579
+#define YY_MIN_SHIFTREDUCE 839
+#define YY_MAX_SHIFTREDUCE 1243
+#define YY_ERROR_ACTION 1244
+#define YY_ACCEPT_ACTION 1245
+#define YY_NO_ACTION 1246
+#define YY_MIN_REDUCE 1247
+#define YY_MAX_REDUCE 1651
/************* End control #defines *******************************************/
#define YY_NLOOKAHEAD ((int)(sizeof(yy_lookahead)/sizeof(yy_lookahead[0])))
@@ -161996,429 +165908,432 @@ typedef union {
** yy_default[] Default action for each state.
**
*********** Begin parsing tables **********************************************/
-#define YY_ACTTAB_COUNT (2071)
+#define YY_ACTTAB_COUNT (2101)
static const YYACTIONTYPE yy_action[] = {
- /* 0 */ 570, 1311, 570, 1290, 201, 201, 570, 116, 112, 222,
- /* 10 */ 570, 1311, 381, 570, 116, 112, 222, 401, 412, 413,
- /* 20 */ 1264, 382, 1273, 41, 41, 41, 41, 1416, 1521, 71,
- /* 30 */ 71, 971, 1262, 41, 41, 495, 71, 71, 272, 972,
- /* 40 */ 298, 480, 298, 123, 124, 114, 1214, 1214, 1048, 1051,
- /* 50 */ 1040, 1040, 121, 121, 122, 122, 122, 122, 547, 413,
- /* 60 */ 1238, 1, 1, 577, 2, 1242, 552, 116, 112, 222,
- /* 70 */ 309, 484, 142, 552, 1276, 528, 116, 112, 222, 1324,
- /* 80 */ 421, 527, 551, 123, 124, 114, 1214, 1214, 1048, 1051,
- /* 90 */ 1040, 1040, 121, 121, 122, 122, 122, 122, 428, 116,
- /* 100 */ 112, 222, 120, 120, 120, 120, 119, 119, 118, 118,
- /* 110 */ 118, 117, 113, 448, 277, 277, 277, 277, 564, 564,
- /* 120 */ 564, 1562, 380, 1564, 1190, 379, 1161, 567, 1161, 567,
- /* 130 */ 413, 1562, 541, 252, 219, 1557, 99, 141, 453, 6,
- /* 140 */ 369, 233, 120, 120, 120, 120, 119, 119, 118, 118,
- /* 150 */ 118, 117, 113, 448, 123, 124, 114, 1214, 1214, 1048,
- /* 160 */ 1051, 1040, 1040, 121, 121, 122, 122, 122, 122, 138,
- /* 170 */ 289, 1190, 1550, 452, 118, 118, 118, 117, 113, 448,
- /* 180 */ 125, 1190, 1191, 1192, 144, 469, 338, 570, 150, 127,
- /* 190 */ 448, 122, 122, 122, 122, 115, 120, 120, 120, 120,
- /* 200 */ 119, 119, 118, 118, 118, 117, 113, 448, 458, 423,
- /* 210 */ 13, 13, 215, 120, 120, 120, 120, 119, 119, 118,
- /* 220 */ 118, 118, 117, 113, 448, 426, 308, 561, 1190, 1191,
- /* 230 */ 1192, 445, 444, 413, 1275, 122, 122, 122, 122, 120,
- /* 240 */ 120, 120, 120, 119, 119, 118, 118, 118, 117, 113,
- /* 250 */ 448, 1547, 98, 1037, 1037, 1049, 1052, 123, 124, 114,
- /* 260 */ 1214, 1214, 1048, 1051, 1040, 1040, 121, 121, 122, 122,
- /* 270 */ 122, 122, 570, 410, 409, 1190, 570, 413, 1221, 319,
- /* 280 */ 1221, 80, 81, 120, 120, 120, 120, 119, 119, 118,
- /* 290 */ 118, 118, 117, 113, 448, 70, 70, 1190, 1608, 71,
- /* 300 */ 71, 123, 124, 114, 1214, 1214, 1048, 1051, 1040, 1040,
- /* 310 */ 121, 121, 122, 122, 122, 122, 120, 120, 120, 120,
- /* 320 */ 119, 119, 118, 118, 118, 117, 113, 448, 1041, 210,
- /* 330 */ 1190, 369, 1190, 1191, 1192, 245, 552, 403, 508, 505,
- /* 340 */ 504, 108, 562, 138, 4, 520, 937, 437, 503, 217,
- /* 350 */ 518, 526, 356, 883, 1190, 1191, 1192, 387, 565, 570,
- /* 360 */ 120, 120, 120, 120, 119, 119, 118, 118, 118, 117,
- /* 370 */ 113, 448, 277, 277, 16, 16, 1602, 445, 444, 153,
- /* 380 */ 413, 449, 13, 13, 1283, 567, 1218, 1190, 1191, 1192,
- /* 390 */ 1007, 1220, 264, 559, 1578, 186, 570, 431, 138, 1219,
- /* 400 */ 308, 561, 476, 138, 123, 124, 114, 1214, 1214, 1048,
- /* 410 */ 1051, 1040, 1040, 121, 121, 122, 122, 122, 122, 55,
- /* 420 */ 55, 417, 1027, 511, 1221, 1190, 1221, 478, 106, 106,
- /* 430 */ 1316, 1316, 1190, 171, 570, 388, 107, 384, 449, 572,
- /* 440 */ 571, 434, 1547, 1017, 336, 553, 569, 263, 280, 364,
- /* 450 */ 514, 359, 513, 250, 495, 308, 561, 71, 71, 355,
- /* 460 */ 308, 561, 378, 120, 120, 120, 120, 119, 119, 118,
- /* 470 */ 118, 118, 117, 113, 448, 1017, 1017, 1019, 1020, 27,
- /* 480 */ 277, 277, 1190, 1191, 1192, 1156, 570, 532, 413, 1190,
- /* 490 */ 1191, 1192, 352, 567, 552, 1264, 537, 521, 1156, 1520,
- /* 500 */ 317, 1156, 285, 554, 489, 573, 570, 573, 486, 51,
- /* 510 */ 51, 207, 123, 124, 114, 1214, 1214, 1048, 1051, 1040,
- /* 520 */ 1040, 121, 121, 122, 122, 122, 122, 171, 1416, 13,
- /* 530 */ 13, 413, 277, 277, 1190, 509, 119, 119, 118, 118,
- /* 540 */ 118, 117, 113, 448, 433, 567, 522, 220, 519, 1556,
- /* 550 */ 369, 550, 1190, 6, 536, 123, 124, 114, 1214, 1214,
- /* 560 */ 1048, 1051, 1040, 1040, 121, 121, 122, 122, 122, 122,
- /* 570 */ 145, 120, 120, 120, 120, 119, 119, 118, 118, 118,
- /* 580 */ 117, 113, 448, 245, 570, 478, 508, 505, 504, 570,
- /* 590 */ 1485, 1190, 1191, 1192, 1314, 1314, 503, 1190, 149, 429,
- /* 600 */ 1190, 484, 413, 274, 369, 956, 876, 56, 56, 1190,
- /* 610 */ 1191, 1192, 71, 71, 120, 120, 120, 120, 119, 119,
- /* 620 */ 118, 118, 118, 117, 113, 448, 123, 124, 114, 1214,
- /* 630 */ 1214, 1048, 1051, 1040, 1040, 121, 121, 122, 122, 122,
- /* 640 */ 122, 413, 545, 1556, 83, 869, 98, 6, 932, 533,
- /* 650 */ 852, 547, 151, 931, 1190, 1191, 1192, 1190, 1191, 1192,
- /* 660 */ 290, 1547, 187, 1637, 399, 123, 124, 114, 1214, 1214,
- /* 670 */ 1048, 1051, 1040, 1040, 121, 121, 122, 122, 122, 122,
- /* 680 */ 570, 958, 570, 457, 957, 120, 120, 120, 120, 119,
- /* 690 */ 119, 118, 118, 118, 117, 113, 448, 1156, 221, 1190,
- /* 700 */ 335, 457, 456, 13, 13, 13, 13, 1007, 369, 467,
- /* 710 */ 1156, 193, 413, 1156, 386, 1547, 1174, 32, 297, 478,
- /* 720 */ 195, 1531, 5, 956, 120, 120, 120, 120, 119, 119,
- /* 730 */ 118, 118, 118, 117, 113, 448, 123, 124, 114, 1214,
- /* 740 */ 1214, 1048, 1051, 1040, 1040, 121, 121, 122, 122, 122,
- /* 750 */ 122, 413, 1071, 423, 1190, 1028, 1190, 1191, 1192, 1190,
- /* 760 */ 423, 336, 464, 322, 548, 1549, 446, 446, 446, 570,
- /* 770 */ 3, 117, 113, 448, 457, 123, 124, 114, 1214, 1214,
- /* 780 */ 1048, 1051, 1040, 1040, 121, 121, 122, 122, 122, 122,
- /* 790 */ 1477, 570, 15, 15, 293, 120, 120, 120, 120, 119,
- /* 800 */ 119, 118, 118, 118, 117, 113, 448, 1190, 570, 1490,
- /* 810 */ 1416, 1190, 1191, 1192, 13, 13, 1190, 1191, 1192, 1548,
- /* 820 */ 271, 271, 413, 286, 308, 561, 1012, 1490, 1492, 196,
- /* 830 */ 288, 71, 71, 567, 120, 120, 120, 120, 119, 119,
- /* 840 */ 118, 118, 118, 117, 113, 448, 123, 124, 114, 1214,
- /* 850 */ 1214, 1048, 1051, 1040, 1040, 121, 121, 122, 122, 122,
- /* 860 */ 122, 413, 201, 1091, 1190, 1191, 1192, 1328, 304, 1533,
- /* 870 */ 392, 278, 278, 454, 568, 406, 926, 926, 570, 567,
- /* 880 */ 570, 430, 495, 484, 567, 123, 124, 114, 1214, 1214,
- /* 890 */ 1048, 1051, 1040, 1040, 121, 121, 122, 122, 122, 122,
- /* 900 */ 1490, 71, 71, 13, 13, 120, 120, 120, 120, 119,
- /* 910 */ 119, 118, 118, 118, 117, 113, 448, 570, 549, 570,
- /* 920 */ 1581, 577, 2, 1242, 1096, 1096, 492, 1484, 309, 1529,
- /* 930 */ 142, 328, 413, 840, 841, 842, 312, 1324, 305, 367,
- /* 940 */ 43, 43, 57, 57, 120, 120, 120, 120, 119, 119,
- /* 950 */ 118, 118, 118, 117, 113, 448, 123, 124, 114, 1214,
- /* 960 */ 1214, 1048, 1051, 1040, 1040, 121, 121, 122, 122, 122,
- /* 970 */ 122, 12, 277, 277, 570, 1156, 413, 576, 432, 1242,
- /* 980 */ 469, 338, 296, 478, 309, 567, 142, 249, 1156, 308,
- /* 990 */ 561, 1156, 325, 1324, 327, 495, 459, 71, 71, 233,
- /* 1000 */ 283, 101, 114, 1214, 1214, 1048, 1051, 1040, 1040, 121,
- /* 1010 */ 121, 122, 122, 122, 122, 120, 120, 120, 120, 119,
- /* 1020 */ 119, 118, 118, 118, 117, 113, 448, 1112, 277, 277,
- /* 1030 */ 1416, 452, 398, 1234, 443, 277, 277, 248, 247, 246,
- /* 1040 */ 1323, 567, 1113, 313, 198, 294, 495, 1322, 567, 468,
- /* 1050 */ 570, 1431, 398, 1134, 1027, 233, 418, 1114, 295, 120,
- /* 1060 */ 120, 120, 120, 119, 119, 118, 118, 118, 117, 113,
- /* 1070 */ 448, 1018, 104, 71, 71, 1017, 326, 500, 912, 570,
- /* 1080 */ 277, 277, 277, 277, 1112, 1265, 419, 452, 913, 365,
- /* 1090 */ 1575, 1319, 413, 567, 956, 567, 9, 202, 255, 1113,
- /* 1100 */ 316, 491, 44, 44, 249, 563, 419, 1017, 1017, 1019,
- /* 1110 */ 447, 1235, 413, 1607, 1114, 901, 123, 124, 114, 1214,
- /* 1120 */ 1214, 1048, 1051, 1040, 1040, 121, 121, 122, 122, 122,
- /* 1130 */ 122, 1235, 413, 1211, 215, 558, 123, 124, 114, 1214,
- /* 1140 */ 1214, 1048, 1051, 1040, 1040, 121, 121, 122, 122, 122,
- /* 1150 */ 122, 1135, 1635, 474, 1635, 255, 123, 111, 114, 1214,
- /* 1160 */ 1214, 1048, 1051, 1040, 1040, 121, 121, 122, 122, 122,
- /* 1170 */ 122, 1135, 1636, 418, 1636, 120, 120, 120, 120, 119,
- /* 1180 */ 119, 118, 118, 118, 117, 113, 448, 221, 209, 355,
- /* 1190 */ 1211, 1211, 147, 1430, 495, 120, 120, 120, 120, 119,
- /* 1200 */ 119, 118, 118, 118, 117, 113, 448, 1260, 543, 523,
- /* 1210 */ 892, 555, 956, 12, 570, 120, 120, 120, 120, 119,
- /* 1220 */ 119, 118, 118, 118, 117, 113, 448, 542, 570, 864,
- /* 1230 */ 1133, 365, 1575, 350, 1360, 413, 1167, 58, 58, 343,
- /* 1240 */ 1359, 512, 277, 277, 277, 277, 277, 277, 1211, 893,
- /* 1250 */ 1133, 59, 59, 463, 367, 567, 570, 567, 96, 567,
- /* 1260 */ 124, 114, 1214, 1214, 1048, 1051, 1040, 1040, 121, 121,
- /* 1270 */ 122, 122, 122, 122, 570, 1416, 570, 281, 1190, 60,
- /* 1280 */ 60, 110, 396, 396, 395, 266, 393, 864, 1167, 849,
- /* 1290 */ 570, 485, 570, 440, 345, 1156, 348, 61, 61, 62,
- /* 1300 */ 62, 971, 227, 1554, 315, 435, 544, 6, 1156, 972,
- /* 1310 */ 570, 1156, 314, 45, 45, 46, 46, 516, 120, 120,
- /* 1320 */ 120, 120, 119, 119, 118, 118, 118, 117, 113, 448,
- /* 1330 */ 420, 173, 1536, 47, 47, 1190, 1191, 1192, 108, 562,
- /* 1340 */ 329, 4, 229, 1555, 932, 570, 441, 6, 570, 931,
- /* 1350 */ 164, 570, 1294, 137, 1194, 565, 570, 1553, 570, 1093,
- /* 1360 */ 570, 6, 570, 1093, 535, 570, 872, 8, 49, 49,
- /* 1370 */ 228, 50, 50, 570, 63, 63, 570, 461, 449, 64,
- /* 1380 */ 64, 65, 65, 14, 14, 66, 66, 411, 129, 129,
- /* 1390 */ 559, 570, 462, 570, 1509, 490, 67, 67, 570, 52,
- /* 1400 */ 52, 550, 411, 471, 539, 414, 226, 1027, 570, 538,
- /* 1410 */ 308, 561, 1194, 411, 68, 68, 69, 69, 570, 1027,
- /* 1420 */ 570, 53, 53, 872, 1018, 106, 106, 529, 1017, 570,
- /* 1430 */ 1508, 159, 159, 107, 455, 449, 572, 571, 475, 307,
- /* 1440 */ 1017, 160, 160, 76, 76, 570, 1552, 470, 411, 411,
- /* 1450 */ 6, 1229, 54, 54, 482, 276, 219, 570, 891, 890,
- /* 1460 */ 1017, 1017, 1019, 84, 206, 1210, 230, 282, 72, 72,
- /* 1470 */ 333, 487, 1017, 1017, 1019, 1020, 27, 1580, 1178, 451,
- /* 1480 */ 130, 130, 281, 148, 105, 38, 103, 396, 396, 395,
- /* 1490 */ 266, 393, 570, 1130, 849, 400, 570, 108, 562, 570,
- /* 1500 */ 4, 311, 570, 30, 17, 570, 279, 227, 570, 315,
- /* 1510 */ 108, 562, 472, 4, 565, 73, 73, 314, 570, 157,
- /* 1520 */ 157, 570, 131, 131, 530, 132, 132, 565, 128, 128,
- /* 1530 */ 570, 158, 158, 570, 31, 291, 570, 449, 334, 525,
- /* 1540 */ 98, 152, 152, 424, 136, 136, 1009, 229, 254, 559,
- /* 1550 */ 449, 483, 340, 135, 135, 164, 133, 133, 137, 134,
- /* 1560 */ 134, 879, 559, 539, 570, 477, 570, 254, 540, 479,
- /* 1570 */ 339, 254, 98, 898, 899, 228, 539, 570, 1027, 570,
- /* 1580 */ 1078, 538, 210, 232, 106, 106, 1356, 75, 75, 77,
- /* 1590 */ 77, 1027, 107, 344, 449, 572, 571, 106, 106, 1017,
- /* 1600 */ 74, 74, 42, 42, 570, 107, 347, 449, 572, 571,
- /* 1610 */ 414, 501, 1017, 251, 363, 308, 561, 1139, 353, 879,
- /* 1620 */ 98, 1074, 349, 251, 362, 1595, 351, 48, 48, 1021,
- /* 1630 */ 1307, 1017, 1017, 1019, 1020, 27, 1293, 1291, 1078, 455,
- /* 1640 */ 965, 929, 254, 110, 1017, 1017, 1019, 1020, 27, 1178,
- /* 1650 */ 451, 974, 975, 281, 108, 562, 1292, 4, 396, 396,
- /* 1660 */ 395, 266, 393, 1347, 1090, 849, 1090, 1089, 862, 1089,
- /* 1670 */ 146, 565, 930, 358, 110, 303, 368, 557, 227, 1368,
- /* 1680 */ 315, 108, 562, 1415, 4, 1343, 496, 1021, 314, 1354,
- /* 1690 */ 1569, 556, 1421, 1272, 449, 204, 1263, 1251, 565, 1250,
- /* 1700 */ 1252, 1588, 269, 1340, 371, 373, 559, 375, 11, 212,
- /* 1710 */ 397, 225, 321, 284, 1402, 460, 287, 331, 229, 332,
- /* 1720 */ 292, 449, 324, 216, 337, 1407, 164, 481, 377, 137,
- /* 1730 */ 1406, 404, 506, 559, 1290, 1027, 361, 1481, 199, 1591,
- /* 1740 */ 211, 106, 106, 936, 1480, 1229, 228, 560, 175, 107,
- /* 1750 */ 200, 449, 572, 571, 258, 391, 1017, 1528, 1526, 223,
- /* 1760 */ 1226, 422, 1027, 83, 208, 79, 82, 184, 106, 106,
- /* 1770 */ 1486, 126, 1397, 550, 169, 320, 107, 1403, 449, 572,
- /* 1780 */ 571, 414, 177, 1017, 1390, 323, 308, 561, 1017, 1017,
- /* 1790 */ 1019, 1020, 27, 465, 35, 235, 100, 562, 499, 4,
- /* 1800 */ 179, 180, 181, 466, 182, 96, 402, 1409, 473, 1408,
- /* 1810 */ 455, 36, 1411, 565, 188, 1017, 1017, 1019, 1020, 27,
- /* 1820 */ 405, 1475, 488, 239, 89, 494, 270, 192, 1497, 342,
- /* 1830 */ 241, 497, 346, 242, 515, 243, 449, 1253, 1310, 1309,
- /* 1840 */ 407, 91, 436, 1308, 883, 217, 438, 439, 559, 524,
- /* 1850 */ 531, 408, 1351, 1606, 1301, 301, 1280, 1605, 360, 1279,
- /* 1860 */ 1278, 1604, 1574, 302, 95, 366, 370, 372, 1300, 1352,
- /* 1870 */ 1350, 374, 256, 257, 442, 10, 1349, 1027, 1461, 385,
- /* 1880 */ 97, 1375, 102, 106, 106, 534, 1560, 34, 1559, 574,
- /* 1890 */ 1184, 107, 265, 449, 572, 571, 267, 268, 1017, 203,
- /* 1900 */ 1333, 383, 389, 1332, 390, 575, 376, 1248, 1243, 1513,
- /* 1910 */ 161, 143, 1374, 1514, 1512, 162, 299, 1511, 163, 213,
- /* 1920 */ 836, 214, 78, 450, 205, 310, 224, 1088, 140, 1086,
- /* 1930 */ 1017, 1017, 1019, 1020, 27, 318, 306, 176, 165, 1210,
- /* 1940 */ 178, 231, 915, 234, 330, 1102, 183, 166, 167, 425,
- /* 1950 */ 427, 185, 85, 86, 87, 168, 88, 415, 1105, 236,
- /* 1960 */ 174, 237, 416, 1101, 154, 18, 238, 341, 1223, 240,
- /* 1970 */ 254, 493, 190, 1094, 37, 189, 851, 498, 362, 244,
- /* 1980 */ 354, 510, 191, 90, 170, 502, 19, 20, 507, 93,
- /* 1990 */ 881, 357, 92, 300, 894, 155, 517, 218, 1172, 156,
- /* 2000 */ 1054, 959, 1141, 94, 39, 1140, 273, 275, 964, 194,
- /* 2010 */ 110, 1158, 1162, 1160, 253, 21, 1166, 7, 1146, 33,
- /* 2020 */ 22, 197, 23, 24, 25, 1165, 546, 26, 98, 1069,
- /* 2030 */ 1055, 1053, 1057, 1111, 1058, 1110, 259, 260, 28, 40,
- /* 2040 */ 1180, 1022, 863, 109, 29, 566, 394, 1179, 139, 172,
- /* 2050 */ 925, 261, 1239, 1239, 1239, 1239, 1239, 1239, 1239, 1239,
- /* 2060 */ 262, 1239, 1239, 1239, 1239, 1597, 1239, 1239, 1239, 1239,
- /* 2070 */ 1596,
+ /* 0 */ 572, 208, 572, 118, 115, 229, 572, 118, 115, 229,
+ /* 10 */ 572, 1318, 381, 1297, 412, 566, 566, 566, 572, 413,
+ /* 20 */ 382, 1318, 1280, 41, 41, 41, 41, 208, 1530, 71,
+ /* 30 */ 71, 975, 423, 41, 41, 495, 303, 279, 303, 976,
+ /* 40 */ 401, 71, 71, 125, 126, 80, 1221, 1221, 1054, 1057,
+ /* 50 */ 1044, 1044, 123, 123, 124, 124, 124, 124, 480, 413,
+ /* 60 */ 1245, 1, 1, 579, 2, 1249, 554, 118, 115, 229,
+ /* 70 */ 317, 484, 146, 484, 528, 118, 115, 229, 533, 1331,
+ /* 80 */ 421, 527, 142, 125, 126, 80, 1221, 1221, 1054, 1057,
+ /* 90 */ 1044, 1044, 123, 123, 124, 124, 124, 124, 118, 115,
+ /* 100 */ 229, 327, 122, 122, 122, 122, 121, 121, 120, 120,
+ /* 110 */ 120, 119, 116, 448, 284, 284, 284, 284, 446, 446,
+ /* 120 */ 446, 1571, 380, 1573, 1196, 379, 1167, 569, 1167, 569,
+ /* 130 */ 413, 1571, 541, 259, 226, 448, 101, 145, 453, 316,
+ /* 140 */ 563, 240, 122, 122, 122, 122, 121, 121, 120, 120,
+ /* 150 */ 120, 119, 116, 448, 125, 126, 80, 1221, 1221, 1054,
+ /* 160 */ 1057, 1044, 1044, 123, 123, 124, 124, 124, 124, 142,
+ /* 170 */ 294, 1196, 343, 452, 120, 120, 120, 119, 116, 448,
+ /* 180 */ 127, 1196, 1197, 1198, 148, 445, 444, 572, 119, 116,
+ /* 190 */ 448, 124, 124, 124, 124, 117, 122, 122, 122, 122,
+ /* 200 */ 121, 121, 120, 120, 120, 119, 116, 448, 458, 113,
+ /* 210 */ 13, 13, 550, 122, 122, 122, 122, 121, 121, 120,
+ /* 220 */ 120, 120, 119, 116, 448, 426, 316, 563, 1196, 1197,
+ /* 230 */ 1198, 149, 1228, 413, 1228, 124, 124, 124, 124, 122,
+ /* 240 */ 122, 122, 122, 121, 121, 120, 120, 120, 119, 116,
+ /* 250 */ 448, 469, 346, 1041, 1041, 1055, 1058, 125, 126, 80,
+ /* 260 */ 1221, 1221, 1054, 1057, 1044, 1044, 123, 123, 124, 124,
+ /* 270 */ 124, 124, 1283, 526, 222, 1196, 572, 413, 224, 518,
+ /* 280 */ 175, 82, 83, 122, 122, 122, 122, 121, 121, 120,
+ /* 290 */ 120, 120, 119, 116, 448, 1011, 16, 16, 1196, 133,
+ /* 300 */ 133, 125, 126, 80, 1221, 1221, 1054, 1057, 1044, 1044,
+ /* 310 */ 123, 123, 124, 124, 124, 124, 122, 122, 122, 122,
+ /* 320 */ 121, 121, 120, 120, 120, 119, 116, 448, 1045, 550,
+ /* 330 */ 1196, 377, 1196, 1197, 1198, 252, 1438, 403, 508, 505,
+ /* 340 */ 504, 111, 564, 570, 4, 930, 930, 437, 503, 344,
+ /* 350 */ 464, 330, 364, 398, 1241, 1196, 1197, 1198, 567, 572,
+ /* 360 */ 122, 122, 122, 122, 121, 121, 120, 120, 120, 119,
+ /* 370 */ 116, 448, 284, 284, 373, 1584, 1611, 445, 444, 154,
+ /* 380 */ 413, 449, 71, 71, 1290, 569, 1225, 1196, 1197, 1198,
+ /* 390 */ 85, 1227, 271, 561, 547, 519, 1565, 572, 98, 1226,
+ /* 400 */ 6, 1282, 476, 142, 125, 126, 80, 1221, 1221, 1054,
+ /* 410 */ 1057, 1044, 1044, 123, 123, 124, 124, 124, 124, 554,
+ /* 420 */ 13, 13, 1031, 511, 1228, 1196, 1228, 553, 109, 109,
+ /* 430 */ 222, 572, 1242, 175, 572, 431, 110, 197, 449, 574,
+ /* 440 */ 573, 434, 1556, 1021, 325, 555, 1196, 270, 287, 372,
+ /* 450 */ 514, 367, 513, 257, 71, 71, 547, 71, 71, 363,
+ /* 460 */ 316, 563, 1617, 122, 122, 122, 122, 121, 121, 120,
+ /* 470 */ 120, 120, 119, 116, 448, 1021, 1021, 1023, 1024, 27,
+ /* 480 */ 284, 284, 1196, 1197, 1198, 1162, 572, 1616, 413, 905,
+ /* 490 */ 190, 554, 360, 569, 554, 941, 537, 521, 1162, 520,
+ /* 500 */ 417, 1162, 556, 1196, 1197, 1198, 572, 548, 1558, 51,
+ /* 510 */ 51, 214, 125, 126, 80, 1221, 1221, 1054, 1057, 1044,
+ /* 520 */ 1044, 123, 123, 124, 124, 124, 124, 1196, 478, 135,
+ /* 530 */ 135, 413, 284, 284, 1494, 509, 121, 121, 120, 120,
+ /* 540 */ 120, 119, 116, 448, 1011, 569, 522, 217, 545, 1565,
+ /* 550 */ 316, 563, 142, 6, 536, 125, 126, 80, 1221, 1221,
+ /* 560 */ 1054, 1057, 1044, 1044, 123, 123, 124, 124, 124, 124,
+ /* 570 */ 1559, 122, 122, 122, 122, 121, 121, 120, 120, 120,
+ /* 580 */ 119, 116, 448, 489, 1196, 1197, 1198, 486, 281, 1271,
+ /* 590 */ 961, 252, 1196, 377, 508, 505, 504, 1196, 344, 575,
+ /* 600 */ 1196, 575, 413, 292, 503, 961, 880, 191, 484, 316,
+ /* 610 */ 563, 388, 290, 384, 122, 122, 122, 122, 121, 121,
+ /* 620 */ 120, 120, 120, 119, 116, 448, 125, 126, 80, 1221,
+ /* 630 */ 1221, 1054, 1057, 1044, 1044, 123, 123, 124, 124, 124,
+ /* 640 */ 124, 413, 398, 1140, 1196, 873, 100, 284, 284, 1196,
+ /* 650 */ 1197, 1198, 377, 1097, 1196, 1197, 1198, 1196, 1197, 1198,
+ /* 660 */ 569, 459, 32, 377, 233, 125, 126, 80, 1221, 1221,
+ /* 670 */ 1054, 1057, 1044, 1044, 123, 123, 124, 124, 124, 124,
+ /* 680 */ 1437, 963, 572, 228, 962, 122, 122, 122, 122, 121,
+ /* 690 */ 121, 120, 120, 120, 119, 116, 448, 1162, 228, 1196,
+ /* 700 */ 157, 1196, 1197, 1198, 1557, 13, 13, 301, 961, 1236,
+ /* 710 */ 1162, 153, 413, 1162, 377, 1587, 1180, 5, 373, 1584,
+ /* 720 */ 433, 1242, 3, 961, 122, 122, 122, 122, 121, 121,
+ /* 730 */ 120, 120, 120, 119, 116, 448, 125, 126, 80, 1221,
+ /* 740 */ 1221, 1054, 1057, 1044, 1044, 123, 123, 124, 124, 124,
+ /* 750 */ 124, 413, 208, 571, 1196, 1032, 1196, 1197, 1198, 1196,
+ /* 760 */ 392, 856, 155, 1556, 286, 406, 1102, 1102, 492, 572,
+ /* 770 */ 469, 346, 1323, 1323, 1556, 125, 126, 80, 1221, 1221,
+ /* 780 */ 1054, 1057, 1044, 1044, 123, 123, 124, 124, 124, 124,
+ /* 790 */ 129, 572, 13, 13, 378, 122, 122, 122, 122, 121,
+ /* 800 */ 121, 120, 120, 120, 119, 116, 448, 302, 572, 457,
+ /* 810 */ 532, 1196, 1197, 1198, 13, 13, 1196, 1197, 1198, 1301,
+ /* 820 */ 467, 1271, 413, 1321, 1321, 1556, 1016, 457, 456, 200,
+ /* 830 */ 299, 71, 71, 1269, 122, 122, 122, 122, 121, 121,
+ /* 840 */ 120, 120, 120, 119, 116, 448, 125, 126, 80, 1221,
+ /* 850 */ 1221, 1054, 1057, 1044, 1044, 123, 123, 124, 124, 124,
+ /* 860 */ 124, 413, 227, 1077, 1162, 284, 284, 423, 312, 278,
+ /* 870 */ 278, 285, 285, 1423, 410, 409, 386, 1162, 569, 572,
+ /* 880 */ 1162, 1200, 569, 1604, 569, 125, 126, 80, 1221, 1221,
+ /* 890 */ 1054, 1057, 1044, 1044, 123, 123, 124, 124, 124, 124,
+ /* 900 */ 457, 1486, 13, 13, 1540, 122, 122, 122, 122, 121,
+ /* 910 */ 121, 120, 120, 120, 119, 116, 448, 201, 572, 358,
+ /* 920 */ 1590, 579, 2, 1249, 844, 845, 846, 1566, 317, 1216,
+ /* 930 */ 146, 6, 413, 255, 254, 253, 206, 1331, 9, 1200,
+ /* 940 */ 262, 71, 71, 428, 122, 122, 122, 122, 121, 121,
+ /* 950 */ 120, 120, 120, 119, 116, 448, 125, 126, 80, 1221,
+ /* 960 */ 1221, 1054, 1057, 1044, 1044, 123, 123, 124, 124, 124,
+ /* 970 */ 124, 572, 284, 284, 572, 1217, 413, 578, 313, 1249,
+ /* 980 */ 353, 1300, 356, 423, 317, 569, 146, 495, 529, 1647,
+ /* 990 */ 399, 375, 495, 1331, 70, 70, 1299, 71, 71, 240,
+ /* 1000 */ 1329, 104, 80, 1221, 1221, 1054, 1057, 1044, 1044, 123,
+ /* 1010 */ 123, 124, 124, 124, 124, 122, 122, 122, 122, 121,
+ /* 1020 */ 121, 120, 120, 120, 119, 116, 448, 1118, 284, 284,
+ /* 1030 */ 432, 452, 1529, 1217, 443, 284, 284, 1493, 1356, 311,
+ /* 1040 */ 478, 569, 1119, 975, 495, 495, 217, 1267, 569, 1542,
+ /* 1050 */ 572, 976, 207, 572, 1031, 240, 387, 1120, 523, 122,
+ /* 1060 */ 122, 122, 122, 121, 121, 120, 120, 120, 119, 116,
+ /* 1070 */ 448, 1022, 107, 71, 71, 1021, 13, 13, 916, 572,
+ /* 1080 */ 1499, 572, 284, 284, 97, 530, 495, 452, 917, 1330,
+ /* 1090 */ 1326, 549, 413, 284, 284, 569, 151, 209, 1499, 1501,
+ /* 1100 */ 262, 454, 55, 55, 56, 56, 569, 1021, 1021, 1023,
+ /* 1110 */ 447, 336, 413, 531, 12, 295, 125, 126, 80, 1221,
+ /* 1120 */ 1221, 1054, 1057, 1044, 1044, 123, 123, 124, 124, 124,
+ /* 1130 */ 124, 351, 413, 868, 1538, 1217, 125, 126, 80, 1221,
+ /* 1140 */ 1221, 1054, 1057, 1044, 1044, 123, 123, 124, 124, 124,
+ /* 1150 */ 124, 1141, 1645, 478, 1645, 375, 125, 114, 80, 1221,
+ /* 1160 */ 1221, 1054, 1057, 1044, 1044, 123, 123, 124, 124, 124,
+ /* 1170 */ 124, 1499, 333, 478, 335, 122, 122, 122, 122, 121,
+ /* 1180 */ 121, 120, 120, 120, 119, 116, 448, 203, 1423, 572,
+ /* 1190 */ 1298, 868, 468, 1217, 440, 122, 122, 122, 122, 121,
+ /* 1200 */ 121, 120, 120, 120, 119, 116, 448, 557, 1141, 1646,
+ /* 1210 */ 543, 1646, 15, 15, 896, 122, 122, 122, 122, 121,
+ /* 1220 */ 121, 120, 120, 120, 119, 116, 448, 572, 298, 542,
+ /* 1230 */ 1139, 1423, 1563, 1564, 1335, 413, 6, 6, 1173, 1272,
+ /* 1240 */ 419, 320, 284, 284, 1423, 512, 569, 529, 300, 461,
+ /* 1250 */ 43, 43, 572, 897, 12, 569, 334, 482, 429, 411,
+ /* 1260 */ 126, 80, 1221, 1221, 1054, 1057, 1044, 1044, 123, 123,
+ /* 1270 */ 124, 124, 124, 124, 572, 57, 57, 288, 1196, 1423,
+ /* 1280 */ 500, 462, 396, 396, 395, 273, 393, 1139, 1562, 853,
+ /* 1290 */ 1173, 411, 6, 572, 321, 1162, 474, 44, 44, 1561,
+ /* 1300 */ 1118, 430, 234, 6, 323, 256, 544, 256, 1162, 435,
+ /* 1310 */ 572, 1162, 322, 17, 491, 1119, 58, 58, 122, 122,
+ /* 1320 */ 122, 122, 121, 121, 120, 120, 120, 119, 116, 448,
+ /* 1330 */ 1120, 216, 485, 59, 59, 1196, 1197, 1198, 111, 564,
+ /* 1340 */ 324, 4, 236, 460, 530, 572, 237, 460, 572, 441,
+ /* 1350 */ 168, 560, 424, 141, 483, 567, 572, 293, 572, 1099,
+ /* 1360 */ 572, 293, 572, 1099, 535, 572, 876, 8, 60, 60,
+ /* 1370 */ 235, 61, 61, 572, 418, 572, 418, 572, 449, 62,
+ /* 1380 */ 62, 45, 45, 46, 46, 47, 47, 199, 49, 49,
+ /* 1390 */ 561, 572, 363, 572, 100, 490, 50, 50, 63, 63,
+ /* 1400 */ 64, 64, 565, 419, 539, 414, 572, 1031, 572, 538,
+ /* 1410 */ 316, 563, 316, 563, 65, 65, 14, 14, 572, 1031,
+ /* 1420 */ 572, 516, 936, 876, 1022, 109, 109, 935, 1021, 66,
+ /* 1430 */ 66, 131, 131, 110, 455, 449, 574, 573, 420, 177,
+ /* 1440 */ 1021, 132, 132, 67, 67, 572, 471, 572, 936, 475,
+ /* 1450 */ 1368, 283, 226, 935, 315, 1367, 411, 572, 463, 411,
+ /* 1460 */ 1021, 1021, 1023, 239, 411, 86, 213, 1354, 52, 52,
+ /* 1470 */ 68, 68, 1021, 1021, 1023, 1024, 27, 1589, 1184, 451,
+ /* 1480 */ 69, 69, 288, 97, 108, 1545, 106, 396, 396, 395,
+ /* 1490 */ 273, 393, 572, 883, 853, 887, 572, 111, 564, 470,
+ /* 1500 */ 4, 572, 152, 30, 38, 572, 1136, 234, 400, 323,
+ /* 1510 */ 111, 564, 531, 4, 567, 53, 53, 322, 572, 163,
+ /* 1520 */ 163, 572, 341, 472, 164, 164, 337, 567, 76, 76,
+ /* 1530 */ 572, 289, 1518, 572, 31, 1517, 572, 449, 342, 487,
+ /* 1540 */ 100, 54, 54, 348, 72, 72, 296, 236, 1084, 561,
+ /* 1550 */ 449, 883, 1364, 134, 134, 168, 73, 73, 141, 161,
+ /* 1560 */ 161, 1578, 561, 539, 572, 319, 572, 352, 540, 1013,
+ /* 1570 */ 477, 261, 261, 895, 894, 235, 539, 572, 1031, 572,
+ /* 1580 */ 479, 538, 261, 371, 109, 109, 525, 136, 136, 130,
+ /* 1590 */ 130, 1031, 110, 370, 449, 574, 573, 109, 109, 1021,
+ /* 1600 */ 162, 162, 156, 156, 572, 110, 1084, 449, 574, 573,
+ /* 1610 */ 414, 355, 1021, 572, 357, 316, 563, 572, 347, 572,
+ /* 1620 */ 100, 501, 361, 258, 100, 902, 903, 140, 140, 359,
+ /* 1630 */ 1314, 1021, 1021, 1023, 1024, 27, 139, 139, 366, 455,
+ /* 1640 */ 137, 137, 138, 138, 1021, 1021, 1023, 1024, 27, 1184,
+ /* 1650 */ 451, 572, 376, 288, 111, 564, 1025, 4, 396, 396,
+ /* 1660 */ 395, 273, 393, 572, 1145, 853, 572, 1080, 572, 258,
+ /* 1670 */ 496, 567, 572, 211, 75, 75, 559, 966, 234, 261,
+ /* 1680 */ 323, 111, 564, 933, 4, 113, 77, 77, 322, 74,
+ /* 1690 */ 74, 42, 42, 1377, 449, 48, 48, 1422, 567, 978,
+ /* 1700 */ 979, 1096, 1095, 1096, 1095, 866, 561, 150, 934, 1350,
+ /* 1710 */ 113, 1362, 558, 1428, 1025, 1279, 1270, 1258, 236, 1257,
+ /* 1720 */ 1259, 449, 1597, 1347, 308, 276, 168, 309, 11, 141,
+ /* 1730 */ 397, 310, 232, 561, 1409, 1031, 339, 291, 329, 219,
+ /* 1740 */ 340, 109, 109, 940, 297, 1414, 235, 345, 481, 110,
+ /* 1750 */ 506, 449, 574, 573, 332, 1413, 1021, 404, 1297, 369,
+ /* 1760 */ 223, 1490, 1031, 1489, 1359, 1360, 1358, 1357, 109, 109,
+ /* 1770 */ 204, 1600, 1236, 562, 265, 218, 110, 205, 449, 574,
+ /* 1780 */ 573, 414, 391, 1021, 1537, 179, 316, 563, 1021, 1021,
+ /* 1790 */ 1023, 1024, 27, 230, 1535, 1233, 79, 564, 85, 4,
+ /* 1800 */ 422, 215, 552, 81, 84, 188, 1410, 128, 1404, 550,
+ /* 1810 */ 455, 35, 328, 567, 173, 1021, 1021, 1023, 1024, 27,
+ /* 1820 */ 181, 1495, 1397, 331, 465, 183, 184, 185, 186, 466,
+ /* 1830 */ 499, 242, 98, 402, 1416, 1418, 449, 1415, 473, 36,
+ /* 1840 */ 192, 488, 405, 1506, 246, 91, 494, 196, 561, 1484,
+ /* 1850 */ 350, 497, 277, 354, 248, 249, 111, 564, 1260, 4,
+ /* 1860 */ 250, 407, 515, 436, 1317, 1308, 93, 1316, 1315, 887,
+ /* 1870 */ 1307, 224, 1583, 567, 438, 524, 439, 1031, 263, 264,
+ /* 1880 */ 442, 1615, 10, 109, 109, 1287, 408, 1614, 1286, 368,
+ /* 1890 */ 1285, 110, 1613, 449, 574, 573, 449, 306, 1021, 307,
+ /* 1900 */ 374, 1382, 1569, 1470, 1381, 385, 105, 314, 561, 99,
+ /* 1910 */ 1568, 534, 34, 576, 1190, 272, 1340, 551, 383, 274,
+ /* 1920 */ 1339, 210, 389, 390, 275, 577, 1255, 1250, 415, 165,
+ /* 1930 */ 1021, 1021, 1023, 1024, 27, 147, 1522, 1031, 166, 1523,
+ /* 1940 */ 416, 1521, 178, 109, 109, 1520, 304, 167, 840, 450,
+ /* 1950 */ 220, 110, 221, 449, 574, 573, 212, 78, 1021, 318,
+ /* 1960 */ 231, 1094, 1092, 144, 180, 326, 169, 1216, 241, 182,
+ /* 1970 */ 919, 338, 238, 1108, 187, 170, 171, 425, 427, 189,
+ /* 1980 */ 87, 88, 89, 90, 172, 1111, 243, 1107, 244, 158,
+ /* 1990 */ 1021, 1021, 1023, 1024, 27, 18, 245, 1230, 493, 349,
+ /* 2000 */ 1100, 261, 247, 193, 194, 37, 370, 855, 498, 251,
+ /* 2010 */ 195, 510, 92, 19, 174, 362, 502, 20, 507, 885,
+ /* 2020 */ 365, 898, 94, 305, 159, 95, 517, 96, 1178, 160,
+ /* 2030 */ 1060, 1147, 39, 1146, 225, 280, 282, 970, 198, 964,
+ /* 2040 */ 113, 1164, 1168, 260, 1166, 21, 1172, 7, 22, 1152,
+ /* 2050 */ 33, 23, 24, 25, 1171, 546, 26, 202, 100, 102,
+ /* 2060 */ 1075, 103, 1061, 1059, 1063, 1117, 1064, 1116, 266, 267,
+ /* 2070 */ 28, 40, 929, 1026, 867, 112, 29, 568, 394, 143,
+ /* 2080 */ 1186, 268, 176, 1185, 269, 1246, 1246, 1246, 1246, 1246,
+ /* 2090 */ 1246, 1246, 1246, 1246, 1246, 1606, 1246, 1246, 1246, 1246,
+ /* 2100 */ 1605,
};
static const YYCODETYPE yy_lookahead[] = {
- /* 0 */ 193, 223, 193, 225, 193, 193, 193, 274, 275, 276,
- /* 10 */ 193, 233, 219, 193, 274, 275, 276, 206, 206, 19,
- /* 20 */ 193, 219, 216, 216, 217, 216, 217, 193, 295, 216,
- /* 30 */ 217, 31, 205, 216, 217, 193, 216, 217, 213, 39,
- /* 40 */ 228, 193, 230, 43, 44, 45, 46, 47, 48, 49,
+ /* 0 */ 193, 193, 193, 274, 275, 276, 193, 274, 275, 276,
+ /* 10 */ 193, 223, 219, 225, 206, 210, 211, 212, 193, 19,
+ /* 20 */ 219, 233, 216, 216, 217, 216, 217, 193, 295, 216,
+ /* 30 */ 217, 31, 193, 216, 217, 193, 228, 213, 230, 39,
+ /* 40 */ 206, 216, 217, 43, 44, 45, 46, 47, 48, 49,
/* 50 */ 50, 51, 52, 53, 54, 55, 56, 57, 193, 19,
/* 60 */ 185, 186, 187, 188, 189, 190, 253, 274, 275, 276,
- /* 70 */ 195, 193, 197, 253, 216, 262, 274, 275, 276, 204,
- /* 80 */ 238, 204, 262, 43, 44, 45, 46, 47, 48, 49,
- /* 90 */ 50, 51, 52, 53, 54, 55, 56, 57, 264, 274,
- /* 100 */ 275, 276, 102, 103, 104, 105, 106, 107, 108, 109,
+ /* 70 */ 195, 193, 197, 193, 261, 274, 275, 276, 253, 204,
+ /* 80 */ 238, 204, 81, 43, 44, 45, 46, 47, 48, 49,
+ /* 90 */ 50, 51, 52, 53, 54, 55, 56, 57, 274, 275,
+ /* 100 */ 276, 262, 102, 103, 104, 105, 106, 107, 108, 109,
/* 110 */ 110, 111, 112, 113, 239, 240, 239, 240, 210, 211,
/* 120 */ 212, 314, 315, 314, 59, 316, 86, 252, 88, 252,
- /* 130 */ 19, 314, 315, 256, 257, 309, 25, 72, 296, 313,
- /* 140 */ 193, 266, 102, 103, 104, 105, 106, 107, 108, 109,
+ /* 130 */ 19, 314, 315, 256, 257, 113, 25, 72, 296, 138,
+ /* 140 */ 139, 266, 102, 103, 104, 105, 106, 107, 108, 109,
/* 150 */ 110, 111, 112, 113, 43, 44, 45, 46, 47, 48,
/* 160 */ 49, 50, 51, 52, 53, 54, 55, 56, 57, 81,
- /* 170 */ 292, 59, 307, 298, 108, 109, 110, 111, 112, 113,
- /* 180 */ 69, 116, 117, 118, 72, 128, 129, 193, 241, 22,
+ /* 170 */ 292, 59, 292, 298, 108, 109, 110, 111, 112, 113,
+ /* 180 */ 69, 116, 117, 118, 72, 106, 107, 193, 111, 112,
/* 190 */ 113, 54, 55, 56, 57, 58, 102, 103, 104, 105,
- /* 200 */ 106, 107, 108, 109, 110, 111, 112, 113, 120, 193,
- /* 210 */ 216, 217, 25, 102, 103, 104, 105, 106, 107, 108,
+ /* 200 */ 106, 107, 108, 109, 110, 111, 112, 113, 120, 25,
+ /* 210 */ 216, 217, 145, 102, 103, 104, 105, 106, 107, 108,
/* 220 */ 109, 110, 111, 112, 113, 231, 138, 139, 116, 117,
- /* 230 */ 118, 106, 107, 19, 216, 54, 55, 56, 57, 102,
+ /* 230 */ 118, 164, 153, 19, 155, 54, 55, 56, 57, 102,
/* 240 */ 103, 104, 105, 106, 107, 108, 109, 110, 111, 112,
- /* 250 */ 113, 304, 25, 46, 47, 48, 49, 43, 44, 45,
+ /* 250 */ 113, 128, 129, 46, 47, 48, 49, 43, 44, 45,
/* 260 */ 46, 47, 48, 49, 50, 51, 52, 53, 54, 55,
- /* 270 */ 56, 57, 193, 106, 107, 59, 193, 19, 153, 263,
- /* 280 */ 155, 67, 24, 102, 103, 104, 105, 106, 107, 108,
- /* 290 */ 109, 110, 111, 112, 113, 216, 217, 59, 230, 216,
+ /* 270 */ 56, 57, 216, 193, 25, 59, 193, 19, 165, 166,
+ /* 280 */ 193, 67, 24, 102, 103, 104, 105, 106, 107, 108,
+ /* 290 */ 109, 110, 111, 112, 113, 73, 216, 217, 59, 216,
/* 300 */ 217, 43, 44, 45, 46, 47, 48, 49, 50, 51,
/* 310 */ 52, 53, 54, 55, 56, 57, 102, 103, 104, 105,
- /* 320 */ 106, 107, 108, 109, 110, 111, 112, 113, 121, 142,
- /* 330 */ 59, 193, 116, 117, 118, 119, 253, 204, 122, 123,
- /* 340 */ 124, 19, 20, 81, 22, 262, 108, 19, 132, 165,
- /* 350 */ 166, 193, 24, 126, 116, 117, 118, 278, 36, 193,
+ /* 320 */ 106, 107, 108, 109, 110, 111, 112, 113, 121, 145,
+ /* 330 */ 59, 193, 116, 117, 118, 119, 273, 204, 122, 123,
+ /* 340 */ 124, 19, 20, 134, 22, 136, 137, 19, 132, 127,
+ /* 350 */ 128, 129, 24, 22, 23, 116, 117, 118, 36, 193,
/* 360 */ 102, 103, 104, 105, 106, 107, 108, 109, 110, 111,
- /* 370 */ 112, 113, 239, 240, 216, 217, 215, 106, 107, 241,
+ /* 370 */ 112, 113, 239, 240, 311, 312, 215, 106, 107, 241,
/* 380 */ 19, 59, 216, 217, 223, 252, 115, 116, 117, 118,
- /* 390 */ 73, 120, 26, 71, 193, 22, 193, 231, 81, 128,
- /* 400 */ 138, 139, 269, 81, 43, 44, 45, 46, 47, 48,
- /* 410 */ 49, 50, 51, 52, 53, 54, 55, 56, 57, 216,
- /* 420 */ 217, 198, 100, 95, 153, 59, 155, 193, 106, 107,
- /* 430 */ 235, 236, 59, 193, 193, 249, 114, 251, 116, 117,
- /* 440 */ 118, 113, 304, 121, 127, 204, 193, 119, 120, 121,
- /* 450 */ 122, 123, 124, 125, 193, 138, 139, 216, 217, 131,
- /* 460 */ 138, 139, 193, 102, 103, 104, 105, 106, 107, 108,
+ /* 390 */ 151, 120, 26, 71, 193, 308, 309, 193, 149, 128,
+ /* 400 */ 313, 216, 269, 81, 43, 44, 45, 46, 47, 48,
+ /* 410 */ 49, 50, 51, 52, 53, 54, 55, 56, 57, 253,
+ /* 420 */ 216, 217, 100, 95, 153, 59, 155, 261, 106, 107,
+ /* 430 */ 25, 193, 101, 193, 193, 231, 114, 25, 116, 117,
+ /* 440 */ 118, 113, 304, 121, 193, 204, 59, 119, 120, 121,
+ /* 450 */ 122, 123, 124, 125, 216, 217, 193, 216, 217, 131,
+ /* 460 */ 138, 139, 230, 102, 103, 104, 105, 106, 107, 108,
/* 470 */ 109, 110, 111, 112, 113, 153, 154, 155, 156, 157,
- /* 480 */ 239, 240, 116, 117, 118, 76, 193, 193, 19, 116,
- /* 490 */ 117, 118, 23, 252, 253, 193, 87, 204, 89, 238,
- /* 500 */ 193, 92, 268, 262, 281, 203, 193, 205, 285, 216,
+ /* 480 */ 239, 240, 116, 117, 118, 76, 193, 23, 19, 25,
+ /* 490 */ 22, 253, 23, 252, 253, 108, 87, 204, 89, 261,
+ /* 500 */ 198, 92, 261, 116, 117, 118, 193, 306, 307, 216,
/* 510 */ 217, 150, 43, 44, 45, 46, 47, 48, 49, 50,
- /* 520 */ 51, 52, 53, 54, 55, 56, 57, 193, 193, 216,
- /* 530 */ 217, 19, 239, 240, 59, 23, 106, 107, 108, 109,
- /* 540 */ 110, 111, 112, 113, 231, 252, 253, 193, 308, 309,
- /* 550 */ 193, 145, 59, 313, 145, 43, 44, 45, 46, 47,
+ /* 520 */ 51, 52, 53, 54, 55, 56, 57, 59, 193, 216,
+ /* 530 */ 217, 19, 239, 240, 283, 23, 106, 107, 108, 109,
+ /* 540 */ 110, 111, 112, 113, 73, 252, 253, 142, 308, 309,
+ /* 550 */ 138, 139, 81, 313, 145, 43, 44, 45, 46, 47,
/* 560 */ 48, 49, 50, 51, 52, 53, 54, 55, 56, 57,
- /* 570 */ 164, 102, 103, 104, 105, 106, 107, 108, 109, 110,
- /* 580 */ 111, 112, 113, 119, 193, 193, 122, 123, 124, 193,
- /* 590 */ 283, 116, 117, 118, 235, 236, 132, 59, 241, 264,
- /* 600 */ 59, 193, 19, 23, 193, 25, 23, 216, 217, 116,
- /* 610 */ 117, 118, 216, 217, 102, 103, 104, 105, 106, 107,
+ /* 570 */ 307, 102, 103, 104, 105, 106, 107, 108, 109, 110,
+ /* 580 */ 111, 112, 113, 281, 116, 117, 118, 285, 23, 193,
+ /* 590 */ 25, 119, 59, 193, 122, 123, 124, 59, 127, 203,
+ /* 600 */ 59, 205, 19, 268, 132, 25, 23, 22, 193, 138,
+ /* 610 */ 139, 249, 204, 251, 102, 103, 104, 105, 106, 107,
/* 620 */ 108, 109, 110, 111, 112, 113, 43, 44, 45, 46,
/* 630 */ 47, 48, 49, 50, 51, 52, 53, 54, 55, 56,
- /* 640 */ 57, 19, 308, 309, 151, 23, 25, 313, 135, 253,
- /* 650 */ 21, 193, 241, 140, 116, 117, 118, 116, 117, 118,
- /* 660 */ 268, 304, 22, 301, 302, 43, 44, 45, 46, 47,
+ /* 640 */ 57, 19, 22, 23, 59, 23, 25, 239, 240, 116,
+ /* 650 */ 117, 118, 193, 11, 116, 117, 118, 116, 117, 118,
+ /* 660 */ 252, 269, 22, 193, 15, 43, 44, 45, 46, 47,
/* 670 */ 48, 49, 50, 51, 52, 53, 54, 55, 56, 57,
- /* 680 */ 193, 143, 193, 193, 143, 102, 103, 104, 105, 106,
+ /* 680 */ 273, 143, 193, 118, 143, 102, 103, 104, 105, 106,
/* 690 */ 107, 108, 109, 110, 111, 112, 113, 76, 118, 59,
- /* 700 */ 292, 211, 212, 216, 217, 216, 217, 73, 193, 80,
- /* 710 */ 89, 25, 19, 92, 193, 304, 23, 22, 231, 193,
- /* 720 */ 231, 193, 22, 143, 102, 103, 104, 105, 106, 107,
+ /* 700 */ 241, 116, 117, 118, 304, 216, 217, 292, 143, 60,
+ /* 710 */ 89, 241, 19, 92, 193, 193, 23, 22, 311, 312,
+ /* 720 */ 231, 101, 22, 143, 102, 103, 104, 105, 106, 107,
/* 730 */ 108, 109, 110, 111, 112, 113, 43, 44, 45, 46,
/* 740 */ 47, 48, 49, 50, 51, 52, 53, 54, 55, 56,
- /* 750 */ 57, 19, 123, 193, 59, 23, 116, 117, 118, 59,
- /* 760 */ 193, 127, 128, 129, 306, 307, 210, 211, 212, 193,
- /* 770 */ 22, 111, 112, 113, 284, 43, 44, 45, 46, 47,
+ /* 750 */ 57, 19, 193, 193, 59, 23, 116, 117, 118, 59,
+ /* 760 */ 201, 21, 241, 304, 22, 206, 127, 128, 129, 193,
+ /* 770 */ 128, 129, 235, 236, 304, 43, 44, 45, 46, 47,
/* 780 */ 48, 49, 50, 51, 52, 53, 54, 55, 56, 57,
- /* 790 */ 161, 193, 216, 217, 268, 102, 103, 104, 105, 106,
- /* 800 */ 107, 108, 109, 110, 111, 112, 113, 59, 193, 193,
- /* 810 */ 193, 116, 117, 118, 216, 217, 116, 117, 118, 304,
- /* 820 */ 239, 240, 19, 263, 138, 139, 23, 211, 212, 231,
- /* 830 */ 263, 216, 217, 252, 102, 103, 104, 105, 106, 107,
+ /* 790 */ 22, 193, 216, 217, 193, 102, 103, 104, 105, 106,
+ /* 800 */ 107, 108, 109, 110, 111, 112, 113, 231, 193, 193,
+ /* 810 */ 193, 116, 117, 118, 216, 217, 116, 117, 118, 226,
+ /* 820 */ 80, 193, 19, 235, 236, 304, 23, 211, 212, 231,
+ /* 830 */ 204, 216, 217, 205, 102, 103, 104, 105, 106, 107,
/* 840 */ 108, 109, 110, 111, 112, 113, 43, 44, 45, 46,
/* 850 */ 47, 48, 49, 50, 51, 52, 53, 54, 55, 56,
- /* 860 */ 57, 19, 193, 11, 116, 117, 118, 240, 253, 193,
- /* 870 */ 201, 239, 240, 193, 134, 206, 136, 137, 193, 252,
- /* 880 */ 193, 264, 193, 193, 252, 43, 44, 45, 46, 47,
+ /* 860 */ 57, 19, 193, 123, 76, 239, 240, 193, 253, 239,
+ /* 870 */ 240, 239, 240, 193, 106, 107, 193, 89, 252, 193,
+ /* 880 */ 92, 59, 252, 141, 252, 43, 44, 45, 46, 47,
/* 890 */ 48, 49, 50, 51, 52, 53, 54, 55, 56, 57,
- /* 900 */ 284, 216, 217, 216, 217, 102, 103, 104, 105, 106,
- /* 910 */ 107, 108, 109, 110, 111, 112, 113, 193, 231, 193,
- /* 920 */ 187, 188, 189, 190, 127, 128, 129, 238, 195, 193,
- /* 930 */ 197, 16, 19, 7, 8, 9, 193, 204, 253, 193,
- /* 940 */ 216, 217, 216, 217, 102, 103, 104, 105, 106, 107,
+ /* 900 */ 284, 161, 216, 217, 193, 102, 103, 104, 105, 106,
+ /* 910 */ 107, 108, 109, 110, 111, 112, 113, 231, 193, 16,
+ /* 920 */ 187, 188, 189, 190, 7, 8, 9, 309, 195, 25,
+ /* 930 */ 197, 313, 19, 127, 128, 129, 262, 204, 22, 117,
+ /* 940 */ 24, 216, 217, 263, 102, 103, 104, 105, 106, 107,
/* 950 */ 108, 109, 110, 111, 112, 113, 43, 44, 45, 46,
/* 960 */ 47, 48, 49, 50, 51, 52, 53, 54, 55, 56,
- /* 970 */ 57, 213, 239, 240, 193, 76, 19, 188, 232, 190,
- /* 980 */ 128, 129, 292, 193, 195, 252, 197, 46, 89, 138,
- /* 990 */ 139, 92, 77, 204, 79, 193, 269, 216, 217, 266,
+ /* 970 */ 57, 193, 239, 240, 193, 59, 19, 188, 253, 190,
+ /* 980 */ 77, 226, 79, 193, 195, 252, 197, 193, 19, 301,
+ /* 990 */ 302, 193, 193, 204, 216, 217, 226, 216, 217, 266,
/* 1000 */ 204, 159, 45, 46, 47, 48, 49, 50, 51, 52,
/* 1010 */ 53, 54, 55, 56, 57, 102, 103, 104, 105, 106,
/* 1020 */ 107, 108, 109, 110, 111, 112, 113, 12, 239, 240,
- /* 1030 */ 193, 298, 22, 23, 253, 239, 240, 127, 128, 129,
- /* 1040 */ 238, 252, 27, 193, 286, 204, 193, 204, 252, 291,
- /* 1050 */ 193, 273, 22, 23, 100, 266, 115, 42, 268, 102,
+ /* 1030 */ 232, 298, 238, 117, 253, 239, 240, 238, 259, 260,
+ /* 1040 */ 193, 252, 27, 31, 193, 193, 142, 204, 252, 193,
+ /* 1050 */ 193, 39, 262, 193, 100, 266, 278, 42, 204, 102,
/* 1060 */ 103, 104, 105, 106, 107, 108, 109, 110, 111, 112,
- /* 1070 */ 113, 117, 159, 216, 217, 121, 161, 19, 63, 193,
- /* 1080 */ 239, 240, 239, 240, 12, 208, 209, 298, 73, 311,
- /* 1090 */ 312, 238, 19, 252, 25, 252, 22, 24, 24, 27,
- /* 1100 */ 193, 264, 216, 217, 46, 208, 209, 153, 154, 155,
- /* 1110 */ 253, 101, 19, 23, 42, 25, 43, 44, 45, 46,
+ /* 1070 */ 113, 117, 159, 216, 217, 121, 216, 217, 63, 193,
+ /* 1080 */ 193, 193, 239, 240, 115, 116, 193, 298, 73, 238,
+ /* 1090 */ 238, 231, 19, 239, 240, 252, 22, 24, 211, 212,
+ /* 1100 */ 24, 193, 216, 217, 216, 217, 252, 153, 154, 155,
+ /* 1110 */ 253, 16, 19, 144, 213, 268, 43, 44, 45, 46,
/* 1120 */ 47, 48, 49, 50, 51, 52, 53, 54, 55, 56,
- /* 1130 */ 57, 101, 19, 59, 25, 63, 43, 44, 45, 46,
+ /* 1130 */ 57, 238, 19, 59, 193, 59, 43, 44, 45, 46,
/* 1140 */ 47, 48, 49, 50, 51, 52, 53, 54, 55, 56,
- /* 1150 */ 57, 22, 23, 115, 25, 24, 43, 44, 45, 46,
+ /* 1150 */ 57, 22, 23, 193, 25, 193, 43, 44, 45, 46,
/* 1160 */ 47, 48, 49, 50, 51, 52, 53, 54, 55, 56,
- /* 1170 */ 57, 22, 23, 115, 25, 102, 103, 104, 105, 106,
- /* 1180 */ 107, 108, 109, 110, 111, 112, 113, 118, 150, 131,
- /* 1190 */ 59, 117, 22, 273, 193, 102, 103, 104, 105, 106,
- /* 1200 */ 107, 108, 109, 110, 111, 112, 113, 204, 66, 204,
- /* 1210 */ 35, 204, 143, 213, 193, 102, 103, 104, 105, 106,
- /* 1220 */ 107, 108, 109, 110, 111, 112, 113, 85, 193, 59,
- /* 1230 */ 101, 311, 312, 16, 193, 19, 94, 216, 217, 238,
- /* 1240 */ 193, 66, 239, 240, 239, 240, 239, 240, 117, 74,
- /* 1250 */ 101, 216, 217, 193, 193, 252, 193, 252, 149, 252,
+ /* 1170 */ 57, 284, 77, 193, 79, 102, 103, 104, 105, 106,
+ /* 1180 */ 107, 108, 109, 110, 111, 112, 113, 286, 193, 193,
+ /* 1190 */ 193, 117, 291, 117, 232, 102, 103, 104, 105, 106,
+ /* 1200 */ 107, 108, 109, 110, 111, 112, 113, 204, 22, 23,
+ /* 1210 */ 66, 25, 216, 217, 35, 102, 103, 104, 105, 106,
+ /* 1220 */ 107, 108, 109, 110, 111, 112, 113, 193, 268, 85,
+ /* 1230 */ 101, 193, 309, 309, 240, 19, 313, 313, 94, 208,
+ /* 1240 */ 209, 193, 239, 240, 193, 66, 252, 19, 268, 244,
+ /* 1250 */ 216, 217, 193, 74, 213, 252, 161, 19, 263, 254,
/* 1260 */ 44, 45, 46, 47, 48, 49, 50, 51, 52, 53,
- /* 1270 */ 54, 55, 56, 57, 193, 193, 193, 5, 59, 216,
- /* 1280 */ 217, 25, 10, 11, 12, 13, 14, 117, 146, 17,
- /* 1290 */ 193, 291, 193, 232, 77, 76, 79, 216, 217, 216,
- /* 1300 */ 217, 31, 30, 309, 32, 130, 87, 313, 89, 39,
- /* 1310 */ 193, 92, 40, 216, 217, 216, 217, 108, 102, 103,
+ /* 1270 */ 54, 55, 56, 57, 193, 216, 217, 5, 59, 193,
+ /* 1280 */ 19, 244, 10, 11, 12, 13, 14, 101, 309, 17,
+ /* 1290 */ 146, 254, 313, 193, 193, 76, 115, 216, 217, 309,
+ /* 1300 */ 12, 263, 30, 313, 32, 46, 87, 46, 89, 130,
+ /* 1310 */ 193, 92, 40, 22, 263, 27, 216, 217, 102, 103,
/* 1320 */ 104, 105, 106, 107, 108, 109, 110, 111, 112, 113,
- /* 1330 */ 299, 300, 193, 216, 217, 116, 117, 118, 19, 20,
- /* 1340 */ 193, 22, 70, 309, 135, 193, 264, 313, 193, 140,
- /* 1350 */ 78, 193, 226, 81, 59, 36, 193, 309, 193, 29,
- /* 1360 */ 193, 313, 193, 33, 145, 193, 59, 48, 216, 217,
- /* 1370 */ 98, 216, 217, 193, 216, 217, 193, 244, 59, 216,
- /* 1380 */ 217, 216, 217, 216, 217, 216, 217, 254, 216, 217,
- /* 1390 */ 71, 193, 244, 193, 193, 65, 216, 217, 193, 216,
- /* 1400 */ 217, 145, 254, 244, 85, 133, 15, 100, 193, 90,
- /* 1410 */ 138, 139, 117, 254, 216, 217, 216, 217, 193, 100,
- /* 1420 */ 193, 216, 217, 116, 117, 106, 107, 19, 121, 193,
- /* 1430 */ 193, 216, 217, 114, 162, 116, 117, 118, 244, 244,
- /* 1440 */ 121, 216, 217, 216, 217, 193, 309, 129, 254, 254,
- /* 1450 */ 313, 60, 216, 217, 19, 256, 257, 193, 120, 121,
- /* 1460 */ 153, 154, 155, 149, 150, 25, 24, 99, 216, 217,
- /* 1470 */ 152, 193, 153, 154, 155, 156, 157, 0, 1, 2,
- /* 1480 */ 216, 217, 5, 22, 158, 24, 160, 10, 11, 12,
- /* 1490 */ 13, 14, 193, 23, 17, 25, 193, 19, 20, 193,
- /* 1500 */ 22, 133, 193, 22, 22, 193, 22, 30, 193, 32,
- /* 1510 */ 19, 20, 129, 22, 36, 216, 217, 40, 193, 216,
- /* 1520 */ 217, 193, 216, 217, 116, 216, 217, 36, 216, 217,
- /* 1530 */ 193, 216, 217, 193, 53, 152, 193, 59, 23, 19,
- /* 1540 */ 25, 216, 217, 61, 216, 217, 23, 70, 25, 71,
- /* 1550 */ 59, 116, 193, 216, 217, 78, 216, 217, 81, 216,
- /* 1560 */ 217, 59, 71, 85, 193, 23, 193, 25, 90, 23,
- /* 1570 */ 23, 25, 25, 7, 8, 98, 85, 193, 100, 193,
- /* 1580 */ 59, 90, 142, 141, 106, 107, 193, 216, 217, 216,
- /* 1590 */ 217, 100, 114, 193, 116, 117, 118, 106, 107, 121,
- /* 1600 */ 216, 217, 216, 217, 193, 114, 193, 116, 117, 118,
- /* 1610 */ 133, 23, 121, 25, 121, 138, 139, 97, 23, 117,
- /* 1620 */ 25, 23, 193, 25, 131, 141, 193, 216, 217, 59,
- /* 1630 */ 193, 153, 154, 155, 156, 157, 226, 193, 117, 162,
- /* 1640 */ 23, 23, 25, 25, 153, 154, 155, 156, 157, 1,
- /* 1650 */ 2, 83, 84, 5, 19, 20, 226, 22, 10, 11,
- /* 1660 */ 12, 13, 14, 258, 153, 17, 155, 153, 23, 155,
- /* 1670 */ 25, 36, 23, 193, 25, 255, 193, 236, 30, 193,
- /* 1680 */ 32, 19, 20, 193, 22, 193, 288, 117, 40, 193,
- /* 1690 */ 318, 193, 193, 193, 59, 242, 193, 193, 36, 193,
- /* 1700 */ 193, 193, 287, 255, 255, 255, 71, 255, 243, 214,
- /* 1710 */ 191, 297, 267, 245, 271, 259, 259, 293, 70, 246,
- /* 1720 */ 246, 59, 267, 229, 245, 271, 78, 293, 259, 81,
- /* 1730 */ 271, 271, 220, 71, 225, 100, 219, 219, 249, 196,
- /* 1740 */ 243, 106, 107, 108, 219, 60, 98, 280, 297, 114,
- /* 1750 */ 249, 116, 117, 118, 141, 245, 121, 200, 200, 297,
- /* 1760 */ 38, 200, 100, 151, 150, 294, 294, 22, 106, 107,
- /* 1770 */ 283, 148, 250, 145, 43, 249, 114, 272, 116, 117,
- /* 1780 */ 118, 133, 234, 121, 250, 249, 138, 139, 153, 154,
- /* 1790 */ 155, 156, 157, 18, 270, 199, 19, 20, 18, 22,
- /* 1800 */ 237, 237, 237, 200, 237, 149, 246, 272, 246, 272,
- /* 1810 */ 162, 270, 234, 36, 234, 153, 154, 155, 156, 157,
- /* 1820 */ 246, 246, 200, 199, 158, 62, 200, 22, 290, 289,
- /* 1830 */ 199, 221, 200, 199, 115, 199, 59, 200, 218, 218,
- /* 1840 */ 221, 22, 64, 218, 126, 165, 24, 113, 71, 305,
- /* 1850 */ 144, 221, 261, 224, 227, 282, 218, 224, 218, 220,
- /* 1860 */ 218, 218, 312, 282, 115, 221, 260, 260, 227, 261,
- /* 1870 */ 261, 260, 200, 91, 82, 22, 261, 100, 277, 200,
- /* 1880 */ 147, 265, 158, 106, 107, 146, 317, 25, 317, 202,
- /* 1890 */ 13, 114, 194, 116, 117, 118, 194, 6, 121, 248,
- /* 1900 */ 250, 249, 247, 250, 246, 192, 260, 192, 192, 213,
- /* 1910 */ 207, 222, 265, 213, 213, 207, 222, 213, 207, 214,
- /* 1920 */ 4, 214, 213, 3, 22, 163, 15, 23, 16, 23,
- /* 1930 */ 153, 154, 155, 156, 157, 139, 279, 151, 130, 25,
- /* 1940 */ 142, 24, 20, 144, 16, 1, 142, 130, 130, 61,
- /* 1950 */ 37, 151, 53, 53, 53, 130, 53, 303, 116, 34,
- /* 1960 */ 300, 141, 303, 1, 5, 22, 115, 161, 75, 141,
- /* 1970 */ 25, 41, 115, 68, 24, 68, 20, 19, 131, 125,
- /* 1980 */ 23, 96, 22, 22, 37, 67, 22, 22, 67, 149,
- /* 1990 */ 59, 24, 22, 67, 28, 23, 22, 141, 23, 23,
- /* 2000 */ 23, 143, 23, 25, 22, 97, 23, 23, 116, 22,
- /* 2010 */ 25, 88, 75, 86, 34, 34, 75, 44, 23, 22,
- /* 2020 */ 34, 25, 34, 34, 34, 93, 24, 34, 25, 23,
- /* 2030 */ 23, 23, 23, 23, 11, 23, 25, 22, 22, 22,
- /* 2040 */ 1, 23, 23, 22, 22, 25, 15, 1, 23, 25,
- /* 2050 */ 135, 141, 319, 319, 319, 319, 319, 319, 319, 319,
- /* 2060 */ 141, 319, 319, 319, 319, 141, 319, 319, 319, 319,
- /* 2070 */ 141, 319, 319, 319, 319, 319, 319, 319, 319, 319,
- /* 2080 */ 319, 319, 319, 319, 319, 319, 319, 319, 319, 319,
- /* 2090 */ 319, 319, 319, 319, 319, 319, 319, 319, 319, 319,
- /* 2100 */ 319, 319, 319, 319, 319, 319, 319, 319, 319, 319,
+ /* 1330 */ 42, 150, 291, 216, 217, 116, 117, 118, 19, 20,
+ /* 1340 */ 193, 22, 70, 260, 116, 193, 24, 264, 193, 263,
+ /* 1350 */ 78, 63, 61, 81, 116, 36, 193, 260, 193, 29,
+ /* 1360 */ 193, 264, 193, 33, 145, 193, 59, 48, 216, 217,
+ /* 1370 */ 98, 216, 217, 193, 115, 193, 115, 193, 59, 216,
+ /* 1380 */ 217, 216, 217, 216, 217, 216, 217, 255, 216, 217,
+ /* 1390 */ 71, 193, 131, 193, 25, 65, 216, 217, 216, 217,
+ /* 1400 */ 216, 217, 208, 209, 85, 133, 193, 100, 193, 90,
+ /* 1410 */ 138, 139, 138, 139, 216, 217, 216, 217, 193, 100,
+ /* 1420 */ 193, 108, 135, 116, 117, 106, 107, 140, 121, 216,
+ /* 1430 */ 217, 216, 217, 114, 162, 116, 117, 118, 299, 300,
+ /* 1440 */ 121, 216, 217, 216, 217, 193, 244, 193, 135, 244,
+ /* 1450 */ 193, 256, 257, 140, 244, 193, 254, 193, 193, 254,
+ /* 1460 */ 153, 154, 155, 141, 254, 149, 150, 258, 216, 217,
+ /* 1470 */ 216, 217, 153, 154, 155, 156, 157, 0, 1, 2,
+ /* 1480 */ 216, 217, 5, 115, 158, 193, 160, 10, 11, 12,
+ /* 1490 */ 13, 14, 193, 59, 17, 126, 193, 19, 20, 129,
+ /* 1500 */ 22, 193, 22, 22, 24, 193, 23, 30, 25, 32,
+ /* 1510 */ 19, 20, 144, 22, 36, 216, 217, 40, 193, 216,
+ /* 1520 */ 217, 193, 152, 129, 216, 217, 193, 36, 216, 217,
+ /* 1530 */ 193, 99, 193, 193, 53, 193, 193, 59, 23, 193,
+ /* 1540 */ 25, 216, 217, 193, 216, 217, 152, 70, 59, 71,
+ /* 1550 */ 59, 117, 193, 216, 217, 78, 216, 217, 81, 216,
+ /* 1560 */ 217, 318, 71, 85, 193, 133, 193, 193, 90, 23,
+ /* 1570 */ 23, 25, 25, 120, 121, 98, 85, 193, 100, 193,
+ /* 1580 */ 23, 90, 25, 121, 106, 107, 19, 216, 217, 216,
+ /* 1590 */ 217, 100, 114, 131, 116, 117, 118, 106, 107, 121,
+ /* 1600 */ 216, 217, 216, 217, 193, 114, 117, 116, 117, 118,
+ /* 1610 */ 133, 193, 121, 193, 193, 138, 139, 193, 23, 193,
+ /* 1620 */ 25, 23, 23, 25, 25, 7, 8, 216, 217, 193,
+ /* 1630 */ 193, 153, 154, 155, 156, 157, 216, 217, 193, 162,
+ /* 1640 */ 216, 217, 216, 217, 153, 154, 155, 156, 157, 1,
+ /* 1650 */ 2, 193, 193, 5, 19, 20, 59, 22, 10, 11,
+ /* 1660 */ 12, 13, 14, 193, 97, 17, 193, 23, 193, 25,
+ /* 1670 */ 288, 36, 193, 242, 216, 217, 236, 23, 30, 25,
+ /* 1680 */ 32, 19, 20, 23, 22, 25, 216, 217, 40, 216,
+ /* 1690 */ 217, 216, 217, 193, 59, 216, 217, 193, 36, 83,
+ /* 1700 */ 84, 153, 153, 155, 155, 23, 71, 25, 23, 193,
+ /* 1710 */ 25, 193, 193, 193, 117, 193, 193, 193, 70, 193,
+ /* 1720 */ 193, 59, 193, 255, 255, 287, 78, 255, 243, 81,
+ /* 1730 */ 191, 255, 297, 71, 271, 100, 293, 245, 267, 214,
+ /* 1740 */ 246, 106, 107, 108, 246, 271, 98, 245, 293, 114,
+ /* 1750 */ 220, 116, 117, 118, 267, 271, 121, 271, 225, 219,
+ /* 1760 */ 229, 219, 100, 219, 259, 259, 259, 259, 106, 107,
+ /* 1770 */ 249, 196, 60, 280, 141, 243, 114, 249, 116, 117,
+ /* 1780 */ 118, 133, 245, 121, 200, 297, 138, 139, 153, 154,
+ /* 1790 */ 155, 156, 157, 297, 200, 38, 19, 20, 151, 22,
+ /* 1800 */ 200, 150, 140, 294, 294, 22, 272, 148, 250, 145,
+ /* 1810 */ 162, 270, 249, 36, 43, 153, 154, 155, 156, 157,
+ /* 1820 */ 234, 283, 250, 249, 18, 237, 237, 237, 237, 200,
+ /* 1830 */ 18, 199, 149, 246, 272, 234, 59, 272, 246, 270,
+ /* 1840 */ 234, 200, 246, 290, 199, 158, 62, 22, 71, 246,
+ /* 1850 */ 289, 221, 200, 200, 199, 199, 19, 20, 200, 22,
+ /* 1860 */ 199, 221, 115, 64, 218, 227, 22, 218, 218, 126,
+ /* 1870 */ 227, 165, 312, 36, 24, 305, 113, 100, 200, 91,
+ /* 1880 */ 82, 224, 22, 106, 107, 218, 221, 224, 220, 218,
+ /* 1890 */ 218, 114, 218, 116, 117, 118, 59, 282, 121, 282,
+ /* 1900 */ 221, 265, 317, 277, 265, 200, 158, 279, 71, 147,
+ /* 1910 */ 317, 146, 25, 202, 13, 194, 250, 140, 249, 194,
+ /* 1920 */ 250, 248, 247, 246, 6, 192, 192, 192, 303, 207,
+ /* 1930 */ 153, 154, 155, 156, 157, 222, 213, 100, 207, 213,
+ /* 1940 */ 303, 213, 300, 106, 107, 213, 222, 207, 4, 3,
+ /* 1950 */ 214, 114, 214, 116, 117, 118, 22, 213, 121, 163,
+ /* 1960 */ 15, 23, 23, 16, 151, 139, 130, 25, 144, 142,
+ /* 1970 */ 20, 16, 24, 1, 142, 130, 130, 61, 37, 151,
+ /* 1980 */ 53, 53, 53, 53, 130, 116, 34, 1, 141, 5,
+ /* 1990 */ 153, 154, 155, 156, 157, 22, 115, 75, 41, 161,
+ /* 2000 */ 68, 25, 141, 68, 115, 24, 131, 20, 19, 125,
+ /* 2010 */ 22, 96, 22, 22, 37, 23, 67, 22, 67, 59,
+ /* 2020 */ 24, 28, 22, 67, 23, 149, 22, 25, 23, 23,
+ /* 2030 */ 23, 23, 22, 97, 141, 23, 23, 116, 22, 143,
+ /* 2040 */ 25, 88, 75, 34, 86, 34, 75, 44, 34, 23,
+ /* 2050 */ 22, 34, 34, 34, 93, 24, 34, 25, 25, 142,
+ /* 2060 */ 23, 142, 23, 23, 23, 23, 11, 23, 25, 22,
+ /* 2070 */ 22, 22, 135, 23, 23, 22, 22, 25, 15, 23,
+ /* 2080 */ 1, 141, 25, 1, 141, 319, 319, 319, 319, 319,
+ /* 2090 */ 319, 319, 319, 319, 319, 141, 319, 319, 319, 319,
+ /* 2100 */ 141, 319, 319, 319, 319, 319, 319, 319, 319, 319,
/* 2110 */ 319, 319, 319, 319, 319, 319, 319, 319, 319, 319,
/* 2120 */ 319, 319, 319, 319, 319, 319, 319, 319, 319, 319,
/* 2130 */ 319, 319, 319, 319, 319, 319, 319, 319, 319, 319,
@@ -162433,177 +166348,180 @@ static const YYCODETYPE yy_lookahead[] = {
/* 2220 */ 319, 319, 319, 319, 319, 319, 319, 319, 319, 319,
/* 2230 */ 319, 319, 319, 319, 319, 319, 319, 319, 319, 319,
/* 2240 */ 319, 319, 319, 319, 319, 319, 319, 319, 319, 319,
- /* 2250 */ 319, 319, 319, 319, 319, 319,
+ /* 2250 */ 319, 319, 319, 319, 319, 319, 319, 319, 319, 319,
+ /* 2260 */ 319, 319, 319, 319, 319, 319, 319, 319, 319, 319,
+ /* 2270 */ 319, 319, 319, 319, 319, 319, 319, 319, 319, 319,
+ /* 2280 */ 319, 319, 319, 319, 319, 319,
};
-#define YY_SHIFT_COUNT (577)
+#define YY_SHIFT_COUNT (579)
#define YY_SHIFT_MIN (0)
-#define YY_SHIFT_MAX (2046)
+#define YY_SHIFT_MAX (2082)
static const unsigned short int yy_shift_ofst[] = {
- /* 0 */ 1648, 1477, 1272, 322, 322, 262, 1319, 1478, 1491, 1662,
- /* 10 */ 1662, 1662, 317, 0, 0, 214, 1093, 1662, 1662, 1662,
- /* 20 */ 1662, 1662, 1662, 1662, 1662, 1662, 1662, 1662, 1662, 1662,
- /* 30 */ 271, 271, 1219, 1219, 216, 88, 262, 262, 262, 262,
- /* 40 */ 262, 40, 111, 258, 361, 469, 512, 583, 622, 693,
+ /* 0 */ 1648, 1477, 1272, 322, 322, 1, 1319, 1478, 1491, 1837,
+ /* 10 */ 1837, 1837, 471, 0, 0, 214, 1093, 1837, 1837, 1837,
+ /* 20 */ 1837, 1837, 1837, 1837, 1837, 1837, 1837, 1837, 1837, 1837,
+ /* 30 */ 271, 271, 1219, 1219, 216, 88, 1, 1, 1, 1,
+ /* 40 */ 1, 40, 111, 258, 361, 469, 512, 583, 622, 693,
/* 50 */ 732, 803, 842, 913, 1073, 1093, 1093, 1093, 1093, 1093,
/* 60 */ 1093, 1093, 1093, 1093, 1093, 1093, 1093, 1093, 1093, 1093,
/* 70 */ 1093, 1093, 1093, 1113, 1093, 1216, 957, 957, 1635, 1662,
- /* 80 */ 1662, 1662, 1662, 1662, 1662, 1662, 1662, 1662, 1662, 1662,
- /* 90 */ 1662, 1662, 1662, 1662, 1662, 1662, 1662, 1662, 1662, 1662,
- /* 100 */ 1662, 1662, 1662, 1662, 1662, 1662, 1662, 1662, 1662, 1662,
- /* 110 */ 1662, 1662, 1662, 1662, 1777, 1662, 1662, 1662, 1662, 1662,
- /* 120 */ 1662, 1662, 1662, 1662, 1662, 1662, 1662, 1662, 137, 181,
- /* 130 */ 181, 181, 181, 181, 94, 430, 66, 65, 112, 366,
- /* 140 */ 475, 475, 629, 1058, 475, 475, 125, 125, 475, 686,
- /* 150 */ 686, 686, 660, 686, 57, 184, 184, 77, 77, 2071,
- /* 160 */ 2071, 328, 328, 328, 493, 373, 373, 373, 373, 1015,
- /* 170 */ 1015, 409, 366, 1129, 1149, 475, 475, 475, 475, 475,
- /* 180 */ 475, 475, 475, 475, 475, 475, 475, 475, 475, 475,
- /* 190 */ 475, 475, 475, 475, 475, 621, 621, 475, 852, 899,
- /* 200 */ 899, 1295, 1295, 406, 851, 2071, 2071, 2071, 2071, 2071,
- /* 210 */ 2071, 2071, 1307, 954, 954, 640, 464, 695, 238, 700,
- /* 220 */ 538, 541, 748, 475, 475, 475, 475, 475, 475, 475,
- /* 230 */ 475, 475, 475, 634, 475, 475, 475, 475, 475, 475,
- /* 240 */ 475, 475, 475, 475, 475, 475, 1175, 1175, 1175, 475,
- /* 250 */ 475, 475, 580, 475, 475, 475, 1074, 1142, 475, 475,
- /* 260 */ 1072, 475, 475, 475, 475, 475, 475, 475, 475, 797,
- /* 270 */ 1330, 740, 1131, 1131, 1131, 1131, 1069, 740, 740, 1209,
- /* 280 */ 167, 926, 1391, 1038, 1314, 187, 1408, 1314, 1408, 1435,
- /* 290 */ 1109, 1038, 1038, 1109, 1038, 187, 1435, 227, 1090, 941,
- /* 300 */ 1270, 1270, 1270, 1408, 1256, 1256, 1326, 1440, 513, 1461,
- /* 310 */ 1685, 1685, 1613, 1613, 1722, 1722, 1613, 1612, 1614, 1745,
- /* 320 */ 1623, 1628, 1731, 1623, 1628, 1775, 1775, 1775, 1775, 1613,
- /* 330 */ 1780, 1656, 1614, 1614, 1656, 1745, 1731, 1656, 1731, 1656,
- /* 340 */ 1613, 1780, 1666, 1763, 1613, 1780, 1805, 1613, 1780, 1613,
- /* 350 */ 1780, 1805, 1719, 1719, 1719, 1778, 1819, 1819, 1805, 1719,
- /* 360 */ 1718, 1719, 1778, 1719, 1719, 1680, 1822, 1734, 1734, 1805,
- /* 370 */ 1706, 1749, 1706, 1749, 1706, 1749, 1706, 1749, 1613, 1782,
- /* 380 */ 1782, 1792, 1792, 1623, 1628, 1853, 1613, 1724, 1623, 1733,
- /* 390 */ 1739, 1656, 1862, 1877, 1877, 1891, 1891, 1891, 2071, 2071,
- /* 400 */ 2071, 2071, 2071, 2071, 2071, 2071, 2071, 2071, 2071, 2071,
- /* 410 */ 2071, 2071, 2071, 207, 915, 1010, 1030, 1217, 910, 1170,
- /* 420 */ 1470, 1368, 1481, 1442, 1318, 1383, 1515, 1482, 1523, 1542,
- /* 430 */ 1546, 1547, 1588, 1595, 1502, 1338, 1566, 1493, 1520, 1521,
- /* 440 */ 1598, 1617, 1568, 1618, 1511, 1514, 1645, 1649, 1570, 1484,
- /* 450 */ 1916, 1920, 1902, 1762, 1911, 1912, 1904, 1906, 1796, 1786,
- /* 460 */ 1808, 1914, 1914, 1917, 1798, 1922, 1799, 1928, 1944, 1804,
- /* 470 */ 1817, 1914, 1818, 1888, 1913, 1914, 1800, 1899, 1900, 1901,
- /* 480 */ 1903, 1825, 1842, 1925, 1820, 1962, 1959, 1943, 1851, 1806,
- /* 490 */ 1905, 1945, 1907, 1893, 1930, 1828, 1857, 1950, 1956, 1958,
- /* 500 */ 1847, 1854, 1960, 1918, 1961, 1964, 1957, 1965, 1921, 1931,
- /* 510 */ 1967, 1885, 1966, 1970, 1926, 1947, 1972, 1840, 1974, 1975,
- /* 520 */ 1976, 1977, 1978, 1979, 1982, 1908, 1856, 1983, 1984, 1892,
- /* 530 */ 1980, 1987, 1858, 1985, 1981, 1986, 1988, 1989, 1923, 1937,
- /* 540 */ 1927, 1973, 1941, 1932, 1990, 1995, 1997, 2002, 1996, 2003,
- /* 550 */ 1993, 2006, 1985, 2007, 2008, 2009, 2010, 2011, 2012, 2015,
- /* 560 */ 2023, 2016, 2017, 2018, 2019, 2021, 2022, 2020, 1915, 1910,
- /* 570 */ 1919, 1924, 1929, 2024, 2025, 2031, 2039, 2046,
+ /* 80 */ 1777, 1837, 1837, 1837, 1837, 1837, 1837, 1837, 1837, 1837,
+ /* 90 */ 1837, 1837, 1837, 1837, 1837, 1837, 1837, 1837, 1837, 1837,
+ /* 100 */ 1837, 1837, 1837, 1837, 1837, 1837, 1837, 1837, 1837, 1837,
+ /* 110 */ 1837, 1837, 1837, 1837, 1837, 1837, 1837, 1837, 1837, 1837,
+ /* 120 */ 1837, 1837, 1837, 1837, 1837, 1837, 1837, 1837, 1837, 1837,
+ /* 130 */ 137, 181, 181, 181, 181, 181, 181, 181, 94, 430,
+ /* 140 */ 66, 65, 112, 366, 533, 533, 740, 1261, 533, 533,
+ /* 150 */ 79, 79, 533, 412, 412, 412, 77, 412, 123, 113,
+ /* 160 */ 113, 22, 22, 2101, 2101, 328, 328, 328, 239, 468,
+ /* 170 */ 468, 468, 468, 1015, 1015, 409, 366, 1129, 1186, 533,
+ /* 180 */ 533, 533, 533, 533, 533, 533, 533, 533, 533, 533,
+ /* 190 */ 533, 533, 533, 533, 533, 533, 533, 533, 533, 969,
+ /* 200 */ 621, 621, 533, 642, 788, 788, 1228, 1228, 822, 822,
+ /* 210 */ 67, 1274, 2101, 2101, 2101, 2101, 2101, 2101, 2101, 1307,
+ /* 220 */ 954, 954, 585, 472, 640, 387, 695, 538, 541, 700,
+ /* 230 */ 533, 533, 533, 533, 533, 533, 533, 533, 533, 533,
+ /* 240 */ 222, 533, 533, 533, 533, 533, 533, 533, 533, 533,
+ /* 250 */ 533, 533, 533, 1179, 1179, 1179, 533, 533, 533, 565,
+ /* 260 */ 533, 533, 533, 916, 1144, 533, 533, 1288, 533, 533,
+ /* 270 */ 533, 533, 533, 533, 533, 533, 639, 1330, 209, 1076,
+ /* 280 */ 1076, 1076, 1076, 580, 209, 209, 1313, 768, 917, 649,
+ /* 290 */ 1181, 1316, 405, 1316, 1238, 249, 1181, 1181, 249, 1181,
+ /* 300 */ 405, 1238, 1369, 464, 1259, 1012, 1012, 1012, 1368, 1368,
+ /* 310 */ 1368, 1368, 184, 184, 1326, 904, 1287, 1480, 1712, 1712,
+ /* 320 */ 1633, 1633, 1757, 1757, 1633, 1647, 1651, 1783, 1659, 1664,
+ /* 330 */ 1771, 1659, 1664, 1806, 1806, 1806, 1806, 1633, 1812, 1683,
+ /* 340 */ 1651, 1651, 1683, 1783, 1771, 1683, 1771, 1683, 1633, 1812,
+ /* 350 */ 1687, 1784, 1633, 1812, 1825, 1633, 1812, 1633, 1812, 1825,
+ /* 360 */ 1747, 1747, 1747, 1799, 1844, 1844, 1825, 1747, 1743, 1747,
+ /* 370 */ 1799, 1747, 1747, 1706, 1850, 1763, 1763, 1825, 1633, 1788,
+ /* 380 */ 1788, 1798, 1798, 1659, 1664, 1860, 1633, 1748, 1659, 1762,
+ /* 390 */ 1765, 1683, 1887, 1901, 1901, 1918, 1918, 1918, 2101, 2101,
+ /* 400 */ 2101, 2101, 2101, 2101, 2101, 2101, 2101, 2101, 2101, 2101,
+ /* 410 */ 2101, 2101, 2101, 207, 1095, 331, 620, 903, 806, 1074,
+ /* 420 */ 1483, 1432, 1481, 1322, 1370, 1394, 1515, 1291, 1546, 1547,
+ /* 430 */ 1557, 1595, 1598, 1599, 1434, 1453, 1618, 1462, 1567, 1489,
+ /* 440 */ 1644, 1654, 1616, 1660, 1548, 1549, 1682, 1685, 1597, 742,
+ /* 450 */ 1944, 1946, 1934, 1796, 1945, 1947, 1938, 1939, 1826, 1813,
+ /* 460 */ 1836, 1942, 1942, 1948, 1827, 1950, 1824, 1955, 1972, 1832,
+ /* 470 */ 1845, 1942, 1846, 1916, 1941, 1942, 1828, 1927, 1928, 1929,
+ /* 480 */ 1930, 1854, 1869, 1952, 1847, 1986, 1984, 1973, 1881, 1838,
+ /* 490 */ 1932, 1976, 1935, 1922, 1957, 1861, 1889, 1981, 1987, 1989,
+ /* 500 */ 1875, 1884, 1988, 1949, 1990, 1991, 1992, 1995, 1951, 1960,
+ /* 510 */ 1996, 1915, 1993, 2000, 1956, 1977, 2001, 1876, 2004, 2005,
+ /* 520 */ 2006, 2007, 2002, 2008, 2010, 1936, 1893, 2012, 2013, 1921,
+ /* 530 */ 2009, 2016, 1896, 2015, 2011, 2014, 2017, 2018, 1953, 1967,
+ /* 540 */ 1958, 2003, 1971, 1961, 2019, 2026, 2028, 2031, 2032, 2033,
+ /* 550 */ 2022, 1917, 1919, 2037, 2015, 2039, 2040, 2041, 2042, 2043,
+ /* 560 */ 2044, 2047, 2055, 2048, 2049, 2050, 2051, 2053, 2054, 2052,
+ /* 570 */ 1937, 1940, 1943, 1954, 1959, 2057, 2056, 2063, 2079, 2082,
};
#define YY_REDUCE_COUNT (412)
-#define YY_REDUCE_MIN (-267)
-#define YY_REDUCE_MAX (1716)
+#define YY_REDUCE_MIN (-271)
+#define YY_REDUCE_MAX (1744)
static const short yy_reduce_ofst[] = {
/* 0 */ -125, 733, 789, 241, 293, -123, -193, -191, -183, -187,
- /* 10 */ -180, 83, 133, -207, -198, -267, -175, -6, 166, 313,
- /* 20 */ 487, 396, 489, 598, 615, 685, 687, 79, 781, 857,
- /* 30 */ 490, 616, 240, 334, -188, 796, 841, 843, 1003, 1005,
- /* 40 */ 1007, -260, -260, -260, -260, -260, -260, -260, -260, -260,
- /* 50 */ -260, -260, -260, -260, -260, -260, -260, -260, -260, -260,
- /* 60 */ -260, -260, -260, -260, -260, -260, -260, -260, -260, -260,
- /* 70 */ -260, -260, -260, -260, -260, -260, -260, -260, 158, 203,
- /* 80 */ 391, 576, 724, 726, 886, 1021, 1035, 1063, 1081, 1083,
- /* 90 */ 1097, 1099, 1117, 1152, 1155, 1158, 1163, 1165, 1167, 1169,
- /* 100 */ 1172, 1180, 1183, 1198, 1200, 1205, 1215, 1225, 1227, 1236,
- /* 110 */ 1252, 1264, 1299, 1303, 1306, 1309, 1312, 1315, 1325, 1328,
- /* 120 */ 1337, 1340, 1343, 1371, 1373, 1384, 1386, 1411, -260, -260,
- /* 130 */ -260, -260, -260, -260, -260, -260, -260, -53, 138, 302,
- /* 140 */ -158, 357, 223, -222, 411, 458, -92, 556, 669, 581,
- /* 150 */ 632, 581, -260, 632, 758, 778, 920, -260, -260, -260,
- /* 160 */ -260, 161, 161, 161, 307, 234, 392, 526, 790, 195,
- /* 170 */ 359, -174, -173, 362, 362, -189, 16, 560, 567, 261,
- /* 180 */ 689, 802, 853, -122, -166, 408, 335, 617, 690, 837,
- /* 190 */ 1001, 746, 1061, 515, 1082, 994, 1034, -135, 1000, 1048,
- /* 200 */ 1137, 877, 897, 186, 627, 1031, 1133, 1148, 1159, 1194,
- /* 210 */ 1199, 1195, -194, -142, 18, -152, 68, 201, 253, 269,
- /* 220 */ 294, 354, 521, 528, 676, 680, 736, 743, 850, 907,
- /* 230 */ 1041, 1047, 1060, 727, 1139, 1147, 1201, 1237, 1278, 1359,
- /* 240 */ 1393, 1400, 1413, 1429, 1433, 1437, 1126, 1410, 1430, 1444,
- /* 250 */ 1480, 1483, 1405, 1486, 1490, 1492, 1420, 1372, 1496, 1498,
- /* 260 */ 1441, 1499, 253, 1500, 1503, 1504, 1506, 1507, 1508, 1398,
- /* 270 */ 1415, 1453, 1448, 1449, 1450, 1452, 1405, 1453, 1453, 1465,
- /* 280 */ 1495, 1519, 1414, 1443, 1445, 1468, 1456, 1455, 1457, 1424,
- /* 290 */ 1473, 1454, 1459, 1474, 1460, 1479, 1434, 1512, 1494, 1509,
- /* 300 */ 1517, 1518, 1525, 1469, 1489, 1501, 1467, 1510, 1497, 1543,
- /* 310 */ 1451, 1462, 1557, 1558, 1471, 1472, 1561, 1487, 1505, 1524,
- /* 320 */ 1522, 1526, 1548, 1534, 1536, 1563, 1564, 1565, 1567, 1603,
- /* 330 */ 1596, 1560, 1535, 1537, 1562, 1541, 1578, 1574, 1580, 1575,
- /* 340 */ 1622, 1624, 1538, 1540, 1626, 1631, 1610, 1632, 1634, 1637,
- /* 350 */ 1636, 1619, 1620, 1621, 1625, 1627, 1629, 1633, 1630, 1638,
- /* 360 */ 1639, 1640, 1641, 1642, 1643, 1550, 1544, 1573, 1581, 1644,
- /* 370 */ 1591, 1606, 1608, 1607, 1609, 1611, 1615, 1646, 1672, 1569,
- /* 380 */ 1571, 1616, 1647, 1650, 1652, 1601, 1679, 1657, 1653, 1651,
- /* 390 */ 1655, 1658, 1687, 1698, 1702, 1713, 1715, 1716, 1654, 1659,
- /* 400 */ 1660, 1703, 1696, 1700, 1701, 1704, 1708, 1689, 1694, 1705,
- /* 410 */ 1707, 1709, 1711,
+ /* 10 */ 166, 238, 133, -207, -199, -267, -176, -6, 204, 489,
+ /* 20 */ 576, -175, 598, 686, 615, 725, 860, 778, 781, 857,
+ /* 30 */ 616, 887, 87, 240, -192, 408, 626, 796, 843, 854,
+ /* 40 */ 1003, -271, -271, -271, -271, -271, -271, -271, -271, -271,
+ /* 50 */ -271, -271, -271, -271, -271, -271, -271, -271, -271, -271,
+ /* 60 */ -271, -271, -271, -271, -271, -271, -271, -271, -271, -271,
+ /* 70 */ -271, -271, -271, -271, -271, -271, -271, -271, 80, 83,
+ /* 80 */ 313, 886, 888, 996, 1034, 1059, 1081, 1100, 1117, 1152,
+ /* 90 */ 1155, 1163, 1165, 1167, 1169, 1172, 1180, 1182, 1184, 1198,
+ /* 100 */ 1200, 1213, 1215, 1225, 1227, 1252, 1254, 1264, 1299, 1303,
+ /* 110 */ 1308, 1312, 1325, 1328, 1337, 1340, 1343, 1371, 1373, 1384,
+ /* 120 */ 1386, 1411, 1420, 1424, 1426, 1458, 1470, 1473, 1475, 1479,
+ /* 130 */ -271, -271, -271, -271, -271, -271, -271, -271, -271, -271,
+ /* 140 */ -271, 138, 459, 396, -158, 470, 302, -212, 521, 201,
+ /* 150 */ -195, -92, 559, 630, 632, 630, -271, 632, 901, 63,
+ /* 160 */ 407, -271, -271, -271, -271, 161, 161, 161, 251, 335,
+ /* 170 */ 847, 960, 980, 537, 588, 618, 628, 688, 688, -166,
+ /* 180 */ -161, 674, 790, 794, 799, 851, 852, -122, 680, -120,
+ /* 190 */ 995, 1038, 415, 1051, 893, 798, 962, 400, 1086, 779,
+ /* 200 */ 923, 924, 263, 1041, 979, 990, 1083, 1097, 1031, 1194,
+ /* 210 */ 362, 994, 1139, 1005, 1037, 1202, 1205, 1195, 1210, -194,
+ /* 220 */ 56, 185, -135, 232, 522, 560, 601, 617, 669, 683,
+ /* 230 */ 711, 856, 908, 941, 1048, 1101, 1147, 1257, 1262, 1265,
+ /* 240 */ 392, 1292, 1333, 1339, 1342, 1346, 1350, 1359, 1374, 1418,
+ /* 250 */ 1421, 1436, 1437, 593, 755, 770, 997, 1445, 1459, 1209,
+ /* 260 */ 1500, 1504, 1516, 1132, 1243, 1518, 1519, 1440, 1520, 560,
+ /* 270 */ 1522, 1523, 1524, 1526, 1527, 1529, 1382, 1438, 1431, 1468,
+ /* 280 */ 1469, 1472, 1476, 1209, 1431, 1431, 1485, 1525, 1539, 1435,
+ /* 290 */ 1463, 1471, 1492, 1487, 1443, 1494, 1474, 1484, 1498, 1486,
+ /* 300 */ 1502, 1455, 1530, 1531, 1533, 1540, 1542, 1544, 1505, 1506,
+ /* 310 */ 1507, 1508, 1521, 1528, 1493, 1537, 1532, 1575, 1488, 1496,
+ /* 320 */ 1584, 1594, 1509, 1510, 1600, 1538, 1534, 1541, 1558, 1563,
+ /* 330 */ 1586, 1572, 1574, 1588, 1589, 1590, 1591, 1629, 1632, 1587,
+ /* 340 */ 1562, 1565, 1592, 1569, 1601, 1596, 1606, 1603, 1641, 1645,
+ /* 350 */ 1553, 1561, 1652, 1655, 1630, 1653, 1656, 1658, 1661, 1640,
+ /* 360 */ 1646, 1649, 1650, 1638, 1657, 1663, 1665, 1667, 1668, 1671,
+ /* 370 */ 1643, 1672, 1674, 1560, 1570, 1615, 1617, 1679, 1678, 1585,
+ /* 380 */ 1593, 1636, 1639, 1666, 1669, 1626, 1705, 1628, 1670, 1673,
+ /* 390 */ 1675, 1677, 1711, 1721, 1725, 1733, 1734, 1735, 1625, 1637,
+ /* 400 */ 1642, 1722, 1723, 1726, 1728, 1732, 1731, 1713, 1724, 1736,
+ /* 410 */ 1738, 1744, 1740,
};
static const YYACTIONTYPE yy_default[] = {
- /* 0 */ 1641, 1641, 1641, 1470, 1237, 1348, 1237, 1237, 1237, 1470,
- /* 10 */ 1470, 1470, 1237, 1378, 1378, 1523, 1270, 1237, 1237, 1237,
- /* 20 */ 1237, 1237, 1237, 1237, 1237, 1237, 1237, 1469, 1237, 1237,
- /* 30 */ 1237, 1237, 1558, 1558, 1237, 1237, 1237, 1237, 1237, 1237,
- /* 40 */ 1237, 1237, 1387, 1237, 1394, 1237, 1237, 1237, 1237, 1237,
- /* 50 */ 1471, 1472, 1237, 1237, 1237, 1522, 1524, 1487, 1401, 1400,
- /* 60 */ 1399, 1398, 1505, 1365, 1392, 1385, 1389, 1465, 1466, 1464,
- /* 70 */ 1468, 1472, 1471, 1237, 1388, 1435, 1449, 1434, 1237, 1237,
- /* 80 */ 1237, 1237, 1237, 1237, 1237, 1237, 1237, 1237, 1237, 1237,
- /* 90 */ 1237, 1237, 1237, 1237, 1237, 1237, 1237, 1237, 1237, 1237,
- /* 100 */ 1237, 1237, 1237, 1237, 1237, 1237, 1237, 1237, 1237, 1237,
- /* 110 */ 1237, 1237, 1237, 1237, 1237, 1237, 1237, 1237, 1237, 1237,
- /* 120 */ 1237, 1237, 1237, 1237, 1237, 1237, 1237, 1237, 1443, 1448,
- /* 130 */ 1455, 1447, 1444, 1437, 1436, 1438, 1439, 1237, 1237, 1261,
- /* 140 */ 1237, 1237, 1258, 1312, 1237, 1237, 1237, 1237, 1237, 1542,
- /* 150 */ 1541, 1237, 1440, 1237, 1270, 1429, 1428, 1452, 1441, 1451,
- /* 160 */ 1450, 1530, 1594, 1593, 1488, 1237, 1237, 1237, 1237, 1237,
- /* 170 */ 1237, 1558, 1237, 1237, 1237, 1237, 1237, 1237, 1237, 1237,
- /* 180 */ 1237, 1237, 1237, 1237, 1237, 1237, 1237, 1237, 1237, 1237,
- /* 190 */ 1237, 1237, 1237, 1237, 1237, 1558, 1558, 1237, 1270, 1558,
- /* 200 */ 1558, 1266, 1266, 1372, 1237, 1537, 1339, 1339, 1339, 1339,
- /* 210 */ 1348, 1339, 1237, 1237, 1237, 1237, 1237, 1237, 1237, 1237,
- /* 220 */ 1237, 1237, 1237, 1237, 1237, 1237, 1237, 1527, 1525, 1237,
- /* 230 */ 1237, 1237, 1237, 1237, 1237, 1237, 1237, 1237, 1237, 1237,
- /* 240 */ 1237, 1237, 1237, 1237, 1237, 1237, 1237, 1237, 1237, 1237,
- /* 250 */ 1237, 1237, 1237, 1237, 1237, 1237, 1344, 1237, 1237, 1237,
- /* 260 */ 1237, 1237, 1237, 1237, 1237, 1237, 1237, 1237, 1587, 1237,
- /* 270 */ 1500, 1326, 1344, 1344, 1344, 1344, 1346, 1327, 1325, 1338,
- /* 280 */ 1271, 1244, 1633, 1404, 1393, 1345, 1367, 1393, 1367, 1630,
- /* 290 */ 1391, 1404, 1404, 1391, 1404, 1345, 1630, 1287, 1610, 1282,
- /* 300 */ 1378, 1378, 1378, 1367, 1372, 1372, 1467, 1345, 1338, 1237,
- /* 310 */ 1633, 1633, 1353, 1353, 1632, 1632, 1353, 1488, 1617, 1413,
- /* 320 */ 1386, 1372, 1315, 1386, 1372, 1321, 1321, 1321, 1321, 1353,
- /* 330 */ 1255, 1391, 1617, 1617, 1391, 1413, 1315, 1391, 1315, 1391,
- /* 340 */ 1353, 1255, 1504, 1627, 1353, 1255, 1478, 1353, 1255, 1353,
- /* 350 */ 1255, 1478, 1313, 1313, 1313, 1302, 1237, 1237, 1478, 1313,
- /* 360 */ 1287, 1313, 1302, 1313, 1313, 1576, 1237, 1482, 1482, 1478,
- /* 370 */ 1371, 1366, 1371, 1366, 1371, 1366, 1371, 1366, 1353, 1568,
- /* 380 */ 1568, 1381, 1381, 1386, 1372, 1473, 1353, 1237, 1386, 1384,
- /* 390 */ 1382, 1391, 1305, 1590, 1590, 1586, 1586, 1586, 1638, 1638,
- /* 400 */ 1537, 1603, 1270, 1270, 1270, 1270, 1603, 1289, 1289, 1271,
- /* 410 */ 1271, 1270, 1603, 1237, 1237, 1237, 1237, 1237, 1237, 1598,
- /* 420 */ 1237, 1532, 1489, 1357, 1237, 1237, 1237, 1237, 1237, 1237,
- /* 430 */ 1237, 1237, 1237, 1237, 1237, 1237, 1237, 1237, 1543, 1237,
- /* 440 */ 1237, 1237, 1237, 1237, 1237, 1237, 1237, 1237, 1237, 1418,
- /* 450 */ 1237, 1240, 1534, 1237, 1237, 1237, 1237, 1237, 1237, 1237,
- /* 460 */ 1237, 1395, 1396, 1358, 1237, 1237, 1237, 1237, 1237, 1237,
- /* 470 */ 1237, 1410, 1237, 1237, 1237, 1405, 1237, 1237, 1237, 1237,
- /* 480 */ 1237, 1237, 1237, 1237, 1629, 1237, 1237, 1237, 1237, 1237,
- /* 490 */ 1237, 1503, 1502, 1237, 1237, 1355, 1237, 1237, 1237, 1237,
- /* 500 */ 1237, 1237, 1237, 1237, 1237, 1237, 1237, 1237, 1237, 1285,
- /* 510 */ 1237, 1237, 1237, 1237, 1237, 1237, 1237, 1237, 1237, 1237,
- /* 520 */ 1237, 1237, 1237, 1237, 1237, 1237, 1237, 1237, 1237, 1237,
- /* 530 */ 1237, 1237, 1237, 1383, 1237, 1237, 1237, 1237, 1237, 1237,
- /* 540 */ 1237, 1237, 1237, 1237, 1237, 1237, 1237, 1237, 1573, 1373,
- /* 550 */ 1237, 1237, 1620, 1237, 1237, 1237, 1237, 1237, 1237, 1237,
- /* 560 */ 1237, 1237, 1237, 1237, 1237, 1237, 1237, 1614, 1329, 1420,
- /* 570 */ 1237, 1419, 1423, 1259, 1237, 1249, 1237, 1237,
+ /* 0 */ 1651, 1651, 1651, 1479, 1244, 1355, 1244, 1244, 1244, 1479,
+ /* 10 */ 1479, 1479, 1244, 1385, 1385, 1532, 1277, 1244, 1244, 1244,
+ /* 20 */ 1244, 1244, 1244, 1244, 1244, 1244, 1244, 1478, 1244, 1244,
+ /* 30 */ 1244, 1244, 1567, 1567, 1244, 1244, 1244, 1244, 1244, 1244,
+ /* 40 */ 1244, 1244, 1394, 1244, 1401, 1244, 1244, 1244, 1244, 1244,
+ /* 50 */ 1480, 1481, 1244, 1244, 1244, 1531, 1533, 1496, 1408, 1407,
+ /* 60 */ 1406, 1405, 1514, 1373, 1399, 1392, 1396, 1474, 1475, 1473,
+ /* 70 */ 1477, 1481, 1480, 1244, 1395, 1442, 1458, 1441, 1244, 1244,
+ /* 80 */ 1244, 1244, 1244, 1244, 1244, 1244, 1244, 1244, 1244, 1244,
+ /* 90 */ 1244, 1244, 1244, 1244, 1244, 1244, 1244, 1244, 1244, 1244,
+ /* 100 */ 1244, 1244, 1244, 1244, 1244, 1244, 1244, 1244, 1244, 1244,
+ /* 110 */ 1244, 1244, 1244, 1244, 1244, 1244, 1244, 1244, 1244, 1244,
+ /* 120 */ 1244, 1244, 1244, 1244, 1244, 1244, 1244, 1244, 1244, 1244,
+ /* 130 */ 1450, 1457, 1456, 1455, 1464, 1454, 1451, 1444, 1443, 1445,
+ /* 140 */ 1446, 1244, 1244, 1268, 1244, 1244, 1265, 1319, 1244, 1244,
+ /* 150 */ 1244, 1244, 1244, 1551, 1550, 1244, 1447, 1244, 1277, 1436,
+ /* 160 */ 1435, 1461, 1448, 1460, 1459, 1539, 1603, 1602, 1497, 1244,
+ /* 170 */ 1244, 1244, 1244, 1244, 1244, 1567, 1244, 1244, 1244, 1244,
+ /* 180 */ 1244, 1244, 1244, 1244, 1244, 1244, 1244, 1244, 1244, 1244,
+ /* 190 */ 1244, 1244, 1244, 1244, 1244, 1244, 1244, 1244, 1244, 1375,
+ /* 200 */ 1567, 1567, 1244, 1277, 1567, 1567, 1376, 1376, 1273, 1273,
+ /* 210 */ 1379, 1244, 1546, 1346, 1346, 1346, 1346, 1355, 1346, 1244,
+ /* 220 */ 1244, 1244, 1244, 1244, 1244, 1244, 1244, 1244, 1244, 1244,
+ /* 230 */ 1244, 1244, 1244, 1244, 1536, 1534, 1244, 1244, 1244, 1244,
+ /* 240 */ 1244, 1244, 1244, 1244, 1244, 1244, 1244, 1244, 1244, 1244,
+ /* 250 */ 1244, 1244, 1244, 1244, 1244, 1244, 1244, 1244, 1244, 1244,
+ /* 260 */ 1244, 1244, 1244, 1351, 1244, 1244, 1244, 1244, 1244, 1244,
+ /* 270 */ 1244, 1244, 1244, 1244, 1244, 1596, 1244, 1509, 1333, 1351,
+ /* 280 */ 1351, 1351, 1351, 1353, 1334, 1332, 1345, 1278, 1251, 1643,
+ /* 290 */ 1411, 1400, 1352, 1400, 1640, 1398, 1411, 1411, 1398, 1411,
+ /* 300 */ 1352, 1640, 1294, 1619, 1289, 1385, 1385, 1385, 1375, 1375,
+ /* 310 */ 1375, 1375, 1379, 1379, 1476, 1352, 1345, 1244, 1643, 1643,
+ /* 320 */ 1361, 1361, 1642, 1642, 1361, 1497, 1627, 1420, 1393, 1379,
+ /* 330 */ 1322, 1393, 1379, 1328, 1328, 1328, 1328, 1361, 1262, 1398,
+ /* 340 */ 1627, 1627, 1398, 1420, 1322, 1398, 1322, 1398, 1361, 1262,
+ /* 350 */ 1513, 1637, 1361, 1262, 1487, 1361, 1262, 1361, 1262, 1487,
+ /* 360 */ 1320, 1320, 1320, 1309, 1244, 1244, 1487, 1320, 1294, 1320,
+ /* 370 */ 1309, 1320, 1320, 1585, 1244, 1491, 1491, 1487, 1361, 1577,
+ /* 380 */ 1577, 1388, 1388, 1393, 1379, 1482, 1361, 1244, 1393, 1391,
+ /* 390 */ 1389, 1398, 1312, 1599, 1599, 1595, 1595, 1595, 1648, 1648,
+ /* 400 */ 1546, 1612, 1277, 1277, 1277, 1277, 1612, 1296, 1296, 1278,
+ /* 410 */ 1278, 1277, 1612, 1244, 1244, 1244, 1244, 1244, 1244, 1607,
+ /* 420 */ 1244, 1541, 1498, 1365, 1244, 1244, 1244, 1244, 1244, 1244,
+ /* 430 */ 1244, 1244, 1244, 1244, 1244, 1244, 1244, 1244, 1552, 1244,
+ /* 440 */ 1244, 1244, 1244, 1244, 1244, 1244, 1244, 1244, 1244, 1425,
+ /* 450 */ 1244, 1247, 1543, 1244, 1244, 1244, 1244, 1244, 1244, 1244,
+ /* 460 */ 1244, 1402, 1403, 1366, 1244, 1244, 1244, 1244, 1244, 1244,
+ /* 470 */ 1244, 1417, 1244, 1244, 1244, 1412, 1244, 1244, 1244, 1244,
+ /* 480 */ 1244, 1244, 1244, 1244, 1639, 1244, 1244, 1244, 1244, 1244,
+ /* 490 */ 1244, 1512, 1511, 1244, 1244, 1363, 1244, 1244, 1244, 1244,
+ /* 500 */ 1244, 1244, 1244, 1244, 1244, 1244, 1244, 1244, 1244, 1292,
+ /* 510 */ 1244, 1244, 1244, 1244, 1244, 1244, 1244, 1244, 1244, 1244,
+ /* 520 */ 1244, 1244, 1244, 1244, 1244, 1244, 1244, 1244, 1244, 1244,
+ /* 530 */ 1244, 1244, 1244, 1390, 1244, 1244, 1244, 1244, 1244, 1244,
+ /* 540 */ 1244, 1244, 1244, 1244, 1244, 1244, 1244, 1244, 1582, 1380,
+ /* 550 */ 1244, 1244, 1244, 1244, 1630, 1244, 1244, 1244, 1244, 1244,
+ /* 560 */ 1244, 1244, 1244, 1244, 1244, 1244, 1244, 1244, 1244, 1623,
+ /* 570 */ 1336, 1427, 1244, 1426, 1430, 1266, 1244, 1256, 1244, 1244,
};
/********** End of lemon-generated parsing tables *****************************/
@@ -163155,12 +167073,12 @@ static const char *const yyTokenName[] = {
/* 256 */ "seltablist",
/* 257 */ "stl_prefix",
/* 258 */ "joinop",
- /* 259 */ "indexed_opt",
- /* 260 */ "on_opt",
- /* 261 */ "using_opt",
- /* 262 */ "exprlist",
- /* 263 */ "xfullname",
- /* 264 */ "idlist",
+ /* 259 */ "on_using",
+ /* 260 */ "indexed_by",
+ /* 261 */ "exprlist",
+ /* 262 */ "xfullname",
+ /* 263 */ "idlist",
+ /* 264 */ "indexed_opt",
/* 265 */ "nulls",
/* 266 */ "with",
/* 267 */ "where_opt_ret",
@@ -163331,29 +167249,29 @@ static const char *const yyRuleName[] = {
/* 106 */ "from ::= FROM seltablist",
/* 107 */ "stl_prefix ::= seltablist joinop",
/* 108 */ "stl_prefix ::=",
- /* 109 */ "seltablist ::= stl_prefix nm dbnm as indexed_opt on_opt using_opt",
- /* 110 */ "seltablist ::= stl_prefix nm dbnm LP exprlist RP as on_opt using_opt",
- /* 111 */ "seltablist ::= stl_prefix LP select RP as on_opt using_opt",
- /* 112 */ "seltablist ::= stl_prefix LP seltablist RP as on_opt using_opt",
- /* 113 */ "dbnm ::=",
- /* 114 */ "dbnm ::= DOT nm",
- /* 115 */ "fullname ::= nm",
- /* 116 */ "fullname ::= nm DOT nm",
- /* 117 */ "xfullname ::= nm",
- /* 118 */ "xfullname ::= nm DOT nm",
- /* 119 */ "xfullname ::= nm DOT nm AS nm",
- /* 120 */ "xfullname ::= nm AS nm",
- /* 121 */ "joinop ::= COMMA|JOIN",
- /* 122 */ "joinop ::= JOIN_KW JOIN",
- /* 123 */ "joinop ::= JOIN_KW nm JOIN",
- /* 124 */ "joinop ::= JOIN_KW nm nm JOIN",
- /* 125 */ "on_opt ::= ON expr",
- /* 126 */ "on_opt ::=",
- /* 127 */ "indexed_opt ::=",
- /* 128 */ "indexed_opt ::= INDEXED BY nm",
- /* 129 */ "indexed_opt ::= NOT INDEXED",
- /* 130 */ "using_opt ::= USING LP idlist RP",
- /* 131 */ "using_opt ::=",
+ /* 109 */ "seltablist ::= stl_prefix nm dbnm as on_using",
+ /* 110 */ "seltablist ::= stl_prefix nm dbnm as indexed_by on_using",
+ /* 111 */ "seltablist ::= stl_prefix nm dbnm LP exprlist RP as on_using",
+ /* 112 */ "seltablist ::= stl_prefix LP select RP as on_using",
+ /* 113 */ "seltablist ::= stl_prefix LP seltablist RP as on_using",
+ /* 114 */ "dbnm ::=",
+ /* 115 */ "dbnm ::= DOT nm",
+ /* 116 */ "fullname ::= nm",
+ /* 117 */ "fullname ::= nm DOT nm",
+ /* 118 */ "xfullname ::= nm",
+ /* 119 */ "xfullname ::= nm DOT nm",
+ /* 120 */ "xfullname ::= nm DOT nm AS nm",
+ /* 121 */ "xfullname ::= nm AS nm",
+ /* 122 */ "joinop ::= COMMA|JOIN",
+ /* 123 */ "joinop ::= JOIN_KW JOIN",
+ /* 124 */ "joinop ::= JOIN_KW nm JOIN",
+ /* 125 */ "joinop ::= JOIN_KW nm nm JOIN",
+ /* 126 */ "on_using ::= ON expr",
+ /* 127 */ "on_using ::= USING LP idlist RP",
+ /* 128 */ "on_using ::=",
+ /* 129 */ "indexed_opt ::=",
+ /* 130 */ "indexed_by ::= INDEXED BY nm",
+ /* 131 */ "indexed_by ::= NOT INDEXED",
/* 132 */ "orderby_opt ::=",
/* 133 */ "orderby_opt ::= ORDER BY sortlist",
/* 134 */ "sortlist ::= sortlist COMMA expr sortorder nulls",
@@ -163431,199 +167349,202 @@ static const char *const yyRuleName[] = {
/* 206 */ "expr ::= expr NOT NULL",
/* 207 */ "expr ::= expr IS expr",
/* 208 */ "expr ::= expr IS NOT expr",
- /* 209 */ "expr ::= NOT expr",
- /* 210 */ "expr ::= BITNOT expr",
- /* 211 */ "expr ::= PLUS|MINUS expr",
- /* 212 */ "expr ::= expr PTR expr",
- /* 213 */ "between_op ::= BETWEEN",
- /* 214 */ "between_op ::= NOT BETWEEN",
- /* 215 */ "expr ::= expr between_op expr AND expr",
- /* 216 */ "in_op ::= IN",
- /* 217 */ "in_op ::= NOT IN",
- /* 218 */ "expr ::= expr in_op LP exprlist RP",
- /* 219 */ "expr ::= LP select RP",
- /* 220 */ "expr ::= expr in_op LP select RP",
- /* 221 */ "expr ::= expr in_op nm dbnm paren_exprlist",
- /* 222 */ "expr ::= EXISTS LP select RP",
- /* 223 */ "expr ::= CASE case_operand case_exprlist case_else END",
- /* 224 */ "case_exprlist ::= case_exprlist WHEN expr THEN expr",
- /* 225 */ "case_exprlist ::= WHEN expr THEN expr",
- /* 226 */ "case_else ::= ELSE expr",
- /* 227 */ "case_else ::=",
- /* 228 */ "case_operand ::= expr",
- /* 229 */ "case_operand ::=",
- /* 230 */ "exprlist ::=",
- /* 231 */ "nexprlist ::= nexprlist COMMA expr",
- /* 232 */ "nexprlist ::= expr",
- /* 233 */ "paren_exprlist ::=",
- /* 234 */ "paren_exprlist ::= LP exprlist RP",
- /* 235 */ "cmd ::= createkw uniqueflag INDEX ifnotexists nm dbnm ON nm LP sortlist RP where_opt",
- /* 236 */ "uniqueflag ::= UNIQUE",
- /* 237 */ "uniqueflag ::=",
- /* 238 */ "eidlist_opt ::=",
- /* 239 */ "eidlist_opt ::= LP eidlist RP",
- /* 240 */ "eidlist ::= eidlist COMMA nm collate sortorder",
- /* 241 */ "eidlist ::= nm collate sortorder",
- /* 242 */ "collate ::=",
- /* 243 */ "collate ::= COLLATE ID|STRING",
- /* 244 */ "cmd ::= DROP INDEX ifexists fullname",
- /* 245 */ "cmd ::= VACUUM vinto",
- /* 246 */ "cmd ::= VACUUM nm vinto",
- /* 247 */ "vinto ::= INTO expr",
- /* 248 */ "vinto ::=",
- /* 249 */ "cmd ::= PRAGMA nm dbnm",
- /* 250 */ "cmd ::= PRAGMA nm dbnm EQ nmnum",
- /* 251 */ "cmd ::= PRAGMA nm dbnm LP nmnum RP",
- /* 252 */ "cmd ::= PRAGMA nm dbnm EQ minus_num",
- /* 253 */ "cmd ::= PRAGMA nm dbnm LP minus_num RP",
- /* 254 */ "plus_num ::= PLUS INTEGER|FLOAT",
- /* 255 */ "minus_num ::= MINUS INTEGER|FLOAT",
- /* 256 */ "cmd ::= createkw trigger_decl BEGIN trigger_cmd_list END",
- /* 257 */ "trigger_decl ::= temp TRIGGER ifnotexists nm dbnm trigger_time trigger_event ON fullname foreach_clause when_clause",
- /* 258 */ "trigger_time ::= BEFORE|AFTER",
- /* 259 */ "trigger_time ::= INSTEAD OF",
- /* 260 */ "trigger_time ::=",
- /* 261 */ "trigger_event ::= DELETE|INSERT",
- /* 262 */ "trigger_event ::= UPDATE",
- /* 263 */ "trigger_event ::= UPDATE OF idlist",
- /* 264 */ "when_clause ::=",
- /* 265 */ "when_clause ::= WHEN expr",
- /* 266 */ "trigger_cmd_list ::= trigger_cmd_list trigger_cmd SEMI",
- /* 267 */ "trigger_cmd_list ::= trigger_cmd SEMI",
- /* 268 */ "trnm ::= nm DOT nm",
- /* 269 */ "tridxby ::= INDEXED BY nm",
- /* 270 */ "tridxby ::= NOT INDEXED",
- /* 271 */ "trigger_cmd ::= UPDATE orconf trnm tridxby SET setlist from where_opt scanpt",
- /* 272 */ "trigger_cmd ::= scanpt insert_cmd INTO trnm idlist_opt select upsert scanpt",
- /* 273 */ "trigger_cmd ::= DELETE FROM trnm tridxby where_opt scanpt",
- /* 274 */ "trigger_cmd ::= scanpt select scanpt",
- /* 275 */ "expr ::= RAISE LP IGNORE RP",
- /* 276 */ "expr ::= RAISE LP raisetype COMMA nm RP",
- /* 277 */ "raisetype ::= ROLLBACK",
- /* 278 */ "raisetype ::= ABORT",
- /* 279 */ "raisetype ::= FAIL",
- /* 280 */ "cmd ::= DROP TRIGGER ifexists fullname",
- /* 281 */ "cmd ::= ATTACH database_kw_opt expr AS expr key_opt",
- /* 282 */ "cmd ::= DETACH database_kw_opt expr",
- /* 283 */ "key_opt ::=",
- /* 284 */ "key_opt ::= KEY expr",
- /* 285 */ "cmd ::= REINDEX",
- /* 286 */ "cmd ::= REINDEX nm dbnm",
- /* 287 */ "cmd ::= ANALYZE",
- /* 288 */ "cmd ::= ANALYZE nm dbnm",
- /* 289 */ "cmd ::= ALTER TABLE fullname RENAME TO nm",
- /* 290 */ "cmd ::= ALTER TABLE add_column_fullname ADD kwcolumn_opt columnname carglist",
- /* 291 */ "cmd ::= ALTER TABLE fullname DROP kwcolumn_opt nm",
- /* 292 */ "add_column_fullname ::= fullname",
- /* 293 */ "cmd ::= ALTER TABLE fullname RENAME kwcolumn_opt nm TO nm",
- /* 294 */ "cmd ::= create_vtab",
- /* 295 */ "cmd ::= create_vtab LP vtabarglist RP",
- /* 296 */ "create_vtab ::= createkw VIRTUAL TABLE ifnotexists nm dbnm USING nm",
- /* 297 */ "vtabarg ::=",
- /* 298 */ "vtabargtoken ::= ANY",
- /* 299 */ "vtabargtoken ::= lp anylist RP",
- /* 300 */ "lp ::= LP",
- /* 301 */ "with ::= WITH wqlist",
- /* 302 */ "with ::= WITH RECURSIVE wqlist",
- /* 303 */ "wqas ::= AS",
- /* 304 */ "wqas ::= AS MATERIALIZED",
- /* 305 */ "wqas ::= AS NOT MATERIALIZED",
- /* 306 */ "wqitem ::= nm eidlist_opt wqas LP select RP",
- /* 307 */ "wqlist ::= wqitem",
- /* 308 */ "wqlist ::= wqlist COMMA wqitem",
- /* 309 */ "windowdefn_list ::= windowdefn",
- /* 310 */ "windowdefn_list ::= windowdefn_list COMMA windowdefn",
- /* 311 */ "windowdefn ::= nm AS LP window RP",
- /* 312 */ "window ::= PARTITION BY nexprlist orderby_opt frame_opt",
- /* 313 */ "window ::= nm PARTITION BY nexprlist orderby_opt frame_opt",
- /* 314 */ "window ::= ORDER BY sortlist frame_opt",
- /* 315 */ "window ::= nm ORDER BY sortlist frame_opt",
- /* 316 */ "window ::= frame_opt",
- /* 317 */ "window ::= nm frame_opt",
- /* 318 */ "frame_opt ::=",
- /* 319 */ "frame_opt ::= range_or_rows frame_bound_s frame_exclude_opt",
- /* 320 */ "frame_opt ::= range_or_rows BETWEEN frame_bound_s AND frame_bound_e frame_exclude_opt",
- /* 321 */ "range_or_rows ::= RANGE|ROWS|GROUPS",
- /* 322 */ "frame_bound_s ::= frame_bound",
- /* 323 */ "frame_bound_s ::= UNBOUNDED PRECEDING",
- /* 324 */ "frame_bound_e ::= frame_bound",
- /* 325 */ "frame_bound_e ::= UNBOUNDED FOLLOWING",
- /* 326 */ "frame_bound ::= expr PRECEDING|FOLLOWING",
- /* 327 */ "frame_bound ::= CURRENT ROW",
- /* 328 */ "frame_exclude_opt ::=",
- /* 329 */ "frame_exclude_opt ::= EXCLUDE frame_exclude",
- /* 330 */ "frame_exclude ::= NO OTHERS",
- /* 331 */ "frame_exclude ::= CURRENT ROW",
- /* 332 */ "frame_exclude ::= GROUP|TIES",
- /* 333 */ "window_clause ::= WINDOW windowdefn_list",
- /* 334 */ "filter_over ::= filter_clause over_clause",
- /* 335 */ "filter_over ::= over_clause",
- /* 336 */ "filter_over ::= filter_clause",
- /* 337 */ "over_clause ::= OVER LP window RP",
- /* 338 */ "over_clause ::= OVER nm",
- /* 339 */ "filter_clause ::= FILTER LP WHERE expr RP",
- /* 340 */ "input ::= cmdlist",
- /* 341 */ "cmdlist ::= cmdlist ecmd",
- /* 342 */ "cmdlist ::= ecmd",
- /* 343 */ "ecmd ::= SEMI",
- /* 344 */ "ecmd ::= cmdx SEMI",
- /* 345 */ "ecmd ::= explain cmdx SEMI",
- /* 346 */ "trans_opt ::=",
- /* 347 */ "trans_opt ::= TRANSACTION",
- /* 348 */ "trans_opt ::= TRANSACTION nm",
- /* 349 */ "savepoint_opt ::= SAVEPOINT",
- /* 350 */ "savepoint_opt ::=",
- /* 351 */ "cmd ::= create_table create_table_args",
- /* 352 */ "table_option_set ::= table_option",
- /* 353 */ "columnlist ::= columnlist COMMA columnname carglist",
- /* 354 */ "columnlist ::= columnname carglist",
- /* 355 */ "nm ::= ID|INDEXED",
- /* 356 */ "nm ::= STRING",
- /* 357 */ "nm ::= JOIN_KW",
- /* 358 */ "typetoken ::= typename",
- /* 359 */ "typename ::= ID|STRING",
- /* 360 */ "signed ::= plus_num",
- /* 361 */ "signed ::= minus_num",
- /* 362 */ "carglist ::= carglist ccons",
- /* 363 */ "carglist ::=",
- /* 364 */ "ccons ::= NULL onconf",
- /* 365 */ "ccons ::= GENERATED ALWAYS AS generated",
- /* 366 */ "ccons ::= AS generated",
- /* 367 */ "conslist_opt ::= COMMA conslist",
- /* 368 */ "conslist ::= conslist tconscomma tcons",
- /* 369 */ "conslist ::= tcons",
- /* 370 */ "tconscomma ::=",
- /* 371 */ "defer_subclause_opt ::= defer_subclause",
- /* 372 */ "resolvetype ::= raisetype",
- /* 373 */ "selectnowith ::= oneselect",
- /* 374 */ "oneselect ::= values",
- /* 375 */ "sclp ::= selcollist COMMA",
- /* 376 */ "as ::= ID|STRING",
- /* 377 */ "returning ::=",
- /* 378 */ "expr ::= term",
- /* 379 */ "likeop ::= LIKE_KW|MATCH",
- /* 380 */ "exprlist ::= nexprlist",
- /* 381 */ "nmnum ::= plus_num",
- /* 382 */ "nmnum ::= nm",
- /* 383 */ "nmnum ::= ON",
- /* 384 */ "nmnum ::= DELETE",
- /* 385 */ "nmnum ::= DEFAULT",
- /* 386 */ "plus_num ::= INTEGER|FLOAT",
- /* 387 */ "foreach_clause ::=",
- /* 388 */ "foreach_clause ::= FOR EACH ROW",
- /* 389 */ "trnm ::= nm",
- /* 390 */ "tridxby ::=",
- /* 391 */ "database_kw_opt ::= DATABASE",
- /* 392 */ "database_kw_opt ::=",
- /* 393 */ "kwcolumn_opt ::=",
- /* 394 */ "kwcolumn_opt ::= COLUMNKW",
- /* 395 */ "vtabarglist ::= vtabarg",
- /* 396 */ "vtabarglist ::= vtabarglist COMMA vtabarg",
- /* 397 */ "vtabarg ::= vtabarg vtabargtoken",
- /* 398 */ "anylist ::=",
- /* 399 */ "anylist ::= anylist LP anylist RP",
- /* 400 */ "anylist ::= anylist ANY",
- /* 401 */ "with ::=",
+ /* 209 */ "expr ::= expr IS NOT DISTINCT FROM expr",
+ /* 210 */ "expr ::= expr IS DISTINCT FROM expr",
+ /* 211 */ "expr ::= NOT expr",
+ /* 212 */ "expr ::= BITNOT expr",
+ /* 213 */ "expr ::= PLUS|MINUS expr",
+ /* 214 */ "expr ::= expr PTR expr",
+ /* 215 */ "between_op ::= BETWEEN",
+ /* 216 */ "between_op ::= NOT BETWEEN",
+ /* 217 */ "expr ::= expr between_op expr AND expr",
+ /* 218 */ "in_op ::= IN",
+ /* 219 */ "in_op ::= NOT IN",
+ /* 220 */ "expr ::= expr in_op LP exprlist RP",
+ /* 221 */ "expr ::= LP select RP",
+ /* 222 */ "expr ::= expr in_op LP select RP",
+ /* 223 */ "expr ::= expr in_op nm dbnm paren_exprlist",
+ /* 224 */ "expr ::= EXISTS LP select RP",
+ /* 225 */ "expr ::= CASE case_operand case_exprlist case_else END",
+ /* 226 */ "case_exprlist ::= case_exprlist WHEN expr THEN expr",
+ /* 227 */ "case_exprlist ::= WHEN expr THEN expr",
+ /* 228 */ "case_else ::= ELSE expr",
+ /* 229 */ "case_else ::=",
+ /* 230 */ "case_operand ::= expr",
+ /* 231 */ "case_operand ::=",
+ /* 232 */ "exprlist ::=",
+ /* 233 */ "nexprlist ::= nexprlist COMMA expr",
+ /* 234 */ "nexprlist ::= expr",
+ /* 235 */ "paren_exprlist ::=",
+ /* 236 */ "paren_exprlist ::= LP exprlist RP",
+ /* 237 */ "cmd ::= createkw uniqueflag INDEX ifnotexists nm dbnm ON nm LP sortlist RP where_opt",
+ /* 238 */ "uniqueflag ::= UNIQUE",
+ /* 239 */ "uniqueflag ::=",
+ /* 240 */ "eidlist_opt ::=",
+ /* 241 */ "eidlist_opt ::= LP eidlist RP",
+ /* 242 */ "eidlist ::= eidlist COMMA nm collate sortorder",
+ /* 243 */ "eidlist ::= nm collate sortorder",
+ /* 244 */ "collate ::=",
+ /* 245 */ "collate ::= COLLATE ID|STRING",
+ /* 246 */ "cmd ::= DROP INDEX ifexists fullname",
+ /* 247 */ "cmd ::= VACUUM vinto",
+ /* 248 */ "cmd ::= VACUUM nm vinto",
+ /* 249 */ "vinto ::= INTO expr",
+ /* 250 */ "vinto ::=",
+ /* 251 */ "cmd ::= PRAGMA nm dbnm",
+ /* 252 */ "cmd ::= PRAGMA nm dbnm EQ nmnum",
+ /* 253 */ "cmd ::= PRAGMA nm dbnm LP nmnum RP",
+ /* 254 */ "cmd ::= PRAGMA nm dbnm EQ minus_num",
+ /* 255 */ "cmd ::= PRAGMA nm dbnm LP minus_num RP",
+ /* 256 */ "plus_num ::= PLUS INTEGER|FLOAT",
+ /* 257 */ "minus_num ::= MINUS INTEGER|FLOAT",
+ /* 258 */ "cmd ::= createkw trigger_decl BEGIN trigger_cmd_list END",
+ /* 259 */ "trigger_decl ::= temp TRIGGER ifnotexists nm dbnm trigger_time trigger_event ON fullname foreach_clause when_clause",
+ /* 260 */ "trigger_time ::= BEFORE|AFTER",
+ /* 261 */ "trigger_time ::= INSTEAD OF",
+ /* 262 */ "trigger_time ::=",
+ /* 263 */ "trigger_event ::= DELETE|INSERT",
+ /* 264 */ "trigger_event ::= UPDATE",
+ /* 265 */ "trigger_event ::= UPDATE OF idlist",
+ /* 266 */ "when_clause ::=",
+ /* 267 */ "when_clause ::= WHEN expr",
+ /* 268 */ "trigger_cmd_list ::= trigger_cmd_list trigger_cmd SEMI",
+ /* 269 */ "trigger_cmd_list ::= trigger_cmd SEMI",
+ /* 270 */ "trnm ::= nm DOT nm",
+ /* 271 */ "tridxby ::= INDEXED BY nm",
+ /* 272 */ "tridxby ::= NOT INDEXED",
+ /* 273 */ "trigger_cmd ::= UPDATE orconf trnm tridxby SET setlist from where_opt scanpt",
+ /* 274 */ "trigger_cmd ::= scanpt insert_cmd INTO trnm idlist_opt select upsert scanpt",
+ /* 275 */ "trigger_cmd ::= DELETE FROM trnm tridxby where_opt scanpt",
+ /* 276 */ "trigger_cmd ::= scanpt select scanpt",
+ /* 277 */ "expr ::= RAISE LP IGNORE RP",
+ /* 278 */ "expr ::= RAISE LP raisetype COMMA nm RP",
+ /* 279 */ "raisetype ::= ROLLBACK",
+ /* 280 */ "raisetype ::= ABORT",
+ /* 281 */ "raisetype ::= FAIL",
+ /* 282 */ "cmd ::= DROP TRIGGER ifexists fullname",
+ /* 283 */ "cmd ::= ATTACH database_kw_opt expr AS expr key_opt",
+ /* 284 */ "cmd ::= DETACH database_kw_opt expr",
+ /* 285 */ "key_opt ::=",
+ /* 286 */ "key_opt ::= KEY expr",
+ /* 287 */ "cmd ::= REINDEX",
+ /* 288 */ "cmd ::= REINDEX nm dbnm",
+ /* 289 */ "cmd ::= ANALYZE",
+ /* 290 */ "cmd ::= ANALYZE nm dbnm",
+ /* 291 */ "cmd ::= ALTER TABLE fullname RENAME TO nm",
+ /* 292 */ "cmd ::= ALTER TABLE add_column_fullname ADD kwcolumn_opt columnname carglist",
+ /* 293 */ "cmd ::= ALTER TABLE fullname DROP kwcolumn_opt nm",
+ /* 294 */ "add_column_fullname ::= fullname",
+ /* 295 */ "cmd ::= ALTER TABLE fullname RENAME kwcolumn_opt nm TO nm",
+ /* 296 */ "cmd ::= create_vtab",
+ /* 297 */ "cmd ::= create_vtab LP vtabarglist RP",
+ /* 298 */ "create_vtab ::= createkw VIRTUAL TABLE ifnotexists nm dbnm USING nm",
+ /* 299 */ "vtabarg ::=",
+ /* 300 */ "vtabargtoken ::= ANY",
+ /* 301 */ "vtabargtoken ::= lp anylist RP",
+ /* 302 */ "lp ::= LP",
+ /* 303 */ "with ::= WITH wqlist",
+ /* 304 */ "with ::= WITH RECURSIVE wqlist",
+ /* 305 */ "wqas ::= AS",
+ /* 306 */ "wqas ::= AS MATERIALIZED",
+ /* 307 */ "wqas ::= AS NOT MATERIALIZED",
+ /* 308 */ "wqitem ::= nm eidlist_opt wqas LP select RP",
+ /* 309 */ "wqlist ::= wqitem",
+ /* 310 */ "wqlist ::= wqlist COMMA wqitem",
+ /* 311 */ "windowdefn_list ::= windowdefn",
+ /* 312 */ "windowdefn_list ::= windowdefn_list COMMA windowdefn",
+ /* 313 */ "windowdefn ::= nm AS LP window RP",
+ /* 314 */ "window ::= PARTITION BY nexprlist orderby_opt frame_opt",
+ /* 315 */ "window ::= nm PARTITION BY nexprlist orderby_opt frame_opt",
+ /* 316 */ "window ::= ORDER BY sortlist frame_opt",
+ /* 317 */ "window ::= nm ORDER BY sortlist frame_opt",
+ /* 318 */ "window ::= frame_opt",
+ /* 319 */ "window ::= nm frame_opt",
+ /* 320 */ "frame_opt ::=",
+ /* 321 */ "frame_opt ::= range_or_rows frame_bound_s frame_exclude_opt",
+ /* 322 */ "frame_opt ::= range_or_rows BETWEEN frame_bound_s AND frame_bound_e frame_exclude_opt",
+ /* 323 */ "range_or_rows ::= RANGE|ROWS|GROUPS",
+ /* 324 */ "frame_bound_s ::= frame_bound",
+ /* 325 */ "frame_bound_s ::= UNBOUNDED PRECEDING",
+ /* 326 */ "frame_bound_e ::= frame_bound",
+ /* 327 */ "frame_bound_e ::= UNBOUNDED FOLLOWING",
+ /* 328 */ "frame_bound ::= expr PRECEDING|FOLLOWING",
+ /* 329 */ "frame_bound ::= CURRENT ROW",
+ /* 330 */ "frame_exclude_opt ::=",
+ /* 331 */ "frame_exclude_opt ::= EXCLUDE frame_exclude",
+ /* 332 */ "frame_exclude ::= NO OTHERS",
+ /* 333 */ "frame_exclude ::= CURRENT ROW",
+ /* 334 */ "frame_exclude ::= GROUP|TIES",
+ /* 335 */ "window_clause ::= WINDOW windowdefn_list",
+ /* 336 */ "filter_over ::= filter_clause over_clause",
+ /* 337 */ "filter_over ::= over_clause",
+ /* 338 */ "filter_over ::= filter_clause",
+ /* 339 */ "over_clause ::= OVER LP window RP",
+ /* 340 */ "over_clause ::= OVER nm",
+ /* 341 */ "filter_clause ::= FILTER LP WHERE expr RP",
+ /* 342 */ "input ::= cmdlist",
+ /* 343 */ "cmdlist ::= cmdlist ecmd",
+ /* 344 */ "cmdlist ::= ecmd",
+ /* 345 */ "ecmd ::= SEMI",
+ /* 346 */ "ecmd ::= cmdx SEMI",
+ /* 347 */ "ecmd ::= explain cmdx SEMI",
+ /* 348 */ "trans_opt ::=",
+ /* 349 */ "trans_opt ::= TRANSACTION",
+ /* 350 */ "trans_opt ::= TRANSACTION nm",
+ /* 351 */ "savepoint_opt ::= SAVEPOINT",
+ /* 352 */ "savepoint_opt ::=",
+ /* 353 */ "cmd ::= create_table create_table_args",
+ /* 354 */ "table_option_set ::= table_option",
+ /* 355 */ "columnlist ::= columnlist COMMA columnname carglist",
+ /* 356 */ "columnlist ::= columnname carglist",
+ /* 357 */ "nm ::= ID|INDEXED",
+ /* 358 */ "nm ::= STRING",
+ /* 359 */ "nm ::= JOIN_KW",
+ /* 360 */ "typetoken ::= typename",
+ /* 361 */ "typename ::= ID|STRING",
+ /* 362 */ "signed ::= plus_num",
+ /* 363 */ "signed ::= minus_num",
+ /* 364 */ "carglist ::= carglist ccons",
+ /* 365 */ "carglist ::=",
+ /* 366 */ "ccons ::= NULL onconf",
+ /* 367 */ "ccons ::= GENERATED ALWAYS AS generated",
+ /* 368 */ "ccons ::= AS generated",
+ /* 369 */ "conslist_opt ::= COMMA conslist",
+ /* 370 */ "conslist ::= conslist tconscomma tcons",
+ /* 371 */ "conslist ::= tcons",
+ /* 372 */ "tconscomma ::=",
+ /* 373 */ "defer_subclause_opt ::= defer_subclause",
+ /* 374 */ "resolvetype ::= raisetype",
+ /* 375 */ "selectnowith ::= oneselect",
+ /* 376 */ "oneselect ::= values",
+ /* 377 */ "sclp ::= selcollist COMMA",
+ /* 378 */ "as ::= ID|STRING",
+ /* 379 */ "indexed_opt ::= indexed_by",
+ /* 380 */ "returning ::=",
+ /* 381 */ "expr ::= term",
+ /* 382 */ "likeop ::= LIKE_KW|MATCH",
+ /* 383 */ "exprlist ::= nexprlist",
+ /* 384 */ "nmnum ::= plus_num",
+ /* 385 */ "nmnum ::= nm",
+ /* 386 */ "nmnum ::= ON",
+ /* 387 */ "nmnum ::= DELETE",
+ /* 388 */ "nmnum ::= DEFAULT",
+ /* 389 */ "plus_num ::= INTEGER|FLOAT",
+ /* 390 */ "foreach_clause ::=",
+ /* 391 */ "foreach_clause ::= FOR EACH ROW",
+ /* 392 */ "trnm ::= nm",
+ /* 393 */ "tridxby ::=",
+ /* 394 */ "database_kw_opt ::= DATABASE",
+ /* 395 */ "database_kw_opt ::=",
+ /* 396 */ "kwcolumn_opt ::=",
+ /* 397 */ "kwcolumn_opt ::= COLUMNKW",
+ /* 398 */ "vtabarglist ::= vtabarg",
+ /* 399 */ "vtabarglist ::= vtabarglist COMMA vtabarg",
+ /* 400 */ "vtabarg ::= vtabarg vtabargtoken",
+ /* 401 */ "anylist ::=",
+ /* 402 */ "anylist ::= anylist LP anylist RP",
+ /* 403 */ "anylist ::= anylist ANY",
+ /* 404 */ "with ::=",
};
#endif /* NDEBUG */
@@ -163761,7 +167682,6 @@ sqlite3SelectDelete(pParse->db, (yypminor->yy47));
case 217: /* expr */
case 246: /* where_opt */
case 248: /* having_opt */
- case 260: /* on_opt */
case 267: /* where_opt_ret */
case 278: /* case_operand */
case 280: /* case_else */
@@ -163781,7 +167701,7 @@ sqlite3ExprDelete(pParse->db, (yypminor->yy528));
case 249: /* orderby_opt */
case 253: /* nexprlist */
case 254: /* sclp */
- case 262: /* exprlist */
+ case 261: /* exprlist */
case 268: /* setlist */
case 277: /* paren_exprlist */
case 279: /* case_exprlist */
@@ -163794,7 +167714,7 @@ sqlite3ExprListDelete(pParse->db, (yypminor->yy322));
case 245: /* from */
case 256: /* seltablist */
case 257: /* stl_prefix */
- case 263: /* xfullname */
+ case 262: /* xfullname */
{
sqlite3SrcListDelete(pParse->db, (yypminor->yy131));
}
@@ -163810,8 +167730,7 @@ sqlite3WithDelete(pParse->db, (yypminor->yy521));
sqlite3WindowListDelete(pParse->db, (yypminor->yy41));
}
break;
- case 261: /* using_opt */
- case 264: /* idlist */
+ case 263: /* idlist */
case 270: /* idlist_opt */
{
sqlite3IdListDelete(pParse->db, (yypminor->yy254));
@@ -164241,29 +168160,29 @@ static const YYCODETYPE yyRuleInfoLhs[] = {
245, /* (106) from ::= FROM seltablist */
257, /* (107) stl_prefix ::= seltablist joinop */
257, /* (108) stl_prefix ::= */
- 256, /* (109) seltablist ::= stl_prefix nm dbnm as indexed_opt on_opt using_opt */
- 256, /* (110) seltablist ::= stl_prefix nm dbnm LP exprlist RP as on_opt using_opt */
- 256, /* (111) seltablist ::= stl_prefix LP select RP as on_opt using_opt */
- 256, /* (112) seltablist ::= stl_prefix LP seltablist RP as on_opt using_opt */
- 200, /* (113) dbnm ::= */
- 200, /* (114) dbnm ::= DOT nm */
- 238, /* (115) fullname ::= nm */
- 238, /* (116) fullname ::= nm DOT nm */
- 263, /* (117) xfullname ::= nm */
- 263, /* (118) xfullname ::= nm DOT nm */
- 263, /* (119) xfullname ::= nm DOT nm AS nm */
- 263, /* (120) xfullname ::= nm AS nm */
- 258, /* (121) joinop ::= COMMA|JOIN */
- 258, /* (122) joinop ::= JOIN_KW JOIN */
- 258, /* (123) joinop ::= JOIN_KW nm JOIN */
- 258, /* (124) joinop ::= JOIN_KW nm nm JOIN */
- 260, /* (125) on_opt ::= ON expr */
- 260, /* (126) on_opt ::= */
- 259, /* (127) indexed_opt ::= */
- 259, /* (128) indexed_opt ::= INDEXED BY nm */
- 259, /* (129) indexed_opt ::= NOT INDEXED */
- 261, /* (130) using_opt ::= USING LP idlist RP */
- 261, /* (131) using_opt ::= */
+ 256, /* (109) seltablist ::= stl_prefix nm dbnm as on_using */
+ 256, /* (110) seltablist ::= stl_prefix nm dbnm as indexed_by on_using */
+ 256, /* (111) seltablist ::= stl_prefix nm dbnm LP exprlist RP as on_using */
+ 256, /* (112) seltablist ::= stl_prefix LP select RP as on_using */
+ 256, /* (113) seltablist ::= stl_prefix LP seltablist RP as on_using */
+ 200, /* (114) dbnm ::= */
+ 200, /* (115) dbnm ::= DOT nm */
+ 238, /* (116) fullname ::= nm */
+ 238, /* (117) fullname ::= nm DOT nm */
+ 262, /* (118) xfullname ::= nm */
+ 262, /* (119) xfullname ::= nm DOT nm */
+ 262, /* (120) xfullname ::= nm DOT nm AS nm */
+ 262, /* (121) xfullname ::= nm AS nm */
+ 258, /* (122) joinop ::= COMMA|JOIN */
+ 258, /* (123) joinop ::= JOIN_KW JOIN */
+ 258, /* (124) joinop ::= JOIN_KW nm JOIN */
+ 258, /* (125) joinop ::= JOIN_KW nm nm JOIN */
+ 259, /* (126) on_using ::= ON expr */
+ 259, /* (127) on_using ::= USING LP idlist RP */
+ 259, /* (128) on_using ::= */
+ 264, /* (129) indexed_opt ::= */
+ 260, /* (130) indexed_by ::= INDEXED BY nm */
+ 260, /* (131) indexed_by ::= NOT INDEXED */
249, /* (132) orderby_opt ::= */
249, /* (133) orderby_opt ::= ORDER BY sortlist */
231, /* (134) sortlist ::= sortlist COMMA expr sortorder nulls */
@@ -164307,8 +168226,8 @@ static const YYCODETYPE yyRuleInfoLhs[] = {
269, /* (172) insert_cmd ::= REPLACE */
270, /* (173) idlist_opt ::= */
270, /* (174) idlist_opt ::= LP idlist RP */
- 264, /* (175) idlist ::= idlist COMMA nm */
- 264, /* (176) idlist ::= nm */
+ 263, /* (175) idlist ::= idlist COMMA nm */
+ 263, /* (176) idlist ::= nm */
217, /* (177) expr ::= LP expr RP */
217, /* (178) expr ::= ID|INDEXED */
217, /* (179) expr ::= JOIN_KW */
@@ -164341,199 +168260,202 @@ static const YYCODETYPE yyRuleInfoLhs[] = {
217, /* (206) expr ::= expr NOT NULL */
217, /* (207) expr ::= expr IS expr */
217, /* (208) expr ::= expr IS NOT expr */
- 217, /* (209) expr ::= NOT expr */
- 217, /* (210) expr ::= BITNOT expr */
- 217, /* (211) expr ::= PLUS|MINUS expr */
- 217, /* (212) expr ::= expr PTR expr */
- 275, /* (213) between_op ::= BETWEEN */
- 275, /* (214) between_op ::= NOT BETWEEN */
- 217, /* (215) expr ::= expr between_op expr AND expr */
- 276, /* (216) in_op ::= IN */
- 276, /* (217) in_op ::= NOT IN */
- 217, /* (218) expr ::= expr in_op LP exprlist RP */
- 217, /* (219) expr ::= LP select RP */
- 217, /* (220) expr ::= expr in_op LP select RP */
- 217, /* (221) expr ::= expr in_op nm dbnm paren_exprlist */
- 217, /* (222) expr ::= EXISTS LP select RP */
- 217, /* (223) expr ::= CASE case_operand case_exprlist case_else END */
- 279, /* (224) case_exprlist ::= case_exprlist WHEN expr THEN expr */
- 279, /* (225) case_exprlist ::= WHEN expr THEN expr */
- 280, /* (226) case_else ::= ELSE expr */
- 280, /* (227) case_else ::= */
- 278, /* (228) case_operand ::= expr */
- 278, /* (229) case_operand ::= */
- 262, /* (230) exprlist ::= */
- 253, /* (231) nexprlist ::= nexprlist COMMA expr */
- 253, /* (232) nexprlist ::= expr */
- 277, /* (233) paren_exprlist ::= */
- 277, /* (234) paren_exprlist ::= LP exprlist RP */
- 190, /* (235) cmd ::= createkw uniqueflag INDEX ifnotexists nm dbnm ON nm LP sortlist RP where_opt */
- 281, /* (236) uniqueflag ::= UNIQUE */
- 281, /* (237) uniqueflag ::= */
- 221, /* (238) eidlist_opt ::= */
- 221, /* (239) eidlist_opt ::= LP eidlist RP */
- 232, /* (240) eidlist ::= eidlist COMMA nm collate sortorder */
- 232, /* (241) eidlist ::= nm collate sortorder */
- 282, /* (242) collate ::= */
- 282, /* (243) collate ::= COLLATE ID|STRING */
- 190, /* (244) cmd ::= DROP INDEX ifexists fullname */
- 190, /* (245) cmd ::= VACUUM vinto */
- 190, /* (246) cmd ::= VACUUM nm vinto */
- 283, /* (247) vinto ::= INTO expr */
- 283, /* (248) vinto ::= */
- 190, /* (249) cmd ::= PRAGMA nm dbnm */
- 190, /* (250) cmd ::= PRAGMA nm dbnm EQ nmnum */
- 190, /* (251) cmd ::= PRAGMA nm dbnm LP nmnum RP */
- 190, /* (252) cmd ::= PRAGMA nm dbnm EQ minus_num */
- 190, /* (253) cmd ::= PRAGMA nm dbnm LP minus_num RP */
- 211, /* (254) plus_num ::= PLUS INTEGER|FLOAT */
- 212, /* (255) minus_num ::= MINUS INTEGER|FLOAT */
- 190, /* (256) cmd ::= createkw trigger_decl BEGIN trigger_cmd_list END */
- 285, /* (257) trigger_decl ::= temp TRIGGER ifnotexists nm dbnm trigger_time trigger_event ON fullname foreach_clause when_clause */
- 287, /* (258) trigger_time ::= BEFORE|AFTER */
- 287, /* (259) trigger_time ::= INSTEAD OF */
- 287, /* (260) trigger_time ::= */
- 288, /* (261) trigger_event ::= DELETE|INSERT */
- 288, /* (262) trigger_event ::= UPDATE */
- 288, /* (263) trigger_event ::= UPDATE OF idlist */
- 290, /* (264) when_clause ::= */
- 290, /* (265) when_clause ::= WHEN expr */
- 286, /* (266) trigger_cmd_list ::= trigger_cmd_list trigger_cmd SEMI */
- 286, /* (267) trigger_cmd_list ::= trigger_cmd SEMI */
- 292, /* (268) trnm ::= nm DOT nm */
- 293, /* (269) tridxby ::= INDEXED BY nm */
- 293, /* (270) tridxby ::= NOT INDEXED */
- 291, /* (271) trigger_cmd ::= UPDATE orconf trnm tridxby SET setlist from where_opt scanpt */
- 291, /* (272) trigger_cmd ::= scanpt insert_cmd INTO trnm idlist_opt select upsert scanpt */
- 291, /* (273) trigger_cmd ::= DELETE FROM trnm tridxby where_opt scanpt */
- 291, /* (274) trigger_cmd ::= scanpt select scanpt */
- 217, /* (275) expr ::= RAISE LP IGNORE RP */
- 217, /* (276) expr ::= RAISE LP raisetype COMMA nm RP */
- 236, /* (277) raisetype ::= ROLLBACK */
- 236, /* (278) raisetype ::= ABORT */
- 236, /* (279) raisetype ::= FAIL */
- 190, /* (280) cmd ::= DROP TRIGGER ifexists fullname */
- 190, /* (281) cmd ::= ATTACH database_kw_opt expr AS expr key_opt */
- 190, /* (282) cmd ::= DETACH database_kw_opt expr */
- 295, /* (283) key_opt ::= */
- 295, /* (284) key_opt ::= KEY expr */
- 190, /* (285) cmd ::= REINDEX */
- 190, /* (286) cmd ::= REINDEX nm dbnm */
- 190, /* (287) cmd ::= ANALYZE */
- 190, /* (288) cmd ::= ANALYZE nm dbnm */
- 190, /* (289) cmd ::= ALTER TABLE fullname RENAME TO nm */
- 190, /* (290) cmd ::= ALTER TABLE add_column_fullname ADD kwcolumn_opt columnname carglist */
- 190, /* (291) cmd ::= ALTER TABLE fullname DROP kwcolumn_opt nm */
- 296, /* (292) add_column_fullname ::= fullname */
- 190, /* (293) cmd ::= ALTER TABLE fullname RENAME kwcolumn_opt nm TO nm */
- 190, /* (294) cmd ::= create_vtab */
- 190, /* (295) cmd ::= create_vtab LP vtabarglist RP */
- 298, /* (296) create_vtab ::= createkw VIRTUAL TABLE ifnotexists nm dbnm USING nm */
- 300, /* (297) vtabarg ::= */
- 301, /* (298) vtabargtoken ::= ANY */
- 301, /* (299) vtabargtoken ::= lp anylist RP */
- 302, /* (300) lp ::= LP */
- 266, /* (301) with ::= WITH wqlist */
- 266, /* (302) with ::= WITH RECURSIVE wqlist */
- 305, /* (303) wqas ::= AS */
- 305, /* (304) wqas ::= AS MATERIALIZED */
- 305, /* (305) wqas ::= AS NOT MATERIALIZED */
- 304, /* (306) wqitem ::= nm eidlist_opt wqas LP select RP */
- 241, /* (307) wqlist ::= wqitem */
- 241, /* (308) wqlist ::= wqlist COMMA wqitem */
- 306, /* (309) windowdefn_list ::= windowdefn */
- 306, /* (310) windowdefn_list ::= windowdefn_list COMMA windowdefn */
- 307, /* (311) windowdefn ::= nm AS LP window RP */
- 308, /* (312) window ::= PARTITION BY nexprlist orderby_opt frame_opt */
- 308, /* (313) window ::= nm PARTITION BY nexprlist orderby_opt frame_opt */
- 308, /* (314) window ::= ORDER BY sortlist frame_opt */
- 308, /* (315) window ::= nm ORDER BY sortlist frame_opt */
- 308, /* (316) window ::= frame_opt */
- 308, /* (317) window ::= nm frame_opt */
- 309, /* (318) frame_opt ::= */
- 309, /* (319) frame_opt ::= range_or_rows frame_bound_s frame_exclude_opt */
- 309, /* (320) frame_opt ::= range_or_rows BETWEEN frame_bound_s AND frame_bound_e frame_exclude_opt */
- 313, /* (321) range_or_rows ::= RANGE|ROWS|GROUPS */
- 315, /* (322) frame_bound_s ::= frame_bound */
- 315, /* (323) frame_bound_s ::= UNBOUNDED PRECEDING */
- 316, /* (324) frame_bound_e ::= frame_bound */
- 316, /* (325) frame_bound_e ::= UNBOUNDED FOLLOWING */
- 314, /* (326) frame_bound ::= expr PRECEDING|FOLLOWING */
- 314, /* (327) frame_bound ::= CURRENT ROW */
- 317, /* (328) frame_exclude_opt ::= */
- 317, /* (329) frame_exclude_opt ::= EXCLUDE frame_exclude */
- 318, /* (330) frame_exclude ::= NO OTHERS */
- 318, /* (331) frame_exclude ::= CURRENT ROW */
- 318, /* (332) frame_exclude ::= GROUP|TIES */
- 251, /* (333) window_clause ::= WINDOW windowdefn_list */
- 273, /* (334) filter_over ::= filter_clause over_clause */
- 273, /* (335) filter_over ::= over_clause */
- 273, /* (336) filter_over ::= filter_clause */
- 312, /* (337) over_clause ::= OVER LP window RP */
- 312, /* (338) over_clause ::= OVER nm */
- 311, /* (339) filter_clause ::= FILTER LP WHERE expr RP */
- 185, /* (340) input ::= cmdlist */
- 186, /* (341) cmdlist ::= cmdlist ecmd */
- 186, /* (342) cmdlist ::= ecmd */
- 187, /* (343) ecmd ::= SEMI */
- 187, /* (344) ecmd ::= cmdx SEMI */
- 187, /* (345) ecmd ::= explain cmdx SEMI */
- 192, /* (346) trans_opt ::= */
- 192, /* (347) trans_opt ::= TRANSACTION */
- 192, /* (348) trans_opt ::= TRANSACTION nm */
- 194, /* (349) savepoint_opt ::= SAVEPOINT */
- 194, /* (350) savepoint_opt ::= */
- 190, /* (351) cmd ::= create_table create_table_args */
- 203, /* (352) table_option_set ::= table_option */
- 201, /* (353) columnlist ::= columnlist COMMA columnname carglist */
- 201, /* (354) columnlist ::= columnname carglist */
- 193, /* (355) nm ::= ID|INDEXED */
- 193, /* (356) nm ::= STRING */
- 193, /* (357) nm ::= JOIN_KW */
- 208, /* (358) typetoken ::= typename */
- 209, /* (359) typename ::= ID|STRING */
- 210, /* (360) signed ::= plus_num */
- 210, /* (361) signed ::= minus_num */
- 207, /* (362) carglist ::= carglist ccons */
- 207, /* (363) carglist ::= */
- 215, /* (364) ccons ::= NULL onconf */
- 215, /* (365) ccons ::= GENERATED ALWAYS AS generated */
- 215, /* (366) ccons ::= AS generated */
- 202, /* (367) conslist_opt ::= COMMA conslist */
- 228, /* (368) conslist ::= conslist tconscomma tcons */
- 228, /* (369) conslist ::= tcons */
- 229, /* (370) tconscomma ::= */
- 233, /* (371) defer_subclause_opt ::= defer_subclause */
- 235, /* (372) resolvetype ::= raisetype */
- 239, /* (373) selectnowith ::= oneselect */
- 240, /* (374) oneselect ::= values */
- 254, /* (375) sclp ::= selcollist COMMA */
- 255, /* (376) as ::= ID|STRING */
- 272, /* (377) returning ::= */
- 217, /* (378) expr ::= term */
- 274, /* (379) likeop ::= LIKE_KW|MATCH */
- 262, /* (380) exprlist ::= nexprlist */
- 284, /* (381) nmnum ::= plus_num */
- 284, /* (382) nmnum ::= nm */
- 284, /* (383) nmnum ::= ON */
- 284, /* (384) nmnum ::= DELETE */
- 284, /* (385) nmnum ::= DEFAULT */
- 211, /* (386) plus_num ::= INTEGER|FLOAT */
- 289, /* (387) foreach_clause ::= */
- 289, /* (388) foreach_clause ::= FOR EACH ROW */
- 292, /* (389) trnm ::= nm */
- 293, /* (390) tridxby ::= */
- 294, /* (391) database_kw_opt ::= DATABASE */
- 294, /* (392) database_kw_opt ::= */
- 297, /* (393) kwcolumn_opt ::= */
- 297, /* (394) kwcolumn_opt ::= COLUMNKW */
- 299, /* (395) vtabarglist ::= vtabarg */
- 299, /* (396) vtabarglist ::= vtabarglist COMMA vtabarg */
- 300, /* (397) vtabarg ::= vtabarg vtabargtoken */
- 303, /* (398) anylist ::= */
- 303, /* (399) anylist ::= anylist LP anylist RP */
- 303, /* (400) anylist ::= anylist ANY */
- 266, /* (401) with ::= */
+ 217, /* (209) expr ::= expr IS NOT DISTINCT FROM expr */
+ 217, /* (210) expr ::= expr IS DISTINCT FROM expr */
+ 217, /* (211) expr ::= NOT expr */
+ 217, /* (212) expr ::= BITNOT expr */
+ 217, /* (213) expr ::= PLUS|MINUS expr */
+ 217, /* (214) expr ::= expr PTR expr */
+ 275, /* (215) between_op ::= BETWEEN */
+ 275, /* (216) between_op ::= NOT BETWEEN */
+ 217, /* (217) expr ::= expr between_op expr AND expr */
+ 276, /* (218) in_op ::= IN */
+ 276, /* (219) in_op ::= NOT IN */
+ 217, /* (220) expr ::= expr in_op LP exprlist RP */
+ 217, /* (221) expr ::= LP select RP */
+ 217, /* (222) expr ::= expr in_op LP select RP */
+ 217, /* (223) expr ::= expr in_op nm dbnm paren_exprlist */
+ 217, /* (224) expr ::= EXISTS LP select RP */
+ 217, /* (225) expr ::= CASE case_operand case_exprlist case_else END */
+ 279, /* (226) case_exprlist ::= case_exprlist WHEN expr THEN expr */
+ 279, /* (227) case_exprlist ::= WHEN expr THEN expr */
+ 280, /* (228) case_else ::= ELSE expr */
+ 280, /* (229) case_else ::= */
+ 278, /* (230) case_operand ::= expr */
+ 278, /* (231) case_operand ::= */
+ 261, /* (232) exprlist ::= */
+ 253, /* (233) nexprlist ::= nexprlist COMMA expr */
+ 253, /* (234) nexprlist ::= expr */
+ 277, /* (235) paren_exprlist ::= */
+ 277, /* (236) paren_exprlist ::= LP exprlist RP */
+ 190, /* (237) cmd ::= createkw uniqueflag INDEX ifnotexists nm dbnm ON nm LP sortlist RP where_opt */
+ 281, /* (238) uniqueflag ::= UNIQUE */
+ 281, /* (239) uniqueflag ::= */
+ 221, /* (240) eidlist_opt ::= */
+ 221, /* (241) eidlist_opt ::= LP eidlist RP */
+ 232, /* (242) eidlist ::= eidlist COMMA nm collate sortorder */
+ 232, /* (243) eidlist ::= nm collate sortorder */
+ 282, /* (244) collate ::= */
+ 282, /* (245) collate ::= COLLATE ID|STRING */
+ 190, /* (246) cmd ::= DROP INDEX ifexists fullname */
+ 190, /* (247) cmd ::= VACUUM vinto */
+ 190, /* (248) cmd ::= VACUUM nm vinto */
+ 283, /* (249) vinto ::= INTO expr */
+ 283, /* (250) vinto ::= */
+ 190, /* (251) cmd ::= PRAGMA nm dbnm */
+ 190, /* (252) cmd ::= PRAGMA nm dbnm EQ nmnum */
+ 190, /* (253) cmd ::= PRAGMA nm dbnm LP nmnum RP */
+ 190, /* (254) cmd ::= PRAGMA nm dbnm EQ minus_num */
+ 190, /* (255) cmd ::= PRAGMA nm dbnm LP minus_num RP */
+ 211, /* (256) plus_num ::= PLUS INTEGER|FLOAT */
+ 212, /* (257) minus_num ::= MINUS INTEGER|FLOAT */
+ 190, /* (258) cmd ::= createkw trigger_decl BEGIN trigger_cmd_list END */
+ 285, /* (259) trigger_decl ::= temp TRIGGER ifnotexists nm dbnm trigger_time trigger_event ON fullname foreach_clause when_clause */
+ 287, /* (260) trigger_time ::= BEFORE|AFTER */
+ 287, /* (261) trigger_time ::= INSTEAD OF */
+ 287, /* (262) trigger_time ::= */
+ 288, /* (263) trigger_event ::= DELETE|INSERT */
+ 288, /* (264) trigger_event ::= UPDATE */
+ 288, /* (265) trigger_event ::= UPDATE OF idlist */
+ 290, /* (266) when_clause ::= */
+ 290, /* (267) when_clause ::= WHEN expr */
+ 286, /* (268) trigger_cmd_list ::= trigger_cmd_list trigger_cmd SEMI */
+ 286, /* (269) trigger_cmd_list ::= trigger_cmd SEMI */
+ 292, /* (270) trnm ::= nm DOT nm */
+ 293, /* (271) tridxby ::= INDEXED BY nm */
+ 293, /* (272) tridxby ::= NOT INDEXED */
+ 291, /* (273) trigger_cmd ::= UPDATE orconf trnm tridxby SET setlist from where_opt scanpt */
+ 291, /* (274) trigger_cmd ::= scanpt insert_cmd INTO trnm idlist_opt select upsert scanpt */
+ 291, /* (275) trigger_cmd ::= DELETE FROM trnm tridxby where_opt scanpt */
+ 291, /* (276) trigger_cmd ::= scanpt select scanpt */
+ 217, /* (277) expr ::= RAISE LP IGNORE RP */
+ 217, /* (278) expr ::= RAISE LP raisetype COMMA nm RP */
+ 236, /* (279) raisetype ::= ROLLBACK */
+ 236, /* (280) raisetype ::= ABORT */
+ 236, /* (281) raisetype ::= FAIL */
+ 190, /* (282) cmd ::= DROP TRIGGER ifexists fullname */
+ 190, /* (283) cmd ::= ATTACH database_kw_opt expr AS expr key_opt */
+ 190, /* (284) cmd ::= DETACH database_kw_opt expr */
+ 295, /* (285) key_opt ::= */
+ 295, /* (286) key_opt ::= KEY expr */
+ 190, /* (287) cmd ::= REINDEX */
+ 190, /* (288) cmd ::= REINDEX nm dbnm */
+ 190, /* (289) cmd ::= ANALYZE */
+ 190, /* (290) cmd ::= ANALYZE nm dbnm */
+ 190, /* (291) cmd ::= ALTER TABLE fullname RENAME TO nm */
+ 190, /* (292) cmd ::= ALTER TABLE add_column_fullname ADD kwcolumn_opt columnname carglist */
+ 190, /* (293) cmd ::= ALTER TABLE fullname DROP kwcolumn_opt nm */
+ 296, /* (294) add_column_fullname ::= fullname */
+ 190, /* (295) cmd ::= ALTER TABLE fullname RENAME kwcolumn_opt nm TO nm */
+ 190, /* (296) cmd ::= create_vtab */
+ 190, /* (297) cmd ::= create_vtab LP vtabarglist RP */
+ 298, /* (298) create_vtab ::= createkw VIRTUAL TABLE ifnotexists nm dbnm USING nm */
+ 300, /* (299) vtabarg ::= */
+ 301, /* (300) vtabargtoken ::= ANY */
+ 301, /* (301) vtabargtoken ::= lp anylist RP */
+ 302, /* (302) lp ::= LP */
+ 266, /* (303) with ::= WITH wqlist */
+ 266, /* (304) with ::= WITH RECURSIVE wqlist */
+ 305, /* (305) wqas ::= AS */
+ 305, /* (306) wqas ::= AS MATERIALIZED */
+ 305, /* (307) wqas ::= AS NOT MATERIALIZED */
+ 304, /* (308) wqitem ::= nm eidlist_opt wqas LP select RP */
+ 241, /* (309) wqlist ::= wqitem */
+ 241, /* (310) wqlist ::= wqlist COMMA wqitem */
+ 306, /* (311) windowdefn_list ::= windowdefn */
+ 306, /* (312) windowdefn_list ::= windowdefn_list COMMA windowdefn */
+ 307, /* (313) windowdefn ::= nm AS LP window RP */
+ 308, /* (314) window ::= PARTITION BY nexprlist orderby_opt frame_opt */
+ 308, /* (315) window ::= nm PARTITION BY nexprlist orderby_opt frame_opt */
+ 308, /* (316) window ::= ORDER BY sortlist frame_opt */
+ 308, /* (317) window ::= nm ORDER BY sortlist frame_opt */
+ 308, /* (318) window ::= frame_opt */
+ 308, /* (319) window ::= nm frame_opt */
+ 309, /* (320) frame_opt ::= */
+ 309, /* (321) frame_opt ::= range_or_rows frame_bound_s frame_exclude_opt */
+ 309, /* (322) frame_opt ::= range_or_rows BETWEEN frame_bound_s AND frame_bound_e frame_exclude_opt */
+ 313, /* (323) range_or_rows ::= RANGE|ROWS|GROUPS */
+ 315, /* (324) frame_bound_s ::= frame_bound */
+ 315, /* (325) frame_bound_s ::= UNBOUNDED PRECEDING */
+ 316, /* (326) frame_bound_e ::= frame_bound */
+ 316, /* (327) frame_bound_e ::= UNBOUNDED FOLLOWING */
+ 314, /* (328) frame_bound ::= expr PRECEDING|FOLLOWING */
+ 314, /* (329) frame_bound ::= CURRENT ROW */
+ 317, /* (330) frame_exclude_opt ::= */
+ 317, /* (331) frame_exclude_opt ::= EXCLUDE frame_exclude */
+ 318, /* (332) frame_exclude ::= NO OTHERS */
+ 318, /* (333) frame_exclude ::= CURRENT ROW */
+ 318, /* (334) frame_exclude ::= GROUP|TIES */
+ 251, /* (335) window_clause ::= WINDOW windowdefn_list */
+ 273, /* (336) filter_over ::= filter_clause over_clause */
+ 273, /* (337) filter_over ::= over_clause */
+ 273, /* (338) filter_over ::= filter_clause */
+ 312, /* (339) over_clause ::= OVER LP window RP */
+ 312, /* (340) over_clause ::= OVER nm */
+ 311, /* (341) filter_clause ::= FILTER LP WHERE expr RP */
+ 185, /* (342) input ::= cmdlist */
+ 186, /* (343) cmdlist ::= cmdlist ecmd */
+ 186, /* (344) cmdlist ::= ecmd */
+ 187, /* (345) ecmd ::= SEMI */
+ 187, /* (346) ecmd ::= cmdx SEMI */
+ 187, /* (347) ecmd ::= explain cmdx SEMI */
+ 192, /* (348) trans_opt ::= */
+ 192, /* (349) trans_opt ::= TRANSACTION */
+ 192, /* (350) trans_opt ::= TRANSACTION nm */
+ 194, /* (351) savepoint_opt ::= SAVEPOINT */
+ 194, /* (352) savepoint_opt ::= */
+ 190, /* (353) cmd ::= create_table create_table_args */
+ 203, /* (354) table_option_set ::= table_option */
+ 201, /* (355) columnlist ::= columnlist COMMA columnname carglist */
+ 201, /* (356) columnlist ::= columnname carglist */
+ 193, /* (357) nm ::= ID|INDEXED */
+ 193, /* (358) nm ::= STRING */
+ 193, /* (359) nm ::= JOIN_KW */
+ 208, /* (360) typetoken ::= typename */
+ 209, /* (361) typename ::= ID|STRING */
+ 210, /* (362) signed ::= plus_num */
+ 210, /* (363) signed ::= minus_num */
+ 207, /* (364) carglist ::= carglist ccons */
+ 207, /* (365) carglist ::= */
+ 215, /* (366) ccons ::= NULL onconf */
+ 215, /* (367) ccons ::= GENERATED ALWAYS AS generated */
+ 215, /* (368) ccons ::= AS generated */
+ 202, /* (369) conslist_opt ::= COMMA conslist */
+ 228, /* (370) conslist ::= conslist tconscomma tcons */
+ 228, /* (371) conslist ::= tcons */
+ 229, /* (372) tconscomma ::= */
+ 233, /* (373) defer_subclause_opt ::= defer_subclause */
+ 235, /* (374) resolvetype ::= raisetype */
+ 239, /* (375) selectnowith ::= oneselect */
+ 240, /* (376) oneselect ::= values */
+ 254, /* (377) sclp ::= selcollist COMMA */
+ 255, /* (378) as ::= ID|STRING */
+ 264, /* (379) indexed_opt ::= indexed_by */
+ 272, /* (380) returning ::= */
+ 217, /* (381) expr ::= term */
+ 274, /* (382) likeop ::= LIKE_KW|MATCH */
+ 261, /* (383) exprlist ::= nexprlist */
+ 284, /* (384) nmnum ::= plus_num */
+ 284, /* (385) nmnum ::= nm */
+ 284, /* (386) nmnum ::= ON */
+ 284, /* (387) nmnum ::= DELETE */
+ 284, /* (388) nmnum ::= DEFAULT */
+ 211, /* (389) plus_num ::= INTEGER|FLOAT */
+ 289, /* (390) foreach_clause ::= */
+ 289, /* (391) foreach_clause ::= FOR EACH ROW */
+ 292, /* (392) trnm ::= nm */
+ 293, /* (393) tridxby ::= */
+ 294, /* (394) database_kw_opt ::= DATABASE */
+ 294, /* (395) database_kw_opt ::= */
+ 297, /* (396) kwcolumn_opt ::= */
+ 297, /* (397) kwcolumn_opt ::= COLUMNKW */
+ 299, /* (398) vtabarglist ::= vtabarg */
+ 299, /* (399) vtabarglist ::= vtabarglist COMMA vtabarg */
+ 300, /* (400) vtabarg ::= vtabarg vtabargtoken */
+ 303, /* (401) anylist ::= */
+ 303, /* (402) anylist ::= anylist LP anylist RP */
+ 303, /* (403) anylist ::= anylist ANY */
+ 266, /* (404) with ::= */
};
/* For rule J, yyRuleInfoNRhs[J] contains the negative of the number
@@ -164648,29 +168570,29 @@ static const signed char yyRuleInfoNRhs[] = {
-2, /* (106) from ::= FROM seltablist */
-2, /* (107) stl_prefix ::= seltablist joinop */
0, /* (108) stl_prefix ::= */
- -7, /* (109) seltablist ::= stl_prefix nm dbnm as indexed_opt on_opt using_opt */
- -9, /* (110) seltablist ::= stl_prefix nm dbnm LP exprlist RP as on_opt using_opt */
- -7, /* (111) seltablist ::= stl_prefix LP select RP as on_opt using_opt */
- -7, /* (112) seltablist ::= stl_prefix LP seltablist RP as on_opt using_opt */
- 0, /* (113) dbnm ::= */
- -2, /* (114) dbnm ::= DOT nm */
- -1, /* (115) fullname ::= nm */
- -3, /* (116) fullname ::= nm DOT nm */
- -1, /* (117) xfullname ::= nm */
- -3, /* (118) xfullname ::= nm DOT nm */
- -5, /* (119) xfullname ::= nm DOT nm AS nm */
- -3, /* (120) xfullname ::= nm AS nm */
- -1, /* (121) joinop ::= COMMA|JOIN */
- -2, /* (122) joinop ::= JOIN_KW JOIN */
- -3, /* (123) joinop ::= JOIN_KW nm JOIN */
- -4, /* (124) joinop ::= JOIN_KW nm nm JOIN */
- -2, /* (125) on_opt ::= ON expr */
- 0, /* (126) on_opt ::= */
- 0, /* (127) indexed_opt ::= */
- -3, /* (128) indexed_opt ::= INDEXED BY nm */
- -2, /* (129) indexed_opt ::= NOT INDEXED */
- -4, /* (130) using_opt ::= USING LP idlist RP */
- 0, /* (131) using_opt ::= */
+ -5, /* (109) seltablist ::= stl_prefix nm dbnm as on_using */
+ -6, /* (110) seltablist ::= stl_prefix nm dbnm as indexed_by on_using */
+ -8, /* (111) seltablist ::= stl_prefix nm dbnm LP exprlist RP as on_using */
+ -6, /* (112) seltablist ::= stl_prefix LP select RP as on_using */
+ -6, /* (113) seltablist ::= stl_prefix LP seltablist RP as on_using */
+ 0, /* (114) dbnm ::= */
+ -2, /* (115) dbnm ::= DOT nm */
+ -1, /* (116) fullname ::= nm */
+ -3, /* (117) fullname ::= nm DOT nm */
+ -1, /* (118) xfullname ::= nm */
+ -3, /* (119) xfullname ::= nm DOT nm */
+ -5, /* (120) xfullname ::= nm DOT nm AS nm */
+ -3, /* (121) xfullname ::= nm AS nm */
+ -1, /* (122) joinop ::= COMMA|JOIN */
+ -2, /* (123) joinop ::= JOIN_KW JOIN */
+ -3, /* (124) joinop ::= JOIN_KW nm JOIN */
+ -4, /* (125) joinop ::= JOIN_KW nm nm JOIN */
+ -2, /* (126) on_using ::= ON expr */
+ -4, /* (127) on_using ::= USING LP idlist RP */
+ 0, /* (128) on_using ::= */
+ 0, /* (129) indexed_opt ::= */
+ -3, /* (130) indexed_by ::= INDEXED BY nm */
+ -2, /* (131) indexed_by ::= NOT INDEXED */
0, /* (132) orderby_opt ::= */
-3, /* (133) orderby_opt ::= ORDER BY sortlist */
-5, /* (134) sortlist ::= sortlist COMMA expr sortorder nulls */
@@ -164748,199 +168670,202 @@ static const signed char yyRuleInfoNRhs[] = {
-3, /* (206) expr ::= expr NOT NULL */
-3, /* (207) expr ::= expr IS expr */
-4, /* (208) expr ::= expr IS NOT expr */
- -2, /* (209) expr ::= NOT expr */
- -2, /* (210) expr ::= BITNOT expr */
- -2, /* (211) expr ::= PLUS|MINUS expr */
- -3, /* (212) expr ::= expr PTR expr */
- -1, /* (213) between_op ::= BETWEEN */
- -2, /* (214) between_op ::= NOT BETWEEN */
- -5, /* (215) expr ::= expr between_op expr AND expr */
- -1, /* (216) in_op ::= IN */
- -2, /* (217) in_op ::= NOT IN */
- -5, /* (218) expr ::= expr in_op LP exprlist RP */
- -3, /* (219) expr ::= LP select RP */
- -5, /* (220) expr ::= expr in_op LP select RP */
- -5, /* (221) expr ::= expr in_op nm dbnm paren_exprlist */
- -4, /* (222) expr ::= EXISTS LP select RP */
- -5, /* (223) expr ::= CASE case_operand case_exprlist case_else END */
- -5, /* (224) case_exprlist ::= case_exprlist WHEN expr THEN expr */
- -4, /* (225) case_exprlist ::= WHEN expr THEN expr */
- -2, /* (226) case_else ::= ELSE expr */
- 0, /* (227) case_else ::= */
- -1, /* (228) case_operand ::= expr */
- 0, /* (229) case_operand ::= */
- 0, /* (230) exprlist ::= */
- -3, /* (231) nexprlist ::= nexprlist COMMA expr */
- -1, /* (232) nexprlist ::= expr */
- 0, /* (233) paren_exprlist ::= */
- -3, /* (234) paren_exprlist ::= LP exprlist RP */
- -12, /* (235) cmd ::= createkw uniqueflag INDEX ifnotexists nm dbnm ON nm LP sortlist RP where_opt */
- -1, /* (236) uniqueflag ::= UNIQUE */
- 0, /* (237) uniqueflag ::= */
- 0, /* (238) eidlist_opt ::= */
- -3, /* (239) eidlist_opt ::= LP eidlist RP */
- -5, /* (240) eidlist ::= eidlist COMMA nm collate sortorder */
- -3, /* (241) eidlist ::= nm collate sortorder */
- 0, /* (242) collate ::= */
- -2, /* (243) collate ::= COLLATE ID|STRING */
- -4, /* (244) cmd ::= DROP INDEX ifexists fullname */
- -2, /* (245) cmd ::= VACUUM vinto */
- -3, /* (246) cmd ::= VACUUM nm vinto */
- -2, /* (247) vinto ::= INTO expr */
- 0, /* (248) vinto ::= */
- -3, /* (249) cmd ::= PRAGMA nm dbnm */
- -5, /* (250) cmd ::= PRAGMA nm dbnm EQ nmnum */
- -6, /* (251) cmd ::= PRAGMA nm dbnm LP nmnum RP */
- -5, /* (252) cmd ::= PRAGMA nm dbnm EQ minus_num */
- -6, /* (253) cmd ::= PRAGMA nm dbnm LP minus_num RP */
- -2, /* (254) plus_num ::= PLUS INTEGER|FLOAT */
- -2, /* (255) minus_num ::= MINUS INTEGER|FLOAT */
- -5, /* (256) cmd ::= createkw trigger_decl BEGIN trigger_cmd_list END */
- -11, /* (257) trigger_decl ::= temp TRIGGER ifnotexists nm dbnm trigger_time trigger_event ON fullname foreach_clause when_clause */
- -1, /* (258) trigger_time ::= BEFORE|AFTER */
- -2, /* (259) trigger_time ::= INSTEAD OF */
- 0, /* (260) trigger_time ::= */
- -1, /* (261) trigger_event ::= DELETE|INSERT */
- -1, /* (262) trigger_event ::= UPDATE */
- -3, /* (263) trigger_event ::= UPDATE OF idlist */
- 0, /* (264) when_clause ::= */
- -2, /* (265) when_clause ::= WHEN expr */
- -3, /* (266) trigger_cmd_list ::= trigger_cmd_list trigger_cmd SEMI */
- -2, /* (267) trigger_cmd_list ::= trigger_cmd SEMI */
- -3, /* (268) trnm ::= nm DOT nm */
- -3, /* (269) tridxby ::= INDEXED BY nm */
- -2, /* (270) tridxby ::= NOT INDEXED */
- -9, /* (271) trigger_cmd ::= UPDATE orconf trnm tridxby SET setlist from where_opt scanpt */
- -8, /* (272) trigger_cmd ::= scanpt insert_cmd INTO trnm idlist_opt select upsert scanpt */
- -6, /* (273) trigger_cmd ::= DELETE FROM trnm tridxby where_opt scanpt */
- -3, /* (274) trigger_cmd ::= scanpt select scanpt */
- -4, /* (275) expr ::= RAISE LP IGNORE RP */
- -6, /* (276) expr ::= RAISE LP raisetype COMMA nm RP */
- -1, /* (277) raisetype ::= ROLLBACK */
- -1, /* (278) raisetype ::= ABORT */
- -1, /* (279) raisetype ::= FAIL */
- -4, /* (280) cmd ::= DROP TRIGGER ifexists fullname */
- -6, /* (281) cmd ::= ATTACH database_kw_opt expr AS expr key_opt */
- -3, /* (282) cmd ::= DETACH database_kw_opt expr */
- 0, /* (283) key_opt ::= */
- -2, /* (284) key_opt ::= KEY expr */
- -1, /* (285) cmd ::= REINDEX */
- -3, /* (286) cmd ::= REINDEX nm dbnm */
- -1, /* (287) cmd ::= ANALYZE */
- -3, /* (288) cmd ::= ANALYZE nm dbnm */
- -6, /* (289) cmd ::= ALTER TABLE fullname RENAME TO nm */
- -7, /* (290) cmd ::= ALTER TABLE add_column_fullname ADD kwcolumn_opt columnname carglist */
- -6, /* (291) cmd ::= ALTER TABLE fullname DROP kwcolumn_opt nm */
- -1, /* (292) add_column_fullname ::= fullname */
- -8, /* (293) cmd ::= ALTER TABLE fullname RENAME kwcolumn_opt nm TO nm */
- -1, /* (294) cmd ::= create_vtab */
- -4, /* (295) cmd ::= create_vtab LP vtabarglist RP */
- -8, /* (296) create_vtab ::= createkw VIRTUAL TABLE ifnotexists nm dbnm USING nm */
- 0, /* (297) vtabarg ::= */
- -1, /* (298) vtabargtoken ::= ANY */
- -3, /* (299) vtabargtoken ::= lp anylist RP */
- -1, /* (300) lp ::= LP */
- -2, /* (301) with ::= WITH wqlist */
- -3, /* (302) with ::= WITH RECURSIVE wqlist */
- -1, /* (303) wqas ::= AS */
- -2, /* (304) wqas ::= AS MATERIALIZED */
- -3, /* (305) wqas ::= AS NOT MATERIALIZED */
- -6, /* (306) wqitem ::= nm eidlist_opt wqas LP select RP */
- -1, /* (307) wqlist ::= wqitem */
- -3, /* (308) wqlist ::= wqlist COMMA wqitem */
- -1, /* (309) windowdefn_list ::= windowdefn */
- -3, /* (310) windowdefn_list ::= windowdefn_list COMMA windowdefn */
- -5, /* (311) windowdefn ::= nm AS LP window RP */
- -5, /* (312) window ::= PARTITION BY nexprlist orderby_opt frame_opt */
- -6, /* (313) window ::= nm PARTITION BY nexprlist orderby_opt frame_opt */
- -4, /* (314) window ::= ORDER BY sortlist frame_opt */
- -5, /* (315) window ::= nm ORDER BY sortlist frame_opt */
- -1, /* (316) window ::= frame_opt */
- -2, /* (317) window ::= nm frame_opt */
- 0, /* (318) frame_opt ::= */
- -3, /* (319) frame_opt ::= range_or_rows frame_bound_s frame_exclude_opt */
- -6, /* (320) frame_opt ::= range_or_rows BETWEEN frame_bound_s AND frame_bound_e frame_exclude_opt */
- -1, /* (321) range_or_rows ::= RANGE|ROWS|GROUPS */
- -1, /* (322) frame_bound_s ::= frame_bound */
- -2, /* (323) frame_bound_s ::= UNBOUNDED PRECEDING */
- -1, /* (324) frame_bound_e ::= frame_bound */
- -2, /* (325) frame_bound_e ::= UNBOUNDED FOLLOWING */
- -2, /* (326) frame_bound ::= expr PRECEDING|FOLLOWING */
- -2, /* (327) frame_bound ::= CURRENT ROW */
- 0, /* (328) frame_exclude_opt ::= */
- -2, /* (329) frame_exclude_opt ::= EXCLUDE frame_exclude */
- -2, /* (330) frame_exclude ::= NO OTHERS */
- -2, /* (331) frame_exclude ::= CURRENT ROW */
- -1, /* (332) frame_exclude ::= GROUP|TIES */
- -2, /* (333) window_clause ::= WINDOW windowdefn_list */
- -2, /* (334) filter_over ::= filter_clause over_clause */
- -1, /* (335) filter_over ::= over_clause */
- -1, /* (336) filter_over ::= filter_clause */
- -4, /* (337) over_clause ::= OVER LP window RP */
- -2, /* (338) over_clause ::= OVER nm */
- -5, /* (339) filter_clause ::= FILTER LP WHERE expr RP */
- -1, /* (340) input ::= cmdlist */
- -2, /* (341) cmdlist ::= cmdlist ecmd */
- -1, /* (342) cmdlist ::= ecmd */
- -1, /* (343) ecmd ::= SEMI */
- -2, /* (344) ecmd ::= cmdx SEMI */
- -3, /* (345) ecmd ::= explain cmdx SEMI */
- 0, /* (346) trans_opt ::= */
- -1, /* (347) trans_opt ::= TRANSACTION */
- -2, /* (348) trans_opt ::= TRANSACTION nm */
- -1, /* (349) savepoint_opt ::= SAVEPOINT */
- 0, /* (350) savepoint_opt ::= */
- -2, /* (351) cmd ::= create_table create_table_args */
- -1, /* (352) table_option_set ::= table_option */
- -4, /* (353) columnlist ::= columnlist COMMA columnname carglist */
- -2, /* (354) columnlist ::= columnname carglist */
- -1, /* (355) nm ::= ID|INDEXED */
- -1, /* (356) nm ::= STRING */
- -1, /* (357) nm ::= JOIN_KW */
- -1, /* (358) typetoken ::= typename */
- -1, /* (359) typename ::= ID|STRING */
- -1, /* (360) signed ::= plus_num */
- -1, /* (361) signed ::= minus_num */
- -2, /* (362) carglist ::= carglist ccons */
- 0, /* (363) carglist ::= */
- -2, /* (364) ccons ::= NULL onconf */
- -4, /* (365) ccons ::= GENERATED ALWAYS AS generated */
- -2, /* (366) ccons ::= AS generated */
- -2, /* (367) conslist_opt ::= COMMA conslist */
- -3, /* (368) conslist ::= conslist tconscomma tcons */
- -1, /* (369) conslist ::= tcons */
- 0, /* (370) tconscomma ::= */
- -1, /* (371) defer_subclause_opt ::= defer_subclause */
- -1, /* (372) resolvetype ::= raisetype */
- -1, /* (373) selectnowith ::= oneselect */
- -1, /* (374) oneselect ::= values */
- -2, /* (375) sclp ::= selcollist COMMA */
- -1, /* (376) as ::= ID|STRING */
- 0, /* (377) returning ::= */
- -1, /* (378) expr ::= term */
- -1, /* (379) likeop ::= LIKE_KW|MATCH */
- -1, /* (380) exprlist ::= nexprlist */
- -1, /* (381) nmnum ::= plus_num */
- -1, /* (382) nmnum ::= nm */
- -1, /* (383) nmnum ::= ON */
- -1, /* (384) nmnum ::= DELETE */
- -1, /* (385) nmnum ::= DEFAULT */
- -1, /* (386) plus_num ::= INTEGER|FLOAT */
- 0, /* (387) foreach_clause ::= */
- -3, /* (388) foreach_clause ::= FOR EACH ROW */
- -1, /* (389) trnm ::= nm */
- 0, /* (390) tridxby ::= */
- -1, /* (391) database_kw_opt ::= DATABASE */
- 0, /* (392) database_kw_opt ::= */
- 0, /* (393) kwcolumn_opt ::= */
- -1, /* (394) kwcolumn_opt ::= COLUMNKW */
- -1, /* (395) vtabarglist ::= vtabarg */
- -3, /* (396) vtabarglist ::= vtabarglist COMMA vtabarg */
- -2, /* (397) vtabarg ::= vtabarg vtabargtoken */
- 0, /* (398) anylist ::= */
- -4, /* (399) anylist ::= anylist LP anylist RP */
- -2, /* (400) anylist ::= anylist ANY */
- 0, /* (401) with ::= */
+ -6, /* (209) expr ::= expr IS NOT DISTINCT FROM expr */
+ -5, /* (210) expr ::= expr IS DISTINCT FROM expr */
+ -2, /* (211) expr ::= NOT expr */
+ -2, /* (212) expr ::= BITNOT expr */
+ -2, /* (213) expr ::= PLUS|MINUS expr */
+ -3, /* (214) expr ::= expr PTR expr */
+ -1, /* (215) between_op ::= BETWEEN */
+ -2, /* (216) between_op ::= NOT BETWEEN */
+ -5, /* (217) expr ::= expr between_op expr AND expr */
+ -1, /* (218) in_op ::= IN */
+ -2, /* (219) in_op ::= NOT IN */
+ -5, /* (220) expr ::= expr in_op LP exprlist RP */
+ -3, /* (221) expr ::= LP select RP */
+ -5, /* (222) expr ::= expr in_op LP select RP */
+ -5, /* (223) expr ::= expr in_op nm dbnm paren_exprlist */
+ -4, /* (224) expr ::= EXISTS LP select RP */
+ -5, /* (225) expr ::= CASE case_operand case_exprlist case_else END */
+ -5, /* (226) case_exprlist ::= case_exprlist WHEN expr THEN expr */
+ -4, /* (227) case_exprlist ::= WHEN expr THEN expr */
+ -2, /* (228) case_else ::= ELSE expr */
+ 0, /* (229) case_else ::= */
+ -1, /* (230) case_operand ::= expr */
+ 0, /* (231) case_operand ::= */
+ 0, /* (232) exprlist ::= */
+ -3, /* (233) nexprlist ::= nexprlist COMMA expr */
+ -1, /* (234) nexprlist ::= expr */
+ 0, /* (235) paren_exprlist ::= */
+ -3, /* (236) paren_exprlist ::= LP exprlist RP */
+ -12, /* (237) cmd ::= createkw uniqueflag INDEX ifnotexists nm dbnm ON nm LP sortlist RP where_opt */
+ -1, /* (238) uniqueflag ::= UNIQUE */
+ 0, /* (239) uniqueflag ::= */
+ 0, /* (240) eidlist_opt ::= */
+ -3, /* (241) eidlist_opt ::= LP eidlist RP */
+ -5, /* (242) eidlist ::= eidlist COMMA nm collate sortorder */
+ -3, /* (243) eidlist ::= nm collate sortorder */
+ 0, /* (244) collate ::= */
+ -2, /* (245) collate ::= COLLATE ID|STRING */
+ -4, /* (246) cmd ::= DROP INDEX ifexists fullname */
+ -2, /* (247) cmd ::= VACUUM vinto */
+ -3, /* (248) cmd ::= VACUUM nm vinto */
+ -2, /* (249) vinto ::= INTO expr */
+ 0, /* (250) vinto ::= */
+ -3, /* (251) cmd ::= PRAGMA nm dbnm */
+ -5, /* (252) cmd ::= PRAGMA nm dbnm EQ nmnum */
+ -6, /* (253) cmd ::= PRAGMA nm dbnm LP nmnum RP */
+ -5, /* (254) cmd ::= PRAGMA nm dbnm EQ minus_num */
+ -6, /* (255) cmd ::= PRAGMA nm dbnm LP minus_num RP */
+ -2, /* (256) plus_num ::= PLUS INTEGER|FLOAT */
+ -2, /* (257) minus_num ::= MINUS INTEGER|FLOAT */
+ -5, /* (258) cmd ::= createkw trigger_decl BEGIN trigger_cmd_list END */
+ -11, /* (259) trigger_decl ::= temp TRIGGER ifnotexists nm dbnm trigger_time trigger_event ON fullname foreach_clause when_clause */
+ -1, /* (260) trigger_time ::= BEFORE|AFTER */
+ -2, /* (261) trigger_time ::= INSTEAD OF */
+ 0, /* (262) trigger_time ::= */
+ -1, /* (263) trigger_event ::= DELETE|INSERT */
+ -1, /* (264) trigger_event ::= UPDATE */
+ -3, /* (265) trigger_event ::= UPDATE OF idlist */
+ 0, /* (266) when_clause ::= */
+ -2, /* (267) when_clause ::= WHEN expr */
+ -3, /* (268) trigger_cmd_list ::= trigger_cmd_list trigger_cmd SEMI */
+ -2, /* (269) trigger_cmd_list ::= trigger_cmd SEMI */
+ -3, /* (270) trnm ::= nm DOT nm */
+ -3, /* (271) tridxby ::= INDEXED BY nm */
+ -2, /* (272) tridxby ::= NOT INDEXED */
+ -9, /* (273) trigger_cmd ::= UPDATE orconf trnm tridxby SET setlist from where_opt scanpt */
+ -8, /* (274) trigger_cmd ::= scanpt insert_cmd INTO trnm idlist_opt select upsert scanpt */
+ -6, /* (275) trigger_cmd ::= DELETE FROM trnm tridxby where_opt scanpt */
+ -3, /* (276) trigger_cmd ::= scanpt select scanpt */
+ -4, /* (277) expr ::= RAISE LP IGNORE RP */
+ -6, /* (278) expr ::= RAISE LP raisetype COMMA nm RP */
+ -1, /* (279) raisetype ::= ROLLBACK */
+ -1, /* (280) raisetype ::= ABORT */
+ -1, /* (281) raisetype ::= FAIL */
+ -4, /* (282) cmd ::= DROP TRIGGER ifexists fullname */
+ -6, /* (283) cmd ::= ATTACH database_kw_opt expr AS expr key_opt */
+ -3, /* (284) cmd ::= DETACH database_kw_opt expr */
+ 0, /* (285) key_opt ::= */
+ -2, /* (286) key_opt ::= KEY expr */
+ -1, /* (287) cmd ::= REINDEX */
+ -3, /* (288) cmd ::= REINDEX nm dbnm */
+ -1, /* (289) cmd ::= ANALYZE */
+ -3, /* (290) cmd ::= ANALYZE nm dbnm */
+ -6, /* (291) cmd ::= ALTER TABLE fullname RENAME TO nm */
+ -7, /* (292) cmd ::= ALTER TABLE add_column_fullname ADD kwcolumn_opt columnname carglist */
+ -6, /* (293) cmd ::= ALTER TABLE fullname DROP kwcolumn_opt nm */
+ -1, /* (294) add_column_fullname ::= fullname */
+ -8, /* (295) cmd ::= ALTER TABLE fullname RENAME kwcolumn_opt nm TO nm */
+ -1, /* (296) cmd ::= create_vtab */
+ -4, /* (297) cmd ::= create_vtab LP vtabarglist RP */
+ -8, /* (298) create_vtab ::= createkw VIRTUAL TABLE ifnotexists nm dbnm USING nm */
+ 0, /* (299) vtabarg ::= */
+ -1, /* (300) vtabargtoken ::= ANY */
+ -3, /* (301) vtabargtoken ::= lp anylist RP */
+ -1, /* (302) lp ::= LP */
+ -2, /* (303) with ::= WITH wqlist */
+ -3, /* (304) with ::= WITH RECURSIVE wqlist */
+ -1, /* (305) wqas ::= AS */
+ -2, /* (306) wqas ::= AS MATERIALIZED */
+ -3, /* (307) wqas ::= AS NOT MATERIALIZED */
+ -6, /* (308) wqitem ::= nm eidlist_opt wqas LP select RP */
+ -1, /* (309) wqlist ::= wqitem */
+ -3, /* (310) wqlist ::= wqlist COMMA wqitem */
+ -1, /* (311) windowdefn_list ::= windowdefn */
+ -3, /* (312) windowdefn_list ::= windowdefn_list COMMA windowdefn */
+ -5, /* (313) windowdefn ::= nm AS LP window RP */
+ -5, /* (314) window ::= PARTITION BY nexprlist orderby_opt frame_opt */
+ -6, /* (315) window ::= nm PARTITION BY nexprlist orderby_opt frame_opt */
+ -4, /* (316) window ::= ORDER BY sortlist frame_opt */
+ -5, /* (317) window ::= nm ORDER BY sortlist frame_opt */
+ -1, /* (318) window ::= frame_opt */
+ -2, /* (319) window ::= nm frame_opt */
+ 0, /* (320) frame_opt ::= */
+ -3, /* (321) frame_opt ::= range_or_rows frame_bound_s frame_exclude_opt */
+ -6, /* (322) frame_opt ::= range_or_rows BETWEEN frame_bound_s AND frame_bound_e frame_exclude_opt */
+ -1, /* (323) range_or_rows ::= RANGE|ROWS|GROUPS */
+ -1, /* (324) frame_bound_s ::= frame_bound */
+ -2, /* (325) frame_bound_s ::= UNBOUNDED PRECEDING */
+ -1, /* (326) frame_bound_e ::= frame_bound */
+ -2, /* (327) frame_bound_e ::= UNBOUNDED FOLLOWING */
+ -2, /* (328) frame_bound ::= expr PRECEDING|FOLLOWING */
+ -2, /* (329) frame_bound ::= CURRENT ROW */
+ 0, /* (330) frame_exclude_opt ::= */
+ -2, /* (331) frame_exclude_opt ::= EXCLUDE frame_exclude */
+ -2, /* (332) frame_exclude ::= NO OTHERS */
+ -2, /* (333) frame_exclude ::= CURRENT ROW */
+ -1, /* (334) frame_exclude ::= GROUP|TIES */
+ -2, /* (335) window_clause ::= WINDOW windowdefn_list */
+ -2, /* (336) filter_over ::= filter_clause over_clause */
+ -1, /* (337) filter_over ::= over_clause */
+ -1, /* (338) filter_over ::= filter_clause */
+ -4, /* (339) over_clause ::= OVER LP window RP */
+ -2, /* (340) over_clause ::= OVER nm */
+ -5, /* (341) filter_clause ::= FILTER LP WHERE expr RP */
+ -1, /* (342) input ::= cmdlist */
+ -2, /* (343) cmdlist ::= cmdlist ecmd */
+ -1, /* (344) cmdlist ::= ecmd */
+ -1, /* (345) ecmd ::= SEMI */
+ -2, /* (346) ecmd ::= cmdx SEMI */
+ -3, /* (347) ecmd ::= explain cmdx SEMI */
+ 0, /* (348) trans_opt ::= */
+ -1, /* (349) trans_opt ::= TRANSACTION */
+ -2, /* (350) trans_opt ::= TRANSACTION nm */
+ -1, /* (351) savepoint_opt ::= SAVEPOINT */
+ 0, /* (352) savepoint_opt ::= */
+ -2, /* (353) cmd ::= create_table create_table_args */
+ -1, /* (354) table_option_set ::= table_option */
+ -4, /* (355) columnlist ::= columnlist COMMA columnname carglist */
+ -2, /* (356) columnlist ::= columnname carglist */
+ -1, /* (357) nm ::= ID|INDEXED */
+ -1, /* (358) nm ::= STRING */
+ -1, /* (359) nm ::= JOIN_KW */
+ -1, /* (360) typetoken ::= typename */
+ -1, /* (361) typename ::= ID|STRING */
+ -1, /* (362) signed ::= plus_num */
+ -1, /* (363) signed ::= minus_num */
+ -2, /* (364) carglist ::= carglist ccons */
+ 0, /* (365) carglist ::= */
+ -2, /* (366) ccons ::= NULL onconf */
+ -4, /* (367) ccons ::= GENERATED ALWAYS AS generated */
+ -2, /* (368) ccons ::= AS generated */
+ -2, /* (369) conslist_opt ::= COMMA conslist */
+ -3, /* (370) conslist ::= conslist tconscomma tcons */
+ -1, /* (371) conslist ::= tcons */
+ 0, /* (372) tconscomma ::= */
+ -1, /* (373) defer_subclause_opt ::= defer_subclause */
+ -1, /* (374) resolvetype ::= raisetype */
+ -1, /* (375) selectnowith ::= oneselect */
+ -1, /* (376) oneselect ::= values */
+ -2, /* (377) sclp ::= selcollist COMMA */
+ -1, /* (378) as ::= ID|STRING */
+ -1, /* (379) indexed_opt ::= indexed_by */
+ 0, /* (380) returning ::= */
+ -1, /* (381) expr ::= term */
+ -1, /* (382) likeop ::= LIKE_KW|MATCH */
+ -1, /* (383) exprlist ::= nexprlist */
+ -1, /* (384) nmnum ::= plus_num */
+ -1, /* (385) nmnum ::= nm */
+ -1, /* (386) nmnum ::= ON */
+ -1, /* (387) nmnum ::= DELETE */
+ -1, /* (388) nmnum ::= DEFAULT */
+ -1, /* (389) plus_num ::= INTEGER|FLOAT */
+ 0, /* (390) foreach_clause ::= */
+ -3, /* (391) foreach_clause ::= FOR EACH ROW */
+ -1, /* (392) trnm ::= nm */
+ 0, /* (393) tridxby ::= */
+ -1, /* (394) database_kw_opt ::= DATABASE */
+ 0, /* (395) database_kw_opt ::= */
+ 0, /* (396) kwcolumn_opt ::= */
+ -1, /* (397) kwcolumn_opt ::= COLUMNKW */
+ -1, /* (398) vtabarglist ::= vtabarg */
+ -3, /* (399) vtabarglist ::= vtabarglist COMMA vtabarg */
+ -2, /* (400) vtabarg ::= vtabarg vtabargtoken */
+ 0, /* (401) anylist ::= */
+ -4, /* (402) anylist ::= anylist LP anylist RP */
+ -2, /* (403) anylist ::= anylist ANY */
+ 0, /* (404) with ::= */
};
static void yy_accept(yyParser*); /* Forward Declaration */
@@ -165000,7 +168925,7 @@ static YYACTIONTYPE yy_reduce(
case 5: /* transtype ::= DEFERRED */
case 6: /* transtype ::= IMMEDIATE */ yytestcase(yyruleno==6);
case 7: /* transtype ::= EXCLUSIVE */ yytestcase(yyruleno==7);
- case 321: /* range_or_rows ::= RANGE|ROWS|GROUPS */ yytestcase(yyruleno==321);
+ case 323: /* range_or_rows ::= RANGE|ROWS|GROUPS */ yytestcase(yyruleno==323);
{yymsp[0].minor.yy394 = yymsp[0].major; /*A-overwrites-X*/}
break;
case 8: /* cmd ::= COMMIT|END trans_opt */
@@ -165037,7 +168962,7 @@ static YYACTIONTYPE yy_reduce(
case 72: /* defer_subclause_opt ::= */ yytestcase(yyruleno==72);
case 81: /* ifexists ::= */ yytestcase(yyruleno==81);
case 98: /* distinct ::= */ yytestcase(yyruleno==98);
- case 242: /* collate ::= */ yytestcase(yyruleno==242);
+ case 244: /* collate ::= */ yytestcase(yyruleno==244);
{yymsp[1].minor.yy394 = 0;}
break;
case 16: /* ifnotexists ::= IF NOT EXISTS */
@@ -165221,9 +169146,9 @@ static YYACTIONTYPE yy_reduce(
break;
case 63: /* init_deferred_pred_opt ::= INITIALLY DEFERRED */
case 80: /* ifexists ::= IF EXISTS */ yytestcase(yyruleno==80);
- case 214: /* between_op ::= NOT BETWEEN */ yytestcase(yyruleno==214);
- case 217: /* in_op ::= NOT IN */ yytestcase(yyruleno==217);
- case 243: /* collate ::= COLLATE ID|STRING */ yytestcase(yyruleno==243);
+ case 216: /* between_op ::= NOT BETWEEN */ yytestcase(yyruleno==216);
+ case 219: /* in_op ::= NOT IN */ yytestcase(yyruleno==219);
+ case 245: /* collate ::= COLLATE ID|STRING */ yytestcase(yyruleno==245);
{yymsp[-1].minor.yy394 = 1;}
break;
case 64: /* init_deferred_pred_opt ::= INITIALLY IMMEDIATE */
@@ -165308,7 +169233,7 @@ static YYACTIONTYPE yy_reduce(
Token x;
x.n = 0;
parserDoubleLinkSelect(pParse, pRhs);
- pFrom = sqlite3SrcListAppendFromTerm(pParse,0,0,0,&x,pRhs,0,0);
+ pFrom = sqlite3SrcListAppendFromTerm(pParse,0,0,0,&x,pRhs,0);
pRhs = sqlite3SelectNew(pParse,0,pFrom,0,0,0,0,0,0);
}
if( pRhs ){
@@ -165373,9 +169298,9 @@ static YYACTIONTYPE yy_reduce(
case 99: /* sclp ::= */
case 132: /* orderby_opt ::= */ yytestcase(yyruleno==132);
case 142: /* groupby_opt ::= */ yytestcase(yyruleno==142);
- case 230: /* exprlist ::= */ yytestcase(yyruleno==230);
- case 233: /* paren_exprlist ::= */ yytestcase(yyruleno==233);
- case 238: /* eidlist_opt ::= */ yytestcase(yyruleno==238);
+ case 232: /* exprlist ::= */ yytestcase(yyruleno==232);
+ case 235: /* paren_exprlist ::= */ yytestcase(yyruleno==235);
+ case 240: /* eidlist_opt ::= */ yytestcase(yyruleno==240);
{yymsp[1].minor.yy322 = 0;}
break;
case 100: /* selcollist ::= sclp scanpt expr scanpt as */
@@ -165400,9 +169325,9 @@ static YYACTIONTYPE yy_reduce(
}
break;
case 103: /* as ::= AS nm */
- case 114: /* dbnm ::= DOT nm */ yytestcase(yyruleno==114);
- case 254: /* plus_num ::= PLUS INTEGER|FLOAT */ yytestcase(yyruleno==254);
- case 255: /* minus_num ::= MINUS INTEGER|FLOAT */ yytestcase(yyruleno==255);
+ case 115: /* dbnm ::= DOT nm */ yytestcase(yyruleno==115);
+ case 256: /* plus_num ::= PLUS INTEGER|FLOAT */ yytestcase(yyruleno==256);
+ case 257: /* minus_num ::= MINUS INTEGER|FLOAT */ yytestcase(yyruleno==257);
{yymsp[-1].minor.yy0 = yymsp[0].minor.yy0;}
break;
case 105: /* from ::= */
@@ -165412,7 +169337,7 @@ static YYACTIONTYPE yy_reduce(
case 106: /* from ::= FROM seltablist */
{
yymsp[-1].minor.yy131 = yymsp[0].minor.yy131;
- sqlite3SrcListShiftJoinType(yymsp[-1].minor.yy131);
+ sqlite3SrcListShiftJoinType(pParse,yymsp[-1].minor.yy131);
}
break;
case 107: /* stl_prefix ::= seltablist joinop */
@@ -165420,35 +169345,43 @@ static YYACTIONTYPE yy_reduce(
if( ALWAYS(yymsp[-1].minor.yy131 && yymsp[-1].minor.yy131->nSrc>0) ) yymsp[-1].minor.yy131->a[yymsp[-1].minor.yy131->nSrc-1].fg.jointype = (u8)yymsp[0].minor.yy394;
}
break;
- case 109: /* seltablist ::= stl_prefix nm dbnm as indexed_opt on_opt using_opt */
+ case 109: /* seltablist ::= stl_prefix nm dbnm as on_using */
{
- yymsp[-6].minor.yy131 = sqlite3SrcListAppendFromTerm(pParse,yymsp[-6].minor.yy131,&yymsp[-5].minor.yy0,&yymsp[-4].minor.yy0,&yymsp[-3].minor.yy0,0,yymsp[-1].minor.yy528,yymsp[0].minor.yy254);
- sqlite3SrcListIndexedBy(pParse, yymsp[-6].minor.yy131, &yymsp[-2].minor.yy0);
+ yymsp[-4].minor.yy131 = sqlite3SrcListAppendFromTerm(pParse,yymsp[-4].minor.yy131,&yymsp[-3].minor.yy0,&yymsp[-2].minor.yy0,&yymsp[-1].minor.yy0,0,&yymsp[0].minor.yy561);
}
break;
- case 110: /* seltablist ::= stl_prefix nm dbnm LP exprlist RP as on_opt using_opt */
+ case 110: /* seltablist ::= stl_prefix nm dbnm as indexed_by on_using */
{
- yymsp[-8].minor.yy131 = sqlite3SrcListAppendFromTerm(pParse,yymsp[-8].minor.yy131,&yymsp[-7].minor.yy0,&yymsp[-6].minor.yy0,&yymsp[-2].minor.yy0,0,yymsp[-1].minor.yy528,yymsp[0].minor.yy254);
- sqlite3SrcListFuncArgs(pParse, yymsp[-8].minor.yy131, yymsp[-4].minor.yy322);
+ yymsp[-5].minor.yy131 = sqlite3SrcListAppendFromTerm(pParse,yymsp[-5].minor.yy131,&yymsp[-4].minor.yy0,&yymsp[-3].minor.yy0,&yymsp[-2].minor.yy0,0,&yymsp[0].minor.yy561);
+ sqlite3SrcListIndexedBy(pParse, yymsp[-5].minor.yy131, &yymsp[-1].minor.yy0);
}
break;
- case 111: /* seltablist ::= stl_prefix LP select RP as on_opt using_opt */
+ case 111: /* seltablist ::= stl_prefix nm dbnm LP exprlist RP as on_using */
{
- yymsp[-6].minor.yy131 = sqlite3SrcListAppendFromTerm(pParse,yymsp[-6].minor.yy131,0,0,&yymsp[-2].minor.yy0,yymsp[-4].minor.yy47,yymsp[-1].minor.yy528,yymsp[0].minor.yy254);
+ yymsp[-7].minor.yy131 = sqlite3SrcListAppendFromTerm(pParse,yymsp[-7].minor.yy131,&yymsp[-6].minor.yy0,&yymsp[-5].minor.yy0,&yymsp[-1].minor.yy0,0,&yymsp[0].minor.yy561);
+ sqlite3SrcListFuncArgs(pParse, yymsp[-7].minor.yy131, yymsp[-3].minor.yy322);
+}
+ break;
+ case 112: /* seltablist ::= stl_prefix LP select RP as on_using */
+{
+ yymsp[-5].minor.yy131 = sqlite3SrcListAppendFromTerm(pParse,yymsp[-5].minor.yy131,0,0,&yymsp[-1].minor.yy0,yymsp[-3].minor.yy47,&yymsp[0].minor.yy561);
}
break;
- case 112: /* seltablist ::= stl_prefix LP seltablist RP as on_opt using_opt */
+ case 113: /* seltablist ::= stl_prefix LP seltablist RP as on_using */
{
- if( yymsp[-6].minor.yy131==0 && yymsp[-2].minor.yy0.n==0 && yymsp[-1].minor.yy528==0 && yymsp[0].minor.yy254==0 ){
- yymsp[-6].minor.yy131 = yymsp[-4].minor.yy131;
- }else if( yymsp[-4].minor.yy131->nSrc==1 ){
- yymsp[-6].minor.yy131 = sqlite3SrcListAppendFromTerm(pParse,yymsp[-6].minor.yy131,0,0,&yymsp[-2].minor.yy0,0,yymsp[-1].minor.yy528,yymsp[0].minor.yy254);
- if( yymsp[-6].minor.yy131 ){
- SrcItem *pNew = &yymsp[-6].minor.yy131->a[yymsp[-6].minor.yy131->nSrc-1];
- SrcItem *pOld = yymsp[-4].minor.yy131->a;
+ if( yymsp[-5].minor.yy131==0 && yymsp[-1].minor.yy0.n==0 && yymsp[0].minor.yy561.pOn==0 && yymsp[0].minor.yy561.pUsing==0 ){
+ yymsp[-5].minor.yy131 = yymsp[-3].minor.yy131;
+ }else if( yymsp[-3].minor.yy131->nSrc==1 ){
+ yymsp[-5].minor.yy131 = sqlite3SrcListAppendFromTerm(pParse,yymsp[-5].minor.yy131,0,0,&yymsp[-1].minor.yy0,0,&yymsp[0].minor.yy561);
+ if( yymsp[-5].minor.yy131 ){
+ SrcItem *pNew = &yymsp[-5].minor.yy131->a[yymsp[-5].minor.yy131->nSrc-1];
+ SrcItem *pOld = yymsp[-3].minor.yy131->a;
pNew->zName = pOld->zName;
pNew->zDatabase = pOld->zDatabase;
pNew->pSelect = pOld->pSelect;
+ if( pNew->pSelect && (pNew->pSelect->selFlags & SF_NestedFrom)!=0 ){
+ pNew->fg.isNestedFrom = 1;
+ }
if( pOld->fg.isTabFunc ){
pNew->u1.pFuncArg = pOld->u1.pFuncArg;
pOld->u1.pFuncArg = 0;
@@ -165458,94 +169391,78 @@ static YYACTIONTYPE yy_reduce(
pOld->zName = pOld->zDatabase = 0;
pOld->pSelect = 0;
}
- sqlite3SrcListDelete(pParse->db, yymsp[-4].minor.yy131);
+ sqlite3SrcListDelete(pParse->db, yymsp[-3].minor.yy131);
}else{
Select *pSubquery;
- sqlite3SrcListShiftJoinType(yymsp[-4].minor.yy131);
- pSubquery = sqlite3SelectNew(pParse,0,yymsp[-4].minor.yy131,0,0,0,0,SF_NestedFrom,0);
- yymsp[-6].minor.yy131 = sqlite3SrcListAppendFromTerm(pParse,yymsp[-6].minor.yy131,0,0,&yymsp[-2].minor.yy0,pSubquery,yymsp[-1].minor.yy528,yymsp[0].minor.yy254);
+ sqlite3SrcListShiftJoinType(pParse,yymsp[-3].minor.yy131);
+ pSubquery = sqlite3SelectNew(pParse,0,yymsp[-3].minor.yy131,0,0,0,0,SF_NestedFrom,0);
+ yymsp[-5].minor.yy131 = sqlite3SrcListAppendFromTerm(pParse,yymsp[-5].minor.yy131,0,0,&yymsp[-1].minor.yy0,pSubquery,&yymsp[0].minor.yy561);
}
}
break;
- case 113: /* dbnm ::= */
- case 127: /* indexed_opt ::= */ yytestcase(yyruleno==127);
+ case 114: /* dbnm ::= */
+ case 129: /* indexed_opt ::= */ yytestcase(yyruleno==129);
{yymsp[1].minor.yy0.z=0; yymsp[1].minor.yy0.n=0;}
break;
- case 115: /* fullname ::= nm */
+ case 116: /* fullname ::= nm */
{
yylhsminor.yy131 = sqlite3SrcListAppend(pParse,0,&yymsp[0].minor.yy0,0);
if( IN_RENAME_OBJECT && yylhsminor.yy131 ) sqlite3RenameTokenMap(pParse, yylhsminor.yy131->a[0].zName, &yymsp[0].minor.yy0);
}
yymsp[0].minor.yy131 = yylhsminor.yy131;
break;
- case 116: /* fullname ::= nm DOT nm */
+ case 117: /* fullname ::= nm DOT nm */
{
yylhsminor.yy131 = sqlite3SrcListAppend(pParse,0,&yymsp[-2].minor.yy0,&yymsp[0].minor.yy0);
if( IN_RENAME_OBJECT && yylhsminor.yy131 ) sqlite3RenameTokenMap(pParse, yylhsminor.yy131->a[0].zName, &yymsp[0].minor.yy0);
}
yymsp[-2].minor.yy131 = yylhsminor.yy131;
break;
- case 117: /* xfullname ::= nm */
+ case 118: /* xfullname ::= nm */
{yymsp[0].minor.yy131 = sqlite3SrcListAppend(pParse,0,&yymsp[0].minor.yy0,0); /*A-overwrites-X*/}
break;
- case 118: /* xfullname ::= nm DOT nm */
+ case 119: /* xfullname ::= nm DOT nm */
{yymsp[-2].minor.yy131 = sqlite3SrcListAppend(pParse,0,&yymsp[-2].minor.yy0,&yymsp[0].minor.yy0); /*A-overwrites-X*/}
break;
- case 119: /* xfullname ::= nm DOT nm AS nm */
+ case 120: /* xfullname ::= nm DOT nm AS nm */
{
yymsp[-4].minor.yy131 = sqlite3SrcListAppend(pParse,0,&yymsp[-4].minor.yy0,&yymsp[-2].minor.yy0); /*A-overwrites-X*/
if( yymsp[-4].minor.yy131 ) yymsp[-4].minor.yy131->a[0].zAlias = sqlite3NameFromToken(pParse->db, &yymsp[0].minor.yy0);
}
break;
- case 120: /* xfullname ::= nm AS nm */
+ case 121: /* xfullname ::= nm AS nm */
{
yymsp[-2].minor.yy131 = sqlite3SrcListAppend(pParse,0,&yymsp[-2].minor.yy0,0); /*A-overwrites-X*/
if( yymsp[-2].minor.yy131 ) yymsp[-2].minor.yy131->a[0].zAlias = sqlite3NameFromToken(pParse->db, &yymsp[0].minor.yy0);
}
break;
- case 121: /* joinop ::= COMMA|JOIN */
+ case 122: /* joinop ::= COMMA|JOIN */
{ yymsp[0].minor.yy394 = JT_INNER; }
break;
- case 122: /* joinop ::= JOIN_KW JOIN */
+ case 123: /* joinop ::= JOIN_KW JOIN */
{yymsp[-1].minor.yy394 = sqlite3JoinType(pParse,&yymsp[-1].minor.yy0,0,0); /*X-overwrites-A*/}
break;
- case 123: /* joinop ::= JOIN_KW nm JOIN */
+ case 124: /* joinop ::= JOIN_KW nm JOIN */
{yymsp[-2].minor.yy394 = sqlite3JoinType(pParse,&yymsp[-2].minor.yy0,&yymsp[-1].minor.yy0,0); /*X-overwrites-A*/}
break;
- case 124: /* joinop ::= JOIN_KW nm nm JOIN */
+ case 125: /* joinop ::= JOIN_KW nm nm JOIN */
{yymsp[-3].minor.yy394 = sqlite3JoinType(pParse,&yymsp[-3].minor.yy0,&yymsp[-2].minor.yy0,&yymsp[-1].minor.yy0);/*X-overwrites-A*/}
break;
- case 125: /* on_opt ::= ON expr */
- case 145: /* having_opt ::= HAVING expr */ yytestcase(yyruleno==145);
- case 152: /* where_opt ::= WHERE expr */ yytestcase(yyruleno==152);
- case 154: /* where_opt_ret ::= WHERE expr */ yytestcase(yyruleno==154);
- case 226: /* case_else ::= ELSE expr */ yytestcase(yyruleno==226);
- case 247: /* vinto ::= INTO expr */ yytestcase(yyruleno==247);
-{yymsp[-1].minor.yy528 = yymsp[0].minor.yy528;}
+ case 126: /* on_using ::= ON expr */
+{yymsp[-1].minor.yy561.pOn = yymsp[0].minor.yy528; yymsp[-1].minor.yy561.pUsing = 0;}
break;
- case 126: /* on_opt ::= */
- case 144: /* having_opt ::= */ yytestcase(yyruleno==144);
- case 146: /* limit_opt ::= */ yytestcase(yyruleno==146);
- case 151: /* where_opt ::= */ yytestcase(yyruleno==151);
- case 153: /* where_opt_ret ::= */ yytestcase(yyruleno==153);
- case 227: /* case_else ::= */ yytestcase(yyruleno==227);
- case 229: /* case_operand ::= */ yytestcase(yyruleno==229);
- case 248: /* vinto ::= */ yytestcase(yyruleno==248);
-{yymsp[1].minor.yy528 = 0;}
+ case 127: /* on_using ::= USING LP idlist RP */
+{yymsp[-3].minor.yy561.pOn = 0; yymsp[-3].minor.yy561.pUsing = yymsp[-1].minor.yy254;}
break;
- case 128: /* indexed_opt ::= INDEXED BY nm */
+ case 128: /* on_using ::= */
+{yymsp[1].minor.yy561.pOn = 0; yymsp[1].minor.yy561.pUsing = 0;}
+ break;
+ case 130: /* indexed_by ::= INDEXED BY nm */
{yymsp[-2].minor.yy0 = yymsp[0].minor.yy0;}
break;
- case 129: /* indexed_opt ::= NOT INDEXED */
+ case 131: /* indexed_by ::= NOT INDEXED */
{yymsp[-1].minor.yy0.z=0; yymsp[-1].minor.yy0.n=1;}
break;
- case 130: /* using_opt ::= USING LP idlist RP */
-{yymsp[-3].minor.yy254 = yymsp[-1].minor.yy254;}
- break;
- case 131: /* using_opt ::= */
- case 173: /* idlist_opt ::= */ yytestcase(yyruleno==173);
-{yymsp[1].minor.yy254 = 0;}
- break;
case 133: /* orderby_opt ::= ORDER BY sortlist */
case 143: /* groupby_opt ::= GROUP BY nexprlist */ yytestcase(yyruleno==143);
{yymsp[-2].minor.yy322 = yymsp[0].minor.yy322;}
@@ -165578,6 +169495,22 @@ static YYACTIONTYPE yy_reduce(
case 140: /* nulls ::= NULLS LAST */
{yymsp[-1].minor.yy394 = SQLITE_SO_DESC;}
break;
+ case 144: /* having_opt ::= */
+ case 146: /* limit_opt ::= */ yytestcase(yyruleno==146);
+ case 151: /* where_opt ::= */ yytestcase(yyruleno==151);
+ case 153: /* where_opt_ret ::= */ yytestcase(yyruleno==153);
+ case 229: /* case_else ::= */ yytestcase(yyruleno==229);
+ case 231: /* case_operand ::= */ yytestcase(yyruleno==231);
+ case 250: /* vinto ::= */ yytestcase(yyruleno==250);
+{yymsp[1].minor.yy528 = 0;}
+ break;
+ case 145: /* having_opt ::= HAVING expr */
+ case 152: /* where_opt ::= WHERE expr */ yytestcase(yyruleno==152);
+ case 154: /* where_opt_ret ::= WHERE expr */ yytestcase(yyruleno==154);
+ case 228: /* case_else ::= ELSE expr */ yytestcase(yyruleno==228);
+ case 249: /* vinto ::= INTO expr */ yytestcase(yyruleno==249);
+{yymsp[-1].minor.yy528 = yymsp[0].minor.yy528;}
+ break;
case 147: /* limit_opt ::= LIMIT expr */
{yymsp[-1].minor.yy528 = sqlite3PExpr(pParse,TK_LIMIT,yymsp[0].minor.yy528,0);}
break;
@@ -165609,7 +169542,18 @@ static YYACTIONTYPE yy_reduce(
case 157: /* cmd ::= with UPDATE orconf xfullname indexed_opt SET setlist from where_opt_ret orderby_opt limit_opt */
{
sqlite3SrcListIndexedBy(pParse, yymsp[-7].minor.yy131, &yymsp[-6].minor.yy0);
- yymsp[-7].minor.yy131 = sqlite3SrcListAppendList(pParse, yymsp[-7].minor.yy131, yymsp[-3].minor.yy131);
+ if( yymsp[-3].minor.yy131 ){
+ SrcList *pFromClause = yymsp[-3].minor.yy131;
+ if( pFromClause->nSrc>1 ){
+ Select *pSubquery;
+ Token as;
+ pSubquery = sqlite3SelectNew(pParse,0,pFromClause,0,0,0,0,SF_NestedFrom,0);
+ as.n = 0;
+ as.z = 0;
+ pFromClause = sqlite3SrcListAppendFromTerm(pParse,0,0,0,&as,pSubquery,0);
+ }
+ yymsp[-7].minor.yy131 = sqlite3SrcListAppendList(pParse, yymsp[-7].minor.yy131, pFromClause);
+ }
sqlite3ExprListCheckLength(pParse,yymsp[-4].minor.yy322,"set list");
#ifndef SQLITE_ENABLE_UPDATE_DELETE_LIMIT
if( yymsp[-1].minor.yy322 || yymsp[0].minor.yy528 ){
@@ -165675,6 +169619,9 @@ static YYACTIONTYPE yy_reduce(
case 170: /* returning ::= RETURNING selcollist */
{sqlite3AddReturning(pParse,yymsp[0].minor.yy322);}
break;
+ case 173: /* idlist_opt ::= */
+{yymsp[1].minor.yy254 = 0;}
+ break;
case 174: /* idlist_opt ::= LP idlist RP */
{yymsp[-2].minor.yy254 = yymsp[-1].minor.yy254;}
break;
@@ -165860,17 +169807,29 @@ static YYACTIONTYPE yy_reduce(
binaryToUnaryIfNull(pParse, yymsp[0].minor.yy528, yymsp[-3].minor.yy528, TK_NOTNULL);
}
break;
- case 209: /* expr ::= NOT expr */
- case 210: /* expr ::= BITNOT expr */ yytestcase(yyruleno==210);
+ case 209: /* expr ::= expr IS NOT DISTINCT FROM expr */
+{
+ yymsp[-5].minor.yy528 = sqlite3PExpr(pParse,TK_IS,yymsp[-5].minor.yy528,yymsp[0].minor.yy528);
+ binaryToUnaryIfNull(pParse, yymsp[0].minor.yy528, yymsp[-5].minor.yy528, TK_ISNULL);
+}
+ break;
+ case 210: /* expr ::= expr IS DISTINCT FROM expr */
+{
+ yymsp[-4].minor.yy528 = sqlite3PExpr(pParse,TK_ISNOT,yymsp[-4].minor.yy528,yymsp[0].minor.yy528);
+ binaryToUnaryIfNull(pParse, yymsp[0].minor.yy528, yymsp[-4].minor.yy528, TK_NOTNULL);
+}
+ break;
+ case 211: /* expr ::= NOT expr */
+ case 212: /* expr ::= BITNOT expr */ yytestcase(yyruleno==212);
{yymsp[-1].minor.yy528 = sqlite3PExpr(pParse, yymsp[-1].major, yymsp[0].minor.yy528, 0);/*A-overwrites-B*/}
break;
- case 211: /* expr ::= PLUS|MINUS expr */
+ case 213: /* expr ::= PLUS|MINUS expr */
{
yymsp[-1].minor.yy528 = sqlite3PExpr(pParse, yymsp[-1].major==TK_PLUS ? TK_UPLUS : TK_UMINUS, yymsp[0].minor.yy528, 0);
/*A-overwrites-B*/
}
break;
- case 212: /* expr ::= expr PTR expr */
+ case 214: /* expr ::= expr PTR expr */
{
ExprList *pList = sqlite3ExprListAppend(pParse, 0, yymsp[-2].minor.yy528);
pList = sqlite3ExprListAppend(pParse, pList, yymsp[0].minor.yy528);
@@ -165878,11 +169837,11 @@ static YYACTIONTYPE yy_reduce(
}
yymsp[-2].minor.yy528 = yylhsminor.yy528;
break;
- case 213: /* between_op ::= BETWEEN */
- case 216: /* in_op ::= IN */ yytestcase(yyruleno==216);
+ case 215: /* between_op ::= BETWEEN */
+ case 218: /* in_op ::= IN */ yytestcase(yyruleno==218);
{yymsp[0].minor.yy394 = 0;}
break;
- case 215: /* expr ::= expr between_op expr AND expr */
+ case 217: /* expr ::= expr between_op expr AND expr */
{
ExprList *pList = sqlite3ExprListAppend(pParse,0, yymsp[-2].minor.yy528);
pList = sqlite3ExprListAppend(pParse,pList, yymsp[0].minor.yy528);
@@ -165895,7 +169854,7 @@ static YYACTIONTYPE yy_reduce(
if( yymsp[-3].minor.yy394 ) yymsp[-4].minor.yy528 = sqlite3PExpr(pParse, TK_NOT, yymsp[-4].minor.yy528, 0);
}
break;
- case 218: /* expr ::= expr in_op LP exprlist RP */
+ case 220: /* expr ::= expr in_op LP exprlist RP */
{
if( yymsp[-1].minor.yy322==0 ){
/* Expressions of the form
@@ -165907,7 +169866,8 @@ static YYACTIONTYPE yy_reduce(
** regardless of the value of expr1.
*/
sqlite3ExprUnmapAndDelete(pParse, yymsp[-4].minor.yy528);
- yymsp[-4].minor.yy528 = sqlite3Expr(pParse->db, TK_INTEGER, yymsp[-3].minor.yy394 ? "1" : "0");
+ yymsp[-4].minor.yy528 = sqlite3Expr(pParse->db, TK_STRING, yymsp[-3].minor.yy394 ? "true" : "false");
+ if( yymsp[-4].minor.yy528 ) sqlite3ExprIdToTrueFalse(yymsp[-4].minor.yy528);
}else{
Expr *pRHS = yymsp[-1].minor.yy322->a[0].pExpr;
if( yymsp[-1].minor.yy322->nExpr==1 && sqlite3ExprIsConstant(pRHS) && yymsp[-4].minor.yy528->op!=TK_VECTOR ){
@@ -165935,20 +169895,20 @@ static YYACTIONTYPE yy_reduce(
}
}
break;
- case 219: /* expr ::= LP select RP */
+ case 221: /* expr ::= LP select RP */
{
yymsp[-2].minor.yy528 = sqlite3PExpr(pParse, TK_SELECT, 0, 0);
sqlite3PExprAddSelect(pParse, yymsp[-2].minor.yy528, yymsp[-1].minor.yy47);
}
break;
- case 220: /* expr ::= expr in_op LP select RP */
+ case 222: /* expr ::= expr in_op LP select RP */
{
yymsp[-4].minor.yy528 = sqlite3PExpr(pParse, TK_IN, yymsp[-4].minor.yy528, 0);
sqlite3PExprAddSelect(pParse, yymsp[-4].minor.yy528, yymsp[-1].minor.yy47);
if( yymsp[-3].minor.yy394 ) yymsp[-4].minor.yy528 = sqlite3PExpr(pParse, TK_NOT, yymsp[-4].minor.yy528, 0);
}
break;
- case 221: /* expr ::= expr in_op nm dbnm paren_exprlist */
+ case 223: /* expr ::= expr in_op nm dbnm paren_exprlist */
{
SrcList *pSrc = sqlite3SrcListAppend(pParse, 0,&yymsp[-2].minor.yy0,&yymsp[-1].minor.yy0);
Select *pSelect = sqlite3SelectNew(pParse, 0,pSrc,0,0,0,0,0,0);
@@ -165958,14 +169918,14 @@ static YYACTIONTYPE yy_reduce(
if( yymsp[-3].minor.yy394 ) yymsp[-4].minor.yy528 = sqlite3PExpr(pParse, TK_NOT, yymsp[-4].minor.yy528, 0);
}
break;
- case 222: /* expr ::= EXISTS LP select RP */
+ case 224: /* expr ::= EXISTS LP select RP */
{
Expr *p;
p = yymsp[-3].minor.yy528 = sqlite3PExpr(pParse, TK_EXISTS, 0, 0);
sqlite3PExprAddSelect(pParse, p, yymsp[-1].minor.yy47);
}
break;
- case 223: /* expr ::= CASE case_operand case_exprlist case_else END */
+ case 225: /* expr ::= CASE case_operand case_exprlist case_else END */
{
yymsp[-4].minor.yy528 = sqlite3PExpr(pParse, TK_CASE, yymsp[-3].minor.yy528, 0);
if( yymsp[-4].minor.yy528 ){
@@ -165977,32 +169937,32 @@ static YYACTIONTYPE yy_reduce(
}
}
break;
- case 224: /* case_exprlist ::= case_exprlist WHEN expr THEN expr */
+ case 226: /* case_exprlist ::= case_exprlist WHEN expr THEN expr */
{
yymsp[-4].minor.yy322 = sqlite3ExprListAppend(pParse,yymsp[-4].minor.yy322, yymsp[-2].minor.yy528);
yymsp[-4].minor.yy322 = sqlite3ExprListAppend(pParse,yymsp[-4].minor.yy322, yymsp[0].minor.yy528);
}
break;
- case 225: /* case_exprlist ::= WHEN expr THEN expr */
+ case 227: /* case_exprlist ::= WHEN expr THEN expr */
{
yymsp[-3].minor.yy322 = sqlite3ExprListAppend(pParse,0, yymsp[-2].minor.yy528);
yymsp[-3].minor.yy322 = sqlite3ExprListAppend(pParse,yymsp[-3].minor.yy322, yymsp[0].minor.yy528);
}
break;
- case 228: /* case_operand ::= expr */
+ case 230: /* case_operand ::= expr */
{yymsp[0].minor.yy528 = yymsp[0].minor.yy528; /*A-overwrites-X*/}
break;
- case 231: /* nexprlist ::= nexprlist COMMA expr */
+ case 233: /* nexprlist ::= nexprlist COMMA expr */
{yymsp[-2].minor.yy322 = sqlite3ExprListAppend(pParse,yymsp[-2].minor.yy322,yymsp[0].minor.yy528);}
break;
- case 232: /* nexprlist ::= expr */
+ case 234: /* nexprlist ::= expr */
{yymsp[0].minor.yy322 = sqlite3ExprListAppend(pParse,0,yymsp[0].minor.yy528); /*A-overwrites-Y*/}
break;
- case 234: /* paren_exprlist ::= LP exprlist RP */
- case 239: /* eidlist_opt ::= LP eidlist RP */ yytestcase(yyruleno==239);
+ case 236: /* paren_exprlist ::= LP exprlist RP */
+ case 241: /* eidlist_opt ::= LP eidlist RP */ yytestcase(yyruleno==241);
{yymsp[-2].minor.yy322 = yymsp[-1].minor.yy322;}
break;
- case 235: /* cmd ::= createkw uniqueflag INDEX ifnotexists nm dbnm ON nm LP sortlist RP where_opt */
+ case 237: /* cmd ::= createkw uniqueflag INDEX ifnotexists nm dbnm ON nm LP sortlist RP where_opt */
{
sqlite3CreateIndex(pParse, &yymsp[-7].minor.yy0, &yymsp[-6].minor.yy0,
sqlite3SrcListAppend(pParse,0,&yymsp[-4].minor.yy0,0), yymsp[-2].minor.yy322, yymsp[-10].minor.yy394,
@@ -166012,48 +169972,48 @@ static YYACTIONTYPE yy_reduce(
}
}
break;
- case 236: /* uniqueflag ::= UNIQUE */
- case 278: /* raisetype ::= ABORT */ yytestcase(yyruleno==278);
+ case 238: /* uniqueflag ::= UNIQUE */
+ case 280: /* raisetype ::= ABORT */ yytestcase(yyruleno==280);
{yymsp[0].minor.yy394 = OE_Abort;}
break;
- case 237: /* uniqueflag ::= */
+ case 239: /* uniqueflag ::= */
{yymsp[1].minor.yy394 = OE_None;}
break;
- case 240: /* eidlist ::= eidlist COMMA nm collate sortorder */
+ case 242: /* eidlist ::= eidlist COMMA nm collate sortorder */
{
yymsp[-4].minor.yy322 = parserAddExprIdListTerm(pParse, yymsp[-4].minor.yy322, &yymsp[-2].minor.yy0, yymsp[-1].minor.yy394, yymsp[0].minor.yy394);
}
break;
- case 241: /* eidlist ::= nm collate sortorder */
+ case 243: /* eidlist ::= nm collate sortorder */
{
yymsp[-2].minor.yy322 = parserAddExprIdListTerm(pParse, 0, &yymsp[-2].minor.yy0, yymsp[-1].minor.yy394, yymsp[0].minor.yy394); /*A-overwrites-Y*/
}
break;
- case 244: /* cmd ::= DROP INDEX ifexists fullname */
+ case 246: /* cmd ::= DROP INDEX ifexists fullname */
{sqlite3DropIndex(pParse, yymsp[0].minor.yy131, yymsp[-1].minor.yy394);}
break;
- case 245: /* cmd ::= VACUUM vinto */
+ case 247: /* cmd ::= VACUUM vinto */
{sqlite3Vacuum(pParse,0,yymsp[0].minor.yy528);}
break;
- case 246: /* cmd ::= VACUUM nm vinto */
+ case 248: /* cmd ::= VACUUM nm vinto */
{sqlite3Vacuum(pParse,&yymsp[-1].minor.yy0,yymsp[0].minor.yy528);}
break;
- case 249: /* cmd ::= PRAGMA nm dbnm */
+ case 251: /* cmd ::= PRAGMA nm dbnm */
{sqlite3Pragma(pParse,&yymsp[-1].minor.yy0,&yymsp[0].minor.yy0,0,0);}
break;
- case 250: /* cmd ::= PRAGMA nm dbnm EQ nmnum */
+ case 252: /* cmd ::= PRAGMA nm dbnm EQ nmnum */
{sqlite3Pragma(pParse,&yymsp[-3].minor.yy0,&yymsp[-2].minor.yy0,&yymsp[0].minor.yy0,0);}
break;
- case 251: /* cmd ::= PRAGMA nm dbnm LP nmnum RP */
+ case 253: /* cmd ::= PRAGMA nm dbnm LP nmnum RP */
{sqlite3Pragma(pParse,&yymsp[-4].minor.yy0,&yymsp[-3].minor.yy0,&yymsp[-1].minor.yy0,0);}
break;
- case 252: /* cmd ::= PRAGMA nm dbnm EQ minus_num */
+ case 254: /* cmd ::= PRAGMA nm dbnm EQ minus_num */
{sqlite3Pragma(pParse,&yymsp[-3].minor.yy0,&yymsp[-2].minor.yy0,&yymsp[0].minor.yy0,1);}
break;
- case 253: /* cmd ::= PRAGMA nm dbnm LP minus_num RP */
+ case 255: /* cmd ::= PRAGMA nm dbnm LP minus_num RP */
{sqlite3Pragma(pParse,&yymsp[-4].minor.yy0,&yymsp[-3].minor.yy0,&yymsp[-1].minor.yy0,1);}
break;
- case 256: /* cmd ::= createkw trigger_decl BEGIN trigger_cmd_list END */
+ case 258: /* cmd ::= createkw trigger_decl BEGIN trigger_cmd_list END */
{
Token all;
all.z = yymsp[-3].minor.yy0.z;
@@ -166061,50 +170021,50 @@ static YYACTIONTYPE yy_reduce(
sqlite3FinishTrigger(pParse, yymsp[-1].minor.yy33, &all);
}
break;
- case 257: /* trigger_decl ::= temp TRIGGER ifnotexists nm dbnm trigger_time trigger_event ON fullname foreach_clause when_clause */
+ case 259: /* trigger_decl ::= temp TRIGGER ifnotexists nm dbnm trigger_time trigger_event ON fullname foreach_clause when_clause */
{
sqlite3BeginTrigger(pParse, &yymsp[-7].minor.yy0, &yymsp[-6].minor.yy0, yymsp[-5].minor.yy394, yymsp[-4].minor.yy180.a, yymsp[-4].minor.yy180.b, yymsp[-2].minor.yy131, yymsp[0].minor.yy528, yymsp[-10].minor.yy394, yymsp[-8].minor.yy394);
yymsp[-10].minor.yy0 = (yymsp[-6].minor.yy0.n==0?yymsp[-7].minor.yy0:yymsp[-6].minor.yy0); /*A-overwrites-T*/
}
break;
- case 258: /* trigger_time ::= BEFORE|AFTER */
+ case 260: /* trigger_time ::= BEFORE|AFTER */
{ yymsp[0].minor.yy394 = yymsp[0].major; /*A-overwrites-X*/ }
break;
- case 259: /* trigger_time ::= INSTEAD OF */
+ case 261: /* trigger_time ::= INSTEAD OF */
{ yymsp[-1].minor.yy394 = TK_INSTEAD;}
break;
- case 260: /* trigger_time ::= */
+ case 262: /* trigger_time ::= */
{ yymsp[1].minor.yy394 = TK_BEFORE; }
break;
- case 261: /* trigger_event ::= DELETE|INSERT */
- case 262: /* trigger_event ::= UPDATE */ yytestcase(yyruleno==262);
+ case 263: /* trigger_event ::= DELETE|INSERT */
+ case 264: /* trigger_event ::= UPDATE */ yytestcase(yyruleno==264);
{yymsp[0].minor.yy180.a = yymsp[0].major; /*A-overwrites-X*/ yymsp[0].minor.yy180.b = 0;}
break;
- case 263: /* trigger_event ::= UPDATE OF idlist */
+ case 265: /* trigger_event ::= UPDATE OF idlist */
{yymsp[-2].minor.yy180.a = TK_UPDATE; yymsp[-2].minor.yy180.b = yymsp[0].minor.yy254;}
break;
- case 264: /* when_clause ::= */
- case 283: /* key_opt ::= */ yytestcase(yyruleno==283);
+ case 266: /* when_clause ::= */
+ case 285: /* key_opt ::= */ yytestcase(yyruleno==285);
{ yymsp[1].minor.yy528 = 0; }
break;
- case 265: /* when_clause ::= WHEN expr */
- case 284: /* key_opt ::= KEY expr */ yytestcase(yyruleno==284);
+ case 267: /* when_clause ::= WHEN expr */
+ case 286: /* key_opt ::= KEY expr */ yytestcase(yyruleno==286);
{ yymsp[-1].minor.yy528 = yymsp[0].minor.yy528; }
break;
- case 266: /* trigger_cmd_list ::= trigger_cmd_list trigger_cmd SEMI */
+ case 268: /* trigger_cmd_list ::= trigger_cmd_list trigger_cmd SEMI */
{
assert( yymsp[-2].minor.yy33!=0 );
yymsp[-2].minor.yy33->pLast->pNext = yymsp[-1].minor.yy33;
yymsp[-2].minor.yy33->pLast = yymsp[-1].minor.yy33;
}
break;
- case 267: /* trigger_cmd_list ::= trigger_cmd SEMI */
+ case 269: /* trigger_cmd_list ::= trigger_cmd SEMI */
{
assert( yymsp[-1].minor.yy33!=0 );
yymsp[-1].minor.yy33->pLast = yymsp[-1].minor.yy33;
}
break;
- case 268: /* trnm ::= nm DOT nm */
+ case 270: /* trnm ::= nm DOT nm */
{
yymsp[-2].minor.yy0 = yymsp[0].minor.yy0;
sqlite3ErrorMsg(pParse,
@@ -166112,39 +170072,39 @@ static YYACTIONTYPE yy_reduce(
"statements within triggers");
}
break;
- case 269: /* tridxby ::= INDEXED BY nm */
+ case 271: /* tridxby ::= INDEXED BY nm */
{
sqlite3ErrorMsg(pParse,
"the INDEXED BY clause is not allowed on UPDATE or DELETE statements "
"within triggers");
}
break;
- case 270: /* tridxby ::= NOT INDEXED */
+ case 272: /* tridxby ::= NOT INDEXED */
{
sqlite3ErrorMsg(pParse,
"the NOT INDEXED clause is not allowed on UPDATE or DELETE statements "
"within triggers");
}
break;
- case 271: /* trigger_cmd ::= UPDATE orconf trnm tridxby SET setlist from where_opt scanpt */
+ case 273: /* trigger_cmd ::= UPDATE orconf trnm tridxby SET setlist from where_opt scanpt */
{yylhsminor.yy33 = sqlite3TriggerUpdateStep(pParse, &yymsp[-6].minor.yy0, yymsp[-2].minor.yy131, yymsp[-3].minor.yy322, yymsp[-1].minor.yy528, yymsp[-7].minor.yy394, yymsp[-8].minor.yy0.z, yymsp[0].minor.yy522);}
yymsp[-8].minor.yy33 = yylhsminor.yy33;
break;
- case 272: /* trigger_cmd ::= scanpt insert_cmd INTO trnm idlist_opt select upsert scanpt */
+ case 274: /* trigger_cmd ::= scanpt insert_cmd INTO trnm idlist_opt select upsert scanpt */
{
yylhsminor.yy33 = sqlite3TriggerInsertStep(pParse,&yymsp[-4].minor.yy0,yymsp[-3].minor.yy254,yymsp[-2].minor.yy47,yymsp[-6].minor.yy394,yymsp[-1].minor.yy444,yymsp[-7].minor.yy522,yymsp[0].minor.yy522);/*yylhsminor.yy33-overwrites-yymsp[-6].minor.yy394*/
}
yymsp[-7].minor.yy33 = yylhsminor.yy33;
break;
- case 273: /* trigger_cmd ::= DELETE FROM trnm tridxby where_opt scanpt */
+ case 275: /* trigger_cmd ::= DELETE FROM trnm tridxby where_opt scanpt */
{yylhsminor.yy33 = sqlite3TriggerDeleteStep(pParse, &yymsp[-3].minor.yy0, yymsp[-1].minor.yy528, yymsp[-5].minor.yy0.z, yymsp[0].minor.yy522);}
yymsp[-5].minor.yy33 = yylhsminor.yy33;
break;
- case 274: /* trigger_cmd ::= scanpt select scanpt */
+ case 276: /* trigger_cmd ::= scanpt select scanpt */
{yylhsminor.yy33 = sqlite3TriggerSelectStep(pParse->db, yymsp[-1].minor.yy47, yymsp[-2].minor.yy522, yymsp[0].minor.yy522); /*yylhsminor.yy33-overwrites-yymsp[-1].minor.yy47*/}
yymsp[-2].minor.yy33 = yylhsminor.yy33;
break;
- case 275: /* expr ::= RAISE LP IGNORE RP */
+ case 277: /* expr ::= RAISE LP IGNORE RP */
{
yymsp[-3].minor.yy528 = sqlite3PExpr(pParse, TK_RAISE, 0, 0);
if( yymsp[-3].minor.yy528 ){
@@ -166152,7 +170112,7 @@ static YYACTIONTYPE yy_reduce(
}
}
break;
- case 276: /* expr ::= RAISE LP raisetype COMMA nm RP */
+ case 278: /* expr ::= RAISE LP raisetype COMMA nm RP */
{
yymsp[-5].minor.yy528 = sqlite3ExprAlloc(pParse->db, TK_RAISE, &yymsp[-1].minor.yy0, 1);
if( yymsp[-5].minor.yy528 ) {
@@ -166160,118 +170120,118 @@ static YYACTIONTYPE yy_reduce(
}
}
break;
- case 277: /* raisetype ::= ROLLBACK */
+ case 279: /* raisetype ::= ROLLBACK */
{yymsp[0].minor.yy394 = OE_Rollback;}
break;
- case 279: /* raisetype ::= FAIL */
+ case 281: /* raisetype ::= FAIL */
{yymsp[0].minor.yy394 = OE_Fail;}
break;
- case 280: /* cmd ::= DROP TRIGGER ifexists fullname */
+ case 282: /* cmd ::= DROP TRIGGER ifexists fullname */
{
sqlite3DropTrigger(pParse,yymsp[0].minor.yy131,yymsp[-1].minor.yy394);
}
break;
- case 281: /* cmd ::= ATTACH database_kw_opt expr AS expr key_opt */
+ case 283: /* cmd ::= ATTACH database_kw_opt expr AS expr key_opt */
{
sqlite3Attach(pParse, yymsp[-3].minor.yy528, yymsp[-1].minor.yy528, yymsp[0].minor.yy528);
}
break;
- case 282: /* cmd ::= DETACH database_kw_opt expr */
+ case 284: /* cmd ::= DETACH database_kw_opt expr */
{
sqlite3Detach(pParse, yymsp[0].minor.yy528);
}
break;
- case 285: /* cmd ::= REINDEX */
+ case 287: /* cmd ::= REINDEX */
{sqlite3Reindex(pParse, 0, 0);}
break;
- case 286: /* cmd ::= REINDEX nm dbnm */
+ case 288: /* cmd ::= REINDEX nm dbnm */
{sqlite3Reindex(pParse, &yymsp[-1].minor.yy0, &yymsp[0].minor.yy0);}
break;
- case 287: /* cmd ::= ANALYZE */
+ case 289: /* cmd ::= ANALYZE */
{sqlite3Analyze(pParse, 0, 0);}
break;
- case 288: /* cmd ::= ANALYZE nm dbnm */
+ case 290: /* cmd ::= ANALYZE nm dbnm */
{sqlite3Analyze(pParse, &yymsp[-1].minor.yy0, &yymsp[0].minor.yy0);}
break;
- case 289: /* cmd ::= ALTER TABLE fullname RENAME TO nm */
+ case 291: /* cmd ::= ALTER TABLE fullname RENAME TO nm */
{
sqlite3AlterRenameTable(pParse,yymsp[-3].minor.yy131,&yymsp[0].minor.yy0);
}
break;
- case 290: /* cmd ::= ALTER TABLE add_column_fullname ADD kwcolumn_opt columnname carglist */
+ case 292: /* cmd ::= ALTER TABLE add_column_fullname ADD kwcolumn_opt columnname carglist */
{
yymsp[-1].minor.yy0.n = (int)(pParse->sLastToken.z-yymsp[-1].minor.yy0.z) + pParse->sLastToken.n;
sqlite3AlterFinishAddColumn(pParse, &yymsp[-1].minor.yy0);
}
break;
- case 291: /* cmd ::= ALTER TABLE fullname DROP kwcolumn_opt nm */
+ case 293: /* cmd ::= ALTER TABLE fullname DROP kwcolumn_opt nm */
{
sqlite3AlterDropColumn(pParse, yymsp[-3].minor.yy131, &yymsp[0].minor.yy0);
}
break;
- case 292: /* add_column_fullname ::= fullname */
+ case 294: /* add_column_fullname ::= fullname */
{
disableLookaside(pParse);
sqlite3AlterBeginAddColumn(pParse, yymsp[0].minor.yy131);
}
break;
- case 293: /* cmd ::= ALTER TABLE fullname RENAME kwcolumn_opt nm TO nm */
+ case 295: /* cmd ::= ALTER TABLE fullname RENAME kwcolumn_opt nm TO nm */
{
sqlite3AlterRenameColumn(pParse, yymsp[-5].minor.yy131, &yymsp[-2].minor.yy0, &yymsp[0].minor.yy0);
}
break;
- case 294: /* cmd ::= create_vtab */
+ case 296: /* cmd ::= create_vtab */
{sqlite3VtabFinishParse(pParse,0);}
break;
- case 295: /* cmd ::= create_vtab LP vtabarglist RP */
+ case 297: /* cmd ::= create_vtab LP vtabarglist RP */
{sqlite3VtabFinishParse(pParse,&yymsp[0].minor.yy0);}
break;
- case 296: /* create_vtab ::= createkw VIRTUAL TABLE ifnotexists nm dbnm USING nm */
+ case 298: /* create_vtab ::= createkw VIRTUAL TABLE ifnotexists nm dbnm USING nm */
{
sqlite3VtabBeginParse(pParse, &yymsp[-3].minor.yy0, &yymsp[-2].minor.yy0, &yymsp[0].minor.yy0, yymsp[-4].minor.yy394);
}
break;
- case 297: /* vtabarg ::= */
+ case 299: /* vtabarg ::= */
{sqlite3VtabArgInit(pParse);}
break;
- case 298: /* vtabargtoken ::= ANY */
- case 299: /* vtabargtoken ::= lp anylist RP */ yytestcase(yyruleno==299);
- case 300: /* lp ::= LP */ yytestcase(yyruleno==300);
+ case 300: /* vtabargtoken ::= ANY */
+ case 301: /* vtabargtoken ::= lp anylist RP */ yytestcase(yyruleno==301);
+ case 302: /* lp ::= LP */ yytestcase(yyruleno==302);
{sqlite3VtabArgExtend(pParse,&yymsp[0].minor.yy0);}
break;
- case 301: /* with ::= WITH wqlist */
- case 302: /* with ::= WITH RECURSIVE wqlist */ yytestcase(yyruleno==302);
+ case 303: /* with ::= WITH wqlist */
+ case 304: /* with ::= WITH RECURSIVE wqlist */ yytestcase(yyruleno==304);
{ sqlite3WithPush(pParse, yymsp[0].minor.yy521, 1); }
break;
- case 303: /* wqas ::= AS */
+ case 305: /* wqas ::= AS */
{yymsp[0].minor.yy516 = M10d_Any;}
break;
- case 304: /* wqas ::= AS MATERIALIZED */
+ case 306: /* wqas ::= AS MATERIALIZED */
{yymsp[-1].minor.yy516 = M10d_Yes;}
break;
- case 305: /* wqas ::= AS NOT MATERIALIZED */
+ case 307: /* wqas ::= AS NOT MATERIALIZED */
{yymsp[-2].minor.yy516 = M10d_No;}
break;
- case 306: /* wqitem ::= nm eidlist_opt wqas LP select RP */
+ case 308: /* wqitem ::= nm eidlist_opt wqas LP select RP */
{
yymsp[-5].minor.yy385 = sqlite3CteNew(pParse, &yymsp[-5].minor.yy0, yymsp[-4].minor.yy322, yymsp[-1].minor.yy47, yymsp[-3].minor.yy516); /*A-overwrites-X*/
}
break;
- case 307: /* wqlist ::= wqitem */
+ case 309: /* wqlist ::= wqitem */
{
yymsp[0].minor.yy521 = sqlite3WithAdd(pParse, 0, yymsp[0].minor.yy385); /*A-overwrites-X*/
}
break;
- case 308: /* wqlist ::= wqlist COMMA wqitem */
+ case 310: /* wqlist ::= wqlist COMMA wqitem */
{
yymsp[-2].minor.yy521 = sqlite3WithAdd(pParse, yymsp[-2].minor.yy521, yymsp[0].minor.yy385);
}
break;
- case 309: /* windowdefn_list ::= windowdefn */
+ case 311: /* windowdefn_list ::= windowdefn */
{ yylhsminor.yy41 = yymsp[0].minor.yy41; }
yymsp[0].minor.yy41 = yylhsminor.yy41;
break;
- case 310: /* windowdefn_list ::= windowdefn_list COMMA windowdefn */
+ case 312: /* windowdefn_list ::= windowdefn_list COMMA windowdefn */
{
assert( yymsp[0].minor.yy41!=0 );
sqlite3WindowChain(pParse, yymsp[0].minor.yy41, yymsp[-2].minor.yy41);
@@ -166280,7 +170240,7 @@ static YYACTIONTYPE yy_reduce(
}
yymsp[-2].minor.yy41 = yylhsminor.yy41;
break;
- case 311: /* windowdefn ::= nm AS LP window RP */
+ case 313: /* windowdefn ::= nm AS LP window RP */
{
if( ALWAYS(yymsp[-1].minor.yy41) ){
yymsp[-1].minor.yy41->zName = sqlite3DbStrNDup(pParse->db, yymsp[-4].minor.yy0.z, yymsp[-4].minor.yy0.n);
@@ -166289,90 +170249,90 @@ static YYACTIONTYPE yy_reduce(
}
yymsp[-4].minor.yy41 = yylhsminor.yy41;
break;
- case 312: /* window ::= PARTITION BY nexprlist orderby_opt frame_opt */
+ case 314: /* window ::= PARTITION BY nexprlist orderby_opt frame_opt */
{
yymsp[-4].minor.yy41 = sqlite3WindowAssemble(pParse, yymsp[0].minor.yy41, yymsp[-2].minor.yy322, yymsp[-1].minor.yy322, 0);
}
break;
- case 313: /* window ::= nm PARTITION BY nexprlist orderby_opt frame_opt */
+ case 315: /* window ::= nm PARTITION BY nexprlist orderby_opt frame_opt */
{
yylhsminor.yy41 = sqlite3WindowAssemble(pParse, yymsp[0].minor.yy41, yymsp[-2].minor.yy322, yymsp[-1].minor.yy322, &yymsp[-5].minor.yy0);
}
yymsp[-5].minor.yy41 = yylhsminor.yy41;
break;
- case 314: /* window ::= ORDER BY sortlist frame_opt */
+ case 316: /* window ::= ORDER BY sortlist frame_opt */
{
yymsp[-3].minor.yy41 = sqlite3WindowAssemble(pParse, yymsp[0].minor.yy41, 0, yymsp[-1].minor.yy322, 0);
}
break;
- case 315: /* window ::= nm ORDER BY sortlist frame_opt */
+ case 317: /* window ::= nm ORDER BY sortlist frame_opt */
{
yylhsminor.yy41 = sqlite3WindowAssemble(pParse, yymsp[0].minor.yy41, 0, yymsp[-1].minor.yy322, &yymsp[-4].minor.yy0);
}
yymsp[-4].minor.yy41 = yylhsminor.yy41;
break;
- case 316: /* window ::= frame_opt */
- case 335: /* filter_over ::= over_clause */ yytestcase(yyruleno==335);
+ case 318: /* window ::= frame_opt */
+ case 337: /* filter_over ::= over_clause */ yytestcase(yyruleno==337);
{
yylhsminor.yy41 = yymsp[0].minor.yy41;
}
yymsp[0].minor.yy41 = yylhsminor.yy41;
break;
- case 317: /* window ::= nm frame_opt */
+ case 319: /* window ::= nm frame_opt */
{
yylhsminor.yy41 = sqlite3WindowAssemble(pParse, yymsp[0].minor.yy41, 0, 0, &yymsp[-1].minor.yy0);
}
yymsp[-1].minor.yy41 = yylhsminor.yy41;
break;
- case 318: /* frame_opt ::= */
+ case 320: /* frame_opt ::= */
{
yymsp[1].minor.yy41 = sqlite3WindowAlloc(pParse, 0, TK_UNBOUNDED, 0, TK_CURRENT, 0, 0);
}
break;
- case 319: /* frame_opt ::= range_or_rows frame_bound_s frame_exclude_opt */
+ case 321: /* frame_opt ::= range_or_rows frame_bound_s frame_exclude_opt */
{
yylhsminor.yy41 = sqlite3WindowAlloc(pParse, yymsp[-2].minor.yy394, yymsp[-1].minor.yy595.eType, yymsp[-1].minor.yy595.pExpr, TK_CURRENT, 0, yymsp[0].minor.yy516);
}
yymsp[-2].minor.yy41 = yylhsminor.yy41;
break;
- case 320: /* frame_opt ::= range_or_rows BETWEEN frame_bound_s AND frame_bound_e frame_exclude_opt */
+ case 322: /* frame_opt ::= range_or_rows BETWEEN frame_bound_s AND frame_bound_e frame_exclude_opt */
{
yylhsminor.yy41 = sqlite3WindowAlloc(pParse, yymsp[-5].minor.yy394, yymsp[-3].minor.yy595.eType, yymsp[-3].minor.yy595.pExpr, yymsp[-1].minor.yy595.eType, yymsp[-1].minor.yy595.pExpr, yymsp[0].minor.yy516);
}
yymsp[-5].minor.yy41 = yylhsminor.yy41;
break;
- case 322: /* frame_bound_s ::= frame_bound */
- case 324: /* frame_bound_e ::= frame_bound */ yytestcase(yyruleno==324);
+ case 324: /* frame_bound_s ::= frame_bound */
+ case 326: /* frame_bound_e ::= frame_bound */ yytestcase(yyruleno==326);
{yylhsminor.yy595 = yymsp[0].minor.yy595;}
yymsp[0].minor.yy595 = yylhsminor.yy595;
break;
- case 323: /* frame_bound_s ::= UNBOUNDED PRECEDING */
- case 325: /* frame_bound_e ::= UNBOUNDED FOLLOWING */ yytestcase(yyruleno==325);
- case 327: /* frame_bound ::= CURRENT ROW */ yytestcase(yyruleno==327);
+ case 325: /* frame_bound_s ::= UNBOUNDED PRECEDING */
+ case 327: /* frame_bound_e ::= UNBOUNDED FOLLOWING */ yytestcase(yyruleno==327);
+ case 329: /* frame_bound ::= CURRENT ROW */ yytestcase(yyruleno==329);
{yylhsminor.yy595.eType = yymsp[-1].major; yylhsminor.yy595.pExpr = 0;}
yymsp[-1].minor.yy595 = yylhsminor.yy595;
break;
- case 326: /* frame_bound ::= expr PRECEDING|FOLLOWING */
+ case 328: /* frame_bound ::= expr PRECEDING|FOLLOWING */
{yylhsminor.yy595.eType = yymsp[0].major; yylhsminor.yy595.pExpr = yymsp[-1].minor.yy528;}
yymsp[-1].minor.yy595 = yylhsminor.yy595;
break;
- case 328: /* frame_exclude_opt ::= */
+ case 330: /* frame_exclude_opt ::= */
{yymsp[1].minor.yy516 = 0;}
break;
- case 329: /* frame_exclude_opt ::= EXCLUDE frame_exclude */
+ case 331: /* frame_exclude_opt ::= EXCLUDE frame_exclude */
{yymsp[-1].minor.yy516 = yymsp[0].minor.yy516;}
break;
- case 330: /* frame_exclude ::= NO OTHERS */
- case 331: /* frame_exclude ::= CURRENT ROW */ yytestcase(yyruleno==331);
+ case 332: /* frame_exclude ::= NO OTHERS */
+ case 333: /* frame_exclude ::= CURRENT ROW */ yytestcase(yyruleno==333);
{yymsp[-1].minor.yy516 = yymsp[-1].major; /*A-overwrites-X*/}
break;
- case 332: /* frame_exclude ::= GROUP|TIES */
+ case 334: /* frame_exclude ::= GROUP|TIES */
{yymsp[0].minor.yy516 = yymsp[0].major; /*A-overwrites-X*/}
break;
- case 333: /* window_clause ::= WINDOW windowdefn_list */
+ case 335: /* window_clause ::= WINDOW windowdefn_list */
{ yymsp[-1].minor.yy41 = yymsp[0].minor.yy41; }
break;
- case 334: /* filter_over ::= filter_clause over_clause */
+ case 336: /* filter_over ::= filter_clause over_clause */
{
if( yymsp[0].minor.yy41 ){
yymsp[0].minor.yy41->pFilter = yymsp[-1].minor.yy528;
@@ -166383,7 +170343,7 @@ static YYACTIONTYPE yy_reduce(
}
yymsp[-1].minor.yy41 = yylhsminor.yy41;
break;
- case 336: /* filter_over ::= filter_clause */
+ case 338: /* filter_over ::= filter_clause */
{
yylhsminor.yy41 = (Window*)sqlite3DbMallocZero(pParse->db, sizeof(Window));
if( yylhsminor.yy41 ){
@@ -166395,13 +170355,13 @@ static YYACTIONTYPE yy_reduce(
}
yymsp[0].minor.yy41 = yylhsminor.yy41;
break;
- case 337: /* over_clause ::= OVER LP window RP */
+ case 339: /* over_clause ::= OVER LP window RP */
{
yymsp[-3].minor.yy41 = yymsp[-1].minor.yy41;
assert( yymsp[-3].minor.yy41!=0 );
}
break;
- case 338: /* over_clause ::= OVER nm */
+ case 340: /* over_clause ::= OVER nm */
{
yymsp[-1].minor.yy41 = (Window*)sqlite3DbMallocZero(pParse->db, sizeof(Window));
if( yymsp[-1].minor.yy41 ){
@@ -166409,72 +170369,73 @@ static YYACTIONTYPE yy_reduce(
}
}
break;
- case 339: /* filter_clause ::= FILTER LP WHERE expr RP */
+ case 341: /* filter_clause ::= FILTER LP WHERE expr RP */
{ yymsp[-4].minor.yy528 = yymsp[-1].minor.yy528; }
break;
default:
- /* (340) input ::= cmdlist */ yytestcase(yyruleno==340);
- /* (341) cmdlist ::= cmdlist ecmd */ yytestcase(yyruleno==341);
- /* (342) cmdlist ::= ecmd (OPTIMIZED OUT) */ assert(yyruleno!=342);
- /* (343) ecmd ::= SEMI */ yytestcase(yyruleno==343);
- /* (344) ecmd ::= cmdx SEMI */ yytestcase(yyruleno==344);
- /* (345) ecmd ::= explain cmdx SEMI (NEVER REDUCES) */ assert(yyruleno!=345);
- /* (346) trans_opt ::= */ yytestcase(yyruleno==346);
- /* (347) trans_opt ::= TRANSACTION */ yytestcase(yyruleno==347);
- /* (348) trans_opt ::= TRANSACTION nm */ yytestcase(yyruleno==348);
- /* (349) savepoint_opt ::= SAVEPOINT */ yytestcase(yyruleno==349);
- /* (350) savepoint_opt ::= */ yytestcase(yyruleno==350);
- /* (351) cmd ::= create_table create_table_args */ yytestcase(yyruleno==351);
- /* (352) table_option_set ::= table_option (OPTIMIZED OUT) */ assert(yyruleno!=352);
- /* (353) columnlist ::= columnlist COMMA columnname carglist */ yytestcase(yyruleno==353);
- /* (354) columnlist ::= columnname carglist */ yytestcase(yyruleno==354);
- /* (355) nm ::= ID|INDEXED */ yytestcase(yyruleno==355);
- /* (356) nm ::= STRING */ yytestcase(yyruleno==356);
- /* (357) nm ::= JOIN_KW */ yytestcase(yyruleno==357);
- /* (358) typetoken ::= typename */ yytestcase(yyruleno==358);
- /* (359) typename ::= ID|STRING */ yytestcase(yyruleno==359);
- /* (360) signed ::= plus_num (OPTIMIZED OUT) */ assert(yyruleno!=360);
- /* (361) signed ::= minus_num (OPTIMIZED OUT) */ assert(yyruleno!=361);
- /* (362) carglist ::= carglist ccons */ yytestcase(yyruleno==362);
- /* (363) carglist ::= */ yytestcase(yyruleno==363);
- /* (364) ccons ::= NULL onconf */ yytestcase(yyruleno==364);
- /* (365) ccons ::= GENERATED ALWAYS AS generated */ yytestcase(yyruleno==365);
- /* (366) ccons ::= AS generated */ yytestcase(yyruleno==366);
- /* (367) conslist_opt ::= COMMA conslist */ yytestcase(yyruleno==367);
- /* (368) conslist ::= conslist tconscomma tcons */ yytestcase(yyruleno==368);
- /* (369) conslist ::= tcons (OPTIMIZED OUT) */ assert(yyruleno!=369);
- /* (370) tconscomma ::= */ yytestcase(yyruleno==370);
- /* (371) defer_subclause_opt ::= defer_subclause (OPTIMIZED OUT) */ assert(yyruleno!=371);
- /* (372) resolvetype ::= raisetype (OPTIMIZED OUT) */ assert(yyruleno!=372);
- /* (373) selectnowith ::= oneselect (OPTIMIZED OUT) */ assert(yyruleno!=373);
- /* (374) oneselect ::= values */ yytestcase(yyruleno==374);
- /* (375) sclp ::= selcollist COMMA */ yytestcase(yyruleno==375);
- /* (376) as ::= ID|STRING */ yytestcase(yyruleno==376);
- /* (377) returning ::= */ yytestcase(yyruleno==377);
- /* (378) expr ::= term (OPTIMIZED OUT) */ assert(yyruleno!=378);
- /* (379) likeop ::= LIKE_KW|MATCH */ yytestcase(yyruleno==379);
- /* (380) exprlist ::= nexprlist */ yytestcase(yyruleno==380);
- /* (381) nmnum ::= plus_num (OPTIMIZED OUT) */ assert(yyruleno!=381);
- /* (382) nmnum ::= nm (OPTIMIZED OUT) */ assert(yyruleno!=382);
- /* (383) nmnum ::= ON */ yytestcase(yyruleno==383);
- /* (384) nmnum ::= DELETE */ yytestcase(yyruleno==384);
- /* (385) nmnum ::= DEFAULT */ yytestcase(yyruleno==385);
- /* (386) plus_num ::= INTEGER|FLOAT */ yytestcase(yyruleno==386);
- /* (387) foreach_clause ::= */ yytestcase(yyruleno==387);
- /* (388) foreach_clause ::= FOR EACH ROW */ yytestcase(yyruleno==388);
- /* (389) trnm ::= nm */ yytestcase(yyruleno==389);
- /* (390) tridxby ::= */ yytestcase(yyruleno==390);
- /* (391) database_kw_opt ::= DATABASE */ yytestcase(yyruleno==391);
- /* (392) database_kw_opt ::= */ yytestcase(yyruleno==392);
- /* (393) kwcolumn_opt ::= */ yytestcase(yyruleno==393);
- /* (394) kwcolumn_opt ::= COLUMNKW */ yytestcase(yyruleno==394);
- /* (395) vtabarglist ::= vtabarg */ yytestcase(yyruleno==395);
- /* (396) vtabarglist ::= vtabarglist COMMA vtabarg */ yytestcase(yyruleno==396);
- /* (397) vtabarg ::= vtabarg vtabargtoken */ yytestcase(yyruleno==397);
- /* (398) anylist ::= */ yytestcase(yyruleno==398);
- /* (399) anylist ::= anylist LP anylist RP */ yytestcase(yyruleno==399);
- /* (400) anylist ::= anylist ANY */ yytestcase(yyruleno==400);
- /* (401) with ::= */ yytestcase(yyruleno==401);
+ /* (342) input ::= cmdlist */ yytestcase(yyruleno==342);
+ /* (343) cmdlist ::= cmdlist ecmd */ yytestcase(yyruleno==343);
+ /* (344) cmdlist ::= ecmd (OPTIMIZED OUT) */ assert(yyruleno!=344);
+ /* (345) ecmd ::= SEMI */ yytestcase(yyruleno==345);
+ /* (346) ecmd ::= cmdx SEMI */ yytestcase(yyruleno==346);
+ /* (347) ecmd ::= explain cmdx SEMI (NEVER REDUCES) */ assert(yyruleno!=347);
+ /* (348) trans_opt ::= */ yytestcase(yyruleno==348);
+ /* (349) trans_opt ::= TRANSACTION */ yytestcase(yyruleno==349);
+ /* (350) trans_opt ::= TRANSACTION nm */ yytestcase(yyruleno==350);
+ /* (351) savepoint_opt ::= SAVEPOINT */ yytestcase(yyruleno==351);
+ /* (352) savepoint_opt ::= */ yytestcase(yyruleno==352);
+ /* (353) cmd ::= create_table create_table_args */ yytestcase(yyruleno==353);
+ /* (354) table_option_set ::= table_option (OPTIMIZED OUT) */ assert(yyruleno!=354);
+ /* (355) columnlist ::= columnlist COMMA columnname carglist */ yytestcase(yyruleno==355);
+ /* (356) columnlist ::= columnname carglist */ yytestcase(yyruleno==356);
+ /* (357) nm ::= ID|INDEXED */ yytestcase(yyruleno==357);
+ /* (358) nm ::= STRING */ yytestcase(yyruleno==358);
+ /* (359) nm ::= JOIN_KW */ yytestcase(yyruleno==359);
+ /* (360) typetoken ::= typename */ yytestcase(yyruleno==360);
+ /* (361) typename ::= ID|STRING */ yytestcase(yyruleno==361);
+ /* (362) signed ::= plus_num (OPTIMIZED OUT) */ assert(yyruleno!=362);
+ /* (363) signed ::= minus_num (OPTIMIZED OUT) */ assert(yyruleno!=363);
+ /* (364) carglist ::= carglist ccons */ yytestcase(yyruleno==364);
+ /* (365) carglist ::= */ yytestcase(yyruleno==365);
+ /* (366) ccons ::= NULL onconf */ yytestcase(yyruleno==366);
+ /* (367) ccons ::= GENERATED ALWAYS AS generated */ yytestcase(yyruleno==367);
+ /* (368) ccons ::= AS generated */ yytestcase(yyruleno==368);
+ /* (369) conslist_opt ::= COMMA conslist */ yytestcase(yyruleno==369);
+ /* (370) conslist ::= conslist tconscomma tcons */ yytestcase(yyruleno==370);
+ /* (371) conslist ::= tcons (OPTIMIZED OUT) */ assert(yyruleno!=371);
+ /* (372) tconscomma ::= */ yytestcase(yyruleno==372);
+ /* (373) defer_subclause_opt ::= defer_subclause (OPTIMIZED OUT) */ assert(yyruleno!=373);
+ /* (374) resolvetype ::= raisetype (OPTIMIZED OUT) */ assert(yyruleno!=374);
+ /* (375) selectnowith ::= oneselect (OPTIMIZED OUT) */ assert(yyruleno!=375);
+ /* (376) oneselect ::= values */ yytestcase(yyruleno==376);
+ /* (377) sclp ::= selcollist COMMA */ yytestcase(yyruleno==377);
+ /* (378) as ::= ID|STRING */ yytestcase(yyruleno==378);
+ /* (379) indexed_opt ::= indexed_by (OPTIMIZED OUT) */ assert(yyruleno!=379);
+ /* (380) returning ::= */ yytestcase(yyruleno==380);
+ /* (381) expr ::= term (OPTIMIZED OUT) */ assert(yyruleno!=381);
+ /* (382) likeop ::= LIKE_KW|MATCH */ yytestcase(yyruleno==382);
+ /* (383) exprlist ::= nexprlist */ yytestcase(yyruleno==383);
+ /* (384) nmnum ::= plus_num (OPTIMIZED OUT) */ assert(yyruleno!=384);
+ /* (385) nmnum ::= nm (OPTIMIZED OUT) */ assert(yyruleno!=385);
+ /* (386) nmnum ::= ON */ yytestcase(yyruleno==386);
+ /* (387) nmnum ::= DELETE */ yytestcase(yyruleno==387);
+ /* (388) nmnum ::= DEFAULT */ yytestcase(yyruleno==388);
+ /* (389) plus_num ::= INTEGER|FLOAT */ yytestcase(yyruleno==389);
+ /* (390) foreach_clause ::= */ yytestcase(yyruleno==390);
+ /* (391) foreach_clause ::= FOR EACH ROW */ yytestcase(yyruleno==391);
+ /* (392) trnm ::= nm */ yytestcase(yyruleno==392);
+ /* (393) tridxby ::= */ yytestcase(yyruleno==393);
+ /* (394) database_kw_opt ::= DATABASE */ yytestcase(yyruleno==394);
+ /* (395) database_kw_opt ::= */ yytestcase(yyruleno==395);
+ /* (396) kwcolumn_opt ::= */ yytestcase(yyruleno==396);
+ /* (397) kwcolumn_opt ::= COLUMNKW */ yytestcase(yyruleno==397);
+ /* (398) vtabarglist ::= vtabarg */ yytestcase(yyruleno==398);
+ /* (399) vtabarglist ::= vtabarglist COMMA vtabarg */ yytestcase(yyruleno==399);
+ /* (400) vtabarg ::= vtabarg vtabargtoken */ yytestcase(yyruleno==400);
+ /* (401) anylist ::= */ yytestcase(yyruleno==401);
+ /* (402) anylist ::= anylist LP anylist RP */ yytestcase(yyruleno==402);
+ /* (403) anylist ::= anylist ANY */ yytestcase(yyruleno==403);
+ /* (404) with ::= */ yytestcase(yyruleno==404);
break;
/********** End reduce actions ************************************************/
};
@@ -167921,6 +171882,7 @@ SQLITE_PRIVATE int sqlite3RunParser(Parse *pParse, const char *zSql){
mxSqlLen -= n;
if( mxSqlLen<0 ){
pParse->rc = SQLITE_TOOBIG;
+ pParse->nErr++;
break;
}
#ifndef SQLITE_OMIT_WINDOWFUNC
@@ -168017,7 +171979,7 @@ SQLITE_PRIVATE int sqlite3RunParser(Parse *pParse, const char *zSql){
if( pParse->pNewTrigger && !IN_RENAME_OBJECT ){
sqlite3DeleteTrigger(db, pParse->pNewTrigger);
}
- sqlite3DbFree(db, pParse->pVList);
+ if( pParse->pVList ) sqlite3DbNNFreeNN(db, pParse->pVList);
db->pParse = pParentParse;
assert( nErr==0 || pParse->rc!=SQLITE_OK );
return nErr;
@@ -169373,18 +173335,19 @@ static int setupLookaside(sqlite3 *db, void *pBuf, int sz, int cnt){
db->lookaside.bMalloced = pBuf==0 ?1:0;
db->lookaside.nSlot = nBig+nSm;
}else{
- db->lookaside.pStart = db;
+ db->lookaside.pStart = 0;
#ifndef SQLITE_OMIT_TWOSIZE_LOOKASIDE
db->lookaside.pSmallInit = 0;
db->lookaside.pSmallFree = 0;
- db->lookaside.pMiddle = db;
+ db->lookaside.pMiddle = 0;
#endif /* SQLITE_OMIT_TWOSIZE_LOOKASIDE */
- db->lookaside.pEnd = db;
+ db->lookaside.pEnd = 0;
db->lookaside.bDisable = 1;
db->lookaside.sz = 0;
db->lookaside.bMalloced = 0;
db->lookaside.nSlot = 0;
}
+ db->lookaside.pTrueEnd = db->lookaside.pEnd;
assert( sqlite3LookasideUsed(db,0)==0 );
#endif /* SQLITE_OMIT_LOOKASIDE */
return SQLITE_OK;
@@ -169463,6 +173426,7 @@ SQLITE_API int sqlite3_db_cacheflush(sqlite3 *db){
SQLITE_API int sqlite3_db_config(sqlite3 *db, int op, ...){
va_list ap;
int rc;
+ sqlite3_mutex_enter(db->mutex);
va_start(ap, op);
switch( op ){
case SQLITE_DBCONFIG_MAINDBNAME: {
@@ -169528,6 +173492,7 @@ SQLITE_API int sqlite3_db_config(sqlite3 *db, int op, ...){
}
}
va_end(ap);
+ sqlite3_mutex_leave(db->mutex);
return rc;
}
@@ -170664,7 +174629,7 @@ SQLITE_API int sqlite3_overload_function(
rc = sqlite3FindFunction(db, zName, nArg, SQLITE_UTF8, 0)!=0;
sqlite3_mutex_leave(db->mutex);
if( rc ) return SQLITE_OK;
- zCopy = sqlite3_mprintf(zName);
+ zCopy = sqlite3_mprintf("%s", zName);
if( zCopy==0 ) return SQLITE_NOMEM;
return sqlite3_create_function_v2(db, zName, nArg, SQLITE_UTF8,
zCopy, sqlite3InvalidFunction, 0, 0, sqlite3_free);
@@ -171898,6 +175863,19 @@ static int openDatabase(
goto opendb_out;
}
+#if SQLITE_OS_UNIX && defined(SQLITE_OS_KV_OPTIONAL)
+ /* Process magic filenames ":localStorage:" and ":sessionStorage:" */
+ if( zFilename && zFilename[0]==':' ){
+ if( strcmp(zFilename, ":localStorage:")==0 ){
+ zFilename = "file:local?vfs=kvvfs";
+ flags |= SQLITE_OPEN_URI;
+ }else if( strcmp(zFilename, ":sessionStorage:")==0 ){
+ zFilename = "file:session?vfs=kvvfs";
+ flags |= SQLITE_OPEN_URI;
+ }
+ }
+#endif /* SQLITE_OS_UNIX && defined(SQLITE_OS_KV_OPTIONAL) */
+
/* Parse the filename/URI argument
**
** Only allow sensible combinations of bits in the flags argument.
@@ -171928,6 +175906,12 @@ static int openDatabase(
sqlite3_free(zErrMsg);
goto opendb_out;
}
+ assert( db->pVfs!=0 );
+#if SQLITE_OS_KV || defined(SQLITE_OS_KV_OPTIONAL)
+ if( sqlite3_stricmp(db->pVfs->zName, "kvvfs")==0 ){
+ db->temp_store = 2;
+ }
+#endif
/* Open the backend database driver */
rc = sqlite3BtreeOpen(db->pVfs, zOpen, db, &db->aDb[0].pBt, 0,
@@ -172477,6 +176461,9 @@ SQLITE_API int sqlite3_file_control(sqlite3 *db, const char *zDbName, int op, vo
sqlite3BtreeSetPageSize(pBtree, 0, iNew, 0);
}
rc = SQLITE_OK;
+ }else if( op==SQLITE_FCNTL_RESET_CACHE ){
+ sqlite3BtreeClearCache(pBtree);
+ rc = SQLITE_OK;
}else{
int nSave = db->busyHandler.nBusy;
rc = sqlite3OsFileControl(fd, op, pArg);
@@ -172646,6 +176633,28 @@ SQLITE_API int sqlite3_test_control(int op, ...){
volatile int x = 0;
assert( /*side-effects-ok*/ (x = va_arg(ap,int))!=0 );
rc = x;
+#if defined(SQLITE_DEBUG)
+ /* Invoke these debugging routines so that the compiler does not
+ ** issue "defined but not used" warnings. */
+ if( x==9999 ){
+ sqlite3ShowExpr(0);
+ sqlite3ShowExpr(0);
+ sqlite3ShowExprList(0);
+ sqlite3ShowIdList(0);
+ sqlite3ShowSrcList(0);
+ sqlite3ShowWith(0);
+ sqlite3ShowUpsert(0);
+ sqlite3ShowTriggerStep(0);
+ sqlite3ShowTriggerStepList(0);
+ sqlite3ShowTrigger(0);
+ sqlite3ShowTriggerList(0);
+#ifndef SQLITE_OMIT_WINDOWFUNC
+ sqlite3ShowWindow(0);
+ sqlite3ShowWinFunc(0);
+#endif
+ sqlite3ShowSelect(0);
+ }
+#endif
break;
}
@@ -172907,8 +176916,8 @@ SQLITE_API int sqlite3_test_control(int op, ...){
**
** "ptr" is a pointer to a u32.
**
- ** op==0 Store the current sqlite3SelectTrace in *ptr
- ** op==1 Set sqlite3SelectTrace to the value *ptr
+ ** op==0 Store the current sqlite3TreeTrace in *ptr
+ ** op==1 Set sqlite3TreeTrace to the value *ptr
** op==3 Store the current sqlite3WhereTrace in *ptr
** op==3 Set sqlite3WhereTrace to the value *ptr
*/
@@ -172916,10 +176925,10 @@ SQLITE_API int sqlite3_test_control(int op, ...){
int opTrace = va_arg(ap, int);
u32 *ptr = va_arg(ap, u32*);
switch( opTrace ){
- case 0: *ptr = sqlite3SelectTrace; break;
- case 1: sqlite3SelectTrace = *ptr; break;
- case 2: *ptr = sqlite3WhereTrace; break;
- case 3: sqlite3WhereTrace = *ptr; break;
+ case 0: *ptr = sqlite3TreeTrace; break;
+ case 1: sqlite3TreeTrace = *ptr; break;
+ case 2: *ptr = sqlite3WhereTrace; break;
+ case 3: sqlite3WhereTrace = *ptr; break;
}
break;
}
@@ -172936,10 +176945,12 @@ SQLITE_API int sqlite3_test_control(int op, ...){
case SQLITE_TESTCTRL_LOGEST: {
double rIn = va_arg(ap, double);
LogEst rLogEst = sqlite3LogEstFromDouble(rIn);
- u64 iInt = sqlite3LogEstToInt(rLogEst);
- va_arg(ap, int*)[0] = rLogEst;
- va_arg(ap, u64*)[0] = iInt;
- va_arg(ap, int*)[0] = sqlite3LogEst(iInt);
+ int *pI1 = va_arg(ap,int*);
+ u64 *pU64 = va_arg(ap,u64*);
+ int *pI2 = va_arg(ap,int*);
+ *pI1 = rLogEst;
+ *pU64 = sqlite3LogEstToInt(rLogEst);
+ *pI2 = sqlite3LogEst(*pU64);
break;
}
@@ -173013,7 +177024,7 @@ static char *appendText(char *p, const char *z){
** Memory layout must be compatible with that generated by the pager
** and expected by sqlite3_uri_parameter() and databaseName().
*/
-SQLITE_API char *sqlite3_create_filename(
+SQLITE_API const char *sqlite3_create_filename(
const char *zDatabase,
const char *zJournal,
const char *zWal,
@@ -173049,10 +177060,10 @@ SQLITE_API char *sqlite3_create_filename(
** error to call this routine with any parameter other than a pointer
** previously obtained from sqlite3_create_filename() or a NULL pointer.
*/
-SQLITE_API void sqlite3_free_filename(char *p){
+SQLITE_API void sqlite3_free_filename(const char *p){
if( p==0 ) return;
- p = (char*)databaseName(p);
- sqlite3_free(p - 4);
+ p = databaseName(p);
+ sqlite3_free((char*)p - 4);
}
@@ -173155,6 +177166,24 @@ SQLITE_PRIVATE Btree *sqlite3DbNameToBtree(sqlite3 *db, const char *zDbName){
}
/*
+** Return the name of the N-th database schema. Return NULL if N is out
+** of range.
+*/
+SQLITE_API const char *sqlite3_db_name(sqlite3 *db, int N){
+#ifdef SQLITE_ENABLE_API_ARMOR
+ if( !sqlite3SafetyCheckOk(db) ){
+ (void)SQLITE_MISUSE_BKPT;
+ return 0;
+ }
+#endif
+ if( N<0 || N>=db->nDb ){
+ return 0;
+ }else{
+ return db->aDb[N].zDbSName;
+ }
+}
+
+/*
** Return the filename of the database associated with a database
** connection.
*/
@@ -173285,8 +177314,8 @@ SQLITE_API int sqlite3_snapshot_open(
*/
SQLITE_API int sqlite3_snapshot_recover(sqlite3 *db, const char *zDb){
int rc = SQLITE_ERROR;
- int iDb;
#ifndef SQLITE_OMIT_WAL
+ int iDb;
#ifdef SQLITE_ENABLE_API_ARMOR
if( !sqlite3SafetyCheckOk(db) ){
@@ -174841,7 +178870,7 @@ struct Fts3MultiSegReader {
int nAdvance; /* How many seg-readers to advance */
Fts3SegFilter *pFilter; /* Pointer to filter object */
char *aBuffer; /* Buffer to merge doclists in */
- int nBuffer; /* Allocated size of aBuffer[] in bytes */
+ i64 nBuffer; /* Allocated size of aBuffer[] in bytes */
int iColFilter; /* If >=0, filter for this column */
int bRestart;
@@ -177537,7 +181566,7 @@ static int fts3TermSelectMerge(
**
** Similar padding is added in the fts3DoclistOrMerge() function.
*/
- pTS->aaOutput[0] = sqlite3_malloc(nDoclist + FTS3_VARINT_MAX + 1);
+ pTS->aaOutput[0] = sqlite3_malloc64((i64)nDoclist + FTS3_VARINT_MAX + 1);
pTS->anOutput[0] = nDoclist;
if( pTS->aaOutput[0] ){
memcpy(pTS->aaOutput[0], aDoclist, nDoclist);
@@ -178957,8 +182986,7 @@ static int fts3EvalDeferredPhrase(Fts3Cursor *pCsr, Fts3Phrase *pPhrase){
char *aPoslist = 0; /* Position list for deferred tokens */
int nPoslist = 0; /* Number of bytes in aPoslist */
int iPrev = -1; /* Token number of previous deferred token */
-
- assert( pPhrase->doclist.bFreeList==0 );
+ char *aFree = (pPhrase->doclist.bFreeList ? pPhrase->doclist.pList : 0);
for(iToken=0; iToken<pPhrase->nToken; iToken++){
Fts3PhraseToken *pToken = &pPhrase->aToken[iToken];
@@ -178972,6 +183000,7 @@ static int fts3EvalDeferredPhrase(Fts3Cursor *pCsr, Fts3Phrase *pPhrase){
if( pList==0 ){
sqlite3_free(aPoslist);
+ sqlite3_free(aFree);
pPhrase->doclist.pList = 0;
pPhrase->doclist.nList = 0;
return SQLITE_OK;
@@ -178992,6 +183021,7 @@ static int fts3EvalDeferredPhrase(Fts3Cursor *pCsr, Fts3Phrase *pPhrase){
nPoslist = (int)(aOut - aPoslist);
if( nPoslist==0 ){
sqlite3_free(aPoslist);
+ sqlite3_free(aFree);
pPhrase->doclist.pList = 0;
pPhrase->doclist.nList = 0;
return SQLITE_OK;
@@ -179024,13 +183054,14 @@ static int fts3EvalDeferredPhrase(Fts3Cursor *pCsr, Fts3Phrase *pPhrase){
nDistance = iPrev - nMaxUndeferred;
}
- aOut = (char *)sqlite3_malloc(nPoslist+8);
+ aOut = (char *)sqlite3Fts3MallocZero(nPoslist+FTS3_BUFFER_PADDING);
if( !aOut ){
sqlite3_free(aPoslist);
return SQLITE_NOMEM;
}
pPhrase->doclist.pList = aOut;
+ assert( p1 && p2 );
if( fts3PoslistPhraseMerge(&aOut, nDistance, 0, 1, &p1, &p2) ){
pPhrase->doclist.bFreeList = 1;
pPhrase->doclist.nList = (int)(aOut - pPhrase->doclist.pList);
@@ -179043,6 +183074,7 @@ static int fts3EvalDeferredPhrase(Fts3Cursor *pCsr, Fts3Phrase *pPhrase){
}
}
+ if( pPhrase->doclist.pList!=aFree ) sqlite3_free(aFree);
return SQLITE_OK;
}
#endif /* SQLITE_DISABLE_FTS4_DEFERRED */
@@ -179391,7 +183423,7 @@ static int fts3EvalIncrPhraseNext(
if( bEof==0 ){
int nList = 0;
int nByte = a[p->nToken-1].nList;
- char *aDoclist = sqlite3_malloc(nByte+FTS3_BUFFER_PADDING);
+ char *aDoclist = sqlite3_malloc64((i64)nByte+FTS3_BUFFER_PADDING);
if( !aDoclist ) return SQLITE_NOMEM;
memcpy(aDoclist, a[p->nToken-1].pList, nByte+1);
memset(&aDoclist[nByte], 0, FTS3_BUFFER_PADDING);
@@ -180217,11 +184249,10 @@ static int fts3EvalTestExpr(
default: {
#ifndef SQLITE_DISABLE_FTS4_DEFERRED
- if( pCsr->pDeferred
- && (pExpr->iDocid==pCsr->iPrevId || pExpr->bDeferred)
- ){
+ if( pCsr->pDeferred && (pExpr->bDeferred || (
+ pExpr->iDocid==pCsr->iPrevId && pExpr->pPhrase->doclist.pList
+ ))){
Fts3Phrase *pPhrase = pExpr->pPhrase;
- assert( pExpr->bDeferred || pPhrase->doclist.bFreeList==0 );
if( pExpr->bDeferred ){
fts3EvalInvalidatePoslist(pPhrase);
}
@@ -183628,7 +187659,7 @@ static int porterNext(
if( n>c->nAllocated ){
char *pNew;
c->nAllocated = n+20;
- pNew = sqlite3_realloc(c->zToken, c->nAllocated);
+ pNew = sqlite3_realloc64(c->zToken, c->nAllocated);
if( !pNew ) return SQLITE_NOMEM;
c->zToken = pNew;
}
@@ -184380,7 +188411,7 @@ static int simpleNext(
if( n>c->nTokenAllocated ){
char *pNew;
c->nTokenAllocated = n+20;
- pNew = sqlite3_realloc(c->pToken, c->nTokenAllocated);
+ pNew = sqlite3_realloc64(c->pToken, c->nTokenAllocated);
if( !pNew ) return SQLITE_NOMEM;
c->pToken = pNew;
}
@@ -185542,7 +189573,7 @@ static int fts3PendingListAppendVarint(
/* Allocate or grow the PendingList as required. */
if( !p ){
- p = sqlite3_malloc(sizeof(*p) + 100);
+ p = sqlite3_malloc64(sizeof(*p) + 100);
if( !p ){
return SQLITE_NOMEM;
}
@@ -185551,14 +189582,14 @@ static int fts3PendingListAppendVarint(
p->nData = 0;
}
else if( p->nData+FTS3_VARINT_MAX+1>p->nSpace ){
- int nNew = p->nSpace * 2;
- p = sqlite3_realloc(p, sizeof(*p) + nNew);
+ i64 nNew = p->nSpace * 2;
+ p = sqlite3_realloc64(p, sizeof(*p) + nNew);
if( !p ){
sqlite3_free(*pp);
*pp = 0;
return SQLITE_NOMEM;
}
- p->nSpace = nNew;
+ p->nSpace = (int)nNew;
p->aData = (char *)&p[1];
}
@@ -186115,7 +190146,7 @@ SQLITE_PRIVATE int sqlite3Fts3ReadBlock(
int nByte = sqlite3_blob_bytes(p->pSegments);
*pnBlob = nByte;
if( paBlob ){
- char *aByte = sqlite3_malloc(nByte + FTS3_NODE_PADDING);
+ char *aByte = sqlite3_malloc64((i64)nByte + FTS3_NODE_PADDING);
if( !aByte ){
rc = SQLITE_NOMEM;
}else{
@@ -186232,7 +190263,7 @@ static int fts3SegReaderNext(
int nTerm = fts3HashKeysize(pElem);
if( (nTerm+1)>pReader->nTermAlloc ){
sqlite3_free(pReader->zTerm);
- pReader->zTerm = (char*)sqlite3_malloc((nTerm+1)*2);
+ pReader->zTerm = (char*)sqlite3_malloc64(((i64)nTerm+1)*2);
if( !pReader->zTerm ) return SQLITE_NOMEM;
pReader->nTermAlloc = (nTerm+1)*2;
}
@@ -186240,7 +190271,7 @@ static int fts3SegReaderNext(
pReader->zTerm[nTerm] = '\0';
pReader->nTerm = nTerm;
- aCopy = (char*)sqlite3_malloc(nCopy);
+ aCopy = (char*)sqlite3_malloc64(nCopy);
if( !aCopy ) return SQLITE_NOMEM;
memcpy(aCopy, pList->aData, nCopy);
pReader->nNode = pReader->nDoclist = nCopy;
@@ -186527,7 +190558,7 @@ SQLITE_PRIVATE int sqlite3Fts3SegReaderNew(
nExtra = nRoot + FTS3_NODE_PADDING;
}
- pReader = (Fts3SegReader *)sqlite3_malloc(sizeof(Fts3SegReader) + nExtra);
+ pReader = (Fts3SegReader *)sqlite3_malloc64(sizeof(Fts3SegReader) + nExtra);
if( !pReader ){
return SQLITE_NOMEM;
}
@@ -186619,7 +190650,7 @@ SQLITE_PRIVATE int sqlite3Fts3SegReaderPending(
if( nElem==nAlloc ){
Fts3HashElem **aElem2;
nAlloc += 16;
- aElem2 = (Fts3HashElem **)sqlite3_realloc(
+ aElem2 = (Fts3HashElem **)sqlite3_realloc64(
aElem, nAlloc*sizeof(Fts3HashElem *)
);
if( !aElem2 ){
@@ -186953,7 +190984,7 @@ static int fts3NodeAddTerm(
** this is not expected to be a serious problem.
*/
assert( pTree->aData==(char *)&pTree[1] );
- pTree->aData = (char *)sqlite3_malloc(nReq);
+ pTree->aData = (char *)sqlite3_malloc64(nReq);
if( !pTree->aData ){
return SQLITE_NOMEM;
}
@@ -186971,7 +191002,7 @@ static int fts3NodeAddTerm(
if( isCopyTerm ){
if( pTree->nMalloc<nTerm ){
- char *zNew = sqlite3_realloc(pTree->zMalloc, nTerm*2);
+ char *zNew = sqlite3_realloc64(pTree->zMalloc, (i64)nTerm*2);
if( !zNew ){
return SQLITE_NOMEM;
}
@@ -186997,7 +191028,7 @@ static int fts3NodeAddTerm(
** now. Instead, the term is inserted into the parent of pTree. If pTree
** has no parent, one is created here.
*/
- pNew = (SegmentNode *)sqlite3_malloc(sizeof(SegmentNode) + p->nNodeSize);
+ pNew = (SegmentNode *)sqlite3_malloc64(sizeof(SegmentNode) + p->nNodeSize);
if( !pNew ){
return SQLITE_NOMEM;
}
@@ -187135,7 +191166,7 @@ static int fts3SegWriterAdd(
){
int nPrefix; /* Size of term prefix in bytes */
int nSuffix; /* Size of term suffix in bytes */
- int nReq; /* Number of bytes required on leaf page */
+ i64 nReq; /* Number of bytes required on leaf page */
int nData;
SegmentWriter *pWriter = *ppWriter;
@@ -187144,13 +191175,13 @@ static int fts3SegWriterAdd(
sqlite3_stmt *pStmt;
/* Allocate the SegmentWriter structure */
- pWriter = (SegmentWriter *)sqlite3_malloc(sizeof(SegmentWriter));
+ pWriter = (SegmentWriter *)sqlite3_malloc64(sizeof(SegmentWriter));
if( !pWriter ) return SQLITE_NOMEM;
memset(pWriter, 0, sizeof(SegmentWriter));
*ppWriter = pWriter;
/* Allocate a buffer in which to accumulate data */
- pWriter->aData = (char *)sqlite3_malloc(p->nNodeSize);
+ pWriter->aData = (char *)sqlite3_malloc64(p->nNodeSize);
if( !pWriter->aData ) return SQLITE_NOMEM;
pWriter->nSize = p->nNodeSize;
@@ -187225,7 +191256,7 @@ static int fts3SegWriterAdd(
** the buffer to make it large enough.
*/
if( nReq>pWriter->nSize ){
- char *aNew = sqlite3_realloc(pWriter->aData, nReq);
+ char *aNew = sqlite3_realloc64(pWriter->aData, nReq);
if( !aNew ) return SQLITE_NOMEM;
pWriter->aData = aNew;
pWriter->nSize = nReq;
@@ -187250,7 +191281,7 @@ static int fts3SegWriterAdd(
*/
if( isCopyTerm ){
if( nTerm>pWriter->nMalloc ){
- char *zNew = sqlite3_realloc(pWriter->zMalloc, nTerm*2);
+ char *zNew = sqlite3_realloc64(pWriter->zMalloc, (i64)nTerm*2);
if( !zNew ){
return SQLITE_NOMEM;
}
@@ -187558,12 +191589,12 @@ static void fts3ColumnFilter(
static int fts3MsrBufferData(
Fts3MultiSegReader *pMsr, /* Multi-segment-reader handle */
char *pList,
- int nList
+ i64 nList
){
if( nList>pMsr->nBuffer ){
char *pNew;
pMsr->nBuffer = nList*2;
- pNew = (char *)sqlite3_realloc(pMsr->aBuffer, pMsr->nBuffer);
+ pNew = (char *)sqlite3_realloc64(pMsr->aBuffer, pMsr->nBuffer);
if( !pNew ) return SQLITE_NOMEM;
pMsr->aBuffer = pNew;
}
@@ -187619,7 +191650,7 @@ SQLITE_PRIVATE int sqlite3Fts3MsrIncrNext(
fts3SegReaderSort(pMsr->apSegment, nMerge, j, xCmp);
if( nList>0 && fts3SegReaderIsPending(apSegment[0]) ){
- rc = fts3MsrBufferData(pMsr, pList, nList+1);
+ rc = fts3MsrBufferData(pMsr, pList, (i64)nList+1);
if( rc!=SQLITE_OK ) return rc;
assert( (pMsr->aBuffer[nList] & 0xFE)==0x00 );
pList = pMsr->aBuffer;
@@ -187756,11 +191787,11 @@ SQLITE_PRIVATE int sqlite3Fts3MsrIncrRestart(Fts3MultiSegReader *pCsr){
return SQLITE_OK;
}
-static int fts3GrowSegReaderBuffer(Fts3MultiSegReader *pCsr, int nReq){
+static int fts3GrowSegReaderBuffer(Fts3MultiSegReader *pCsr, i64 nReq){
if( nReq>pCsr->nBuffer ){
char *aNew;
pCsr->nBuffer = nReq*2;
- aNew = sqlite3_realloc(pCsr->aBuffer, pCsr->nBuffer);
+ aNew = sqlite3_realloc64(pCsr->aBuffer, pCsr->nBuffer);
if( !aNew ){
return SQLITE_NOMEM;
}
@@ -187851,7 +191882,8 @@ SQLITE_PRIVATE int sqlite3Fts3SegReaderStep(
){
pCsr->nDoclist = apSegment[0]->nDoclist;
if( fts3SegReaderIsPending(apSegment[0]) ){
- rc = fts3MsrBufferData(pCsr, apSegment[0]->aDoclist, pCsr->nDoclist);
+ rc = fts3MsrBufferData(pCsr, apSegment[0]->aDoclist,
+ (i64)pCsr->nDoclist);
pCsr->aDoclist = pCsr->aBuffer;
}else{
pCsr->aDoclist = apSegment[0]->aDoclist;
@@ -187904,7 +191936,8 @@ SQLITE_PRIVATE int sqlite3Fts3SegReaderStep(
nByte = sqlite3Fts3VarintLen(iDelta) + (isRequirePos?nList+1:0);
- rc = fts3GrowSegReaderBuffer(pCsr, nByte+nDoclist+FTS3_NODE_PADDING);
+ rc = fts3GrowSegReaderBuffer(pCsr,
+ (i64)nByte+nDoclist+FTS3_NODE_PADDING);
if( rc ) return rc;
if( isFirst ){
@@ -187930,7 +191963,7 @@ SQLITE_PRIVATE int sqlite3Fts3SegReaderStep(
fts3SegReaderSort(apSegment, nMerge, j, xCmp);
}
if( nDoclist>0 ){
- rc = fts3GrowSegReaderBuffer(pCsr, nDoclist+FTS3_NODE_PADDING);
+ rc = fts3GrowSegReaderBuffer(pCsr, (i64)nDoclist+FTS3_NODE_PADDING);
if( rc ) return rc;
memset(&pCsr->aBuffer[nDoclist], 0, FTS3_NODE_PADDING);
pCsr->aDoclist = pCsr->aBuffer;
@@ -188643,7 +192676,7 @@ struct NodeReader {
static void blobGrowBuffer(Blob *pBlob, int nMin, int *pRc){
if( *pRc==SQLITE_OK && nMin>pBlob->nAlloc ){
int nAlloc = nMin;
- char *a = (char *)sqlite3_realloc(pBlob->a, nAlloc);
+ char *a = (char *)sqlite3_realloc64(pBlob->a, nAlloc);
if( a ){
pBlob->nAlloc = nAlloc;
pBlob->a = a;
@@ -188792,6 +192825,8 @@ static int fts3IncrmergePush(
pBlk->n += sqlite3Fts3PutVarint(&pBlk->a[pBlk->n], nPrefix);
}
pBlk->n += sqlite3Fts3PutVarint(&pBlk->a[pBlk->n], nSuffix);
+ assert( nPrefix+nSuffix<=nTerm );
+ assert( nPrefix>=0 );
memcpy(&pBlk->a[pBlk->n], &zTerm[nPrefix], nSuffix);
pBlk->n += nSuffix;
@@ -188914,6 +192949,7 @@ static int fts3IncrmergeAppend(
pLeaf = &pWriter->aNodeWriter[0];
nPrefix = fts3PrefixCompress(pLeaf->key.a, pLeaf->key.n, zTerm, nTerm);
nSuffix = nTerm - nPrefix;
+ if(nSuffix<=0 ) return FTS_CORRUPT_VTAB;
nSpace = sqlite3Fts3VarintLen(nPrefix);
nSpace += sqlite3Fts3VarintLen(nSuffix) + nSuffix;
@@ -189437,7 +193473,7 @@ static int fts3RepackSegdirLevel(
if( nIdx>=nAlloc ){
int *aNew;
nAlloc += 16;
- aNew = sqlite3_realloc(aIdx, nAlloc*sizeof(int));
+ aNew = sqlite3_realloc64(aIdx, nAlloc*sizeof(int));
if( !aNew ){
rc = SQLITE_NOMEM;
break;
@@ -189811,7 +193847,7 @@ SQLITE_PRIVATE int sqlite3Fts3Incrmerge(Fts3Table *p, int nMerge, int nMin){
/* Allocate space for the cursor, filter and writer objects */
const int nAlloc = sizeof(*pCsr) + sizeof(*pFilter) + sizeof(*pWriter);
- pWriter = (IncrmergeWriter *)sqlite3_malloc(nAlloc);
+ pWriter = (IncrmergeWriter *)sqlite3_malloc64(nAlloc);
if( !pWriter ) return SQLITE_NOMEM;
pFilter = (Fts3SegFilter *)&pWriter[1];
pCsr = (Fts3MultiSegReader *)&pFilter[1];
@@ -190447,7 +194483,7 @@ SQLITE_PRIVATE int sqlite3Fts3DeferredTokenList(
return SQLITE_OK;
}
- pRet = (char *)sqlite3_malloc(p->pList->nData);
+ pRet = (char *)sqlite3_malloc64(p->pList->nData);
if( !pRet ) return SQLITE_NOMEM;
nSkip = sqlite3Fts3GetVarint(p->pList->aData, &dummy);
@@ -190467,7 +194503,7 @@ SQLITE_PRIVATE int sqlite3Fts3DeferToken(
int iCol /* Column that token must appear in (or -1) */
){
Fts3DeferredToken *pDeferred;
- pDeferred = sqlite3_malloc(sizeof(*pDeferred));
+ pDeferred = sqlite3_malloc64(sizeof(*pDeferred));
if( !pDeferred ){
return SQLITE_NOMEM;
}
@@ -194871,7 +198907,7 @@ static JsonNode *jsonMergePatch(
if( pPatch->eType!=JSON_OBJECT ){
return pPatch;
}
- assert( iTarget>=0 && iTarget<pParse->nNode );
+ assert( iTarget<pParse->nNode );
pTarget = &pParse->aNode[iTarget];
assert( (pPatch->jnFlags & JNODE_APPEND)==0 );
if( pTarget->eType!=JSON_OBJECT ){
@@ -199182,7 +203218,7 @@ static int rtreeUpdate(
rtreeReference(pRtree);
assert(nData>=1);
- cell.iRowid = 0; /* Used only to suppress a compiler warning */
+ memset(&cell, 0, sizeof(cell));
/* Constraint handling. A write operation on an r-tree table may return
** SQLITE_CONSTRAINT for two reasons:
@@ -202046,7 +206082,7 @@ static int geopolyUpdate(
sqlite3_free(p);
nChange = 1;
}
- for(jj=1; jj<pRtree->nAux; jj++){
+ for(jj=1; jj<nData-2; jj++){
nChange++;
sqlite3_bind_value(pUp, jj+2, aData[jj+2]);
}
@@ -202649,8 +206685,9 @@ static void icuRegexpFunc(sqlite3_context *p, int nArg, sqlite3_value **apArg){
if( U_SUCCESS(status) ){
sqlite3_set_auxdata(p, 0, pExpr, icuRegexpDelete);
- }else{
- assert(!pExpr);
+ pExpr = sqlite3_get_auxdata(p, 0);
+ }
+ if( !pExpr ){
icuFunctionError(p, "uregex_open", status);
return;
}
@@ -203808,6 +207845,34 @@ SQLITE_API void sqlite3rbu_bp_progress(sqlite3rbu *pRbu, int *pnOne, int*pnTwo);
SQLITE_API int sqlite3rbu_state(sqlite3rbu *pRbu);
/*
+** As part of applying an RBU update or performing an RBU vacuum operation,
+** the system must at one point move the *-oal file to the equivalent *-wal
+** path. Normally, it does this by invoking POSIX function rename(2) directly.
+** Except on WINCE platforms, where it uses win32 API MoveFileW(). This
+** function may be used to register a callback that the RBU module will invoke
+** instead of one of these APIs.
+**
+** If a callback is registered with an RBU handle, it invokes it instead
+** of rename(2) when it needs to move a file within the file-system. The
+** first argument passed to the xRename() callback is a copy of the second
+** argument (pArg) passed to this function. The second is the full path
+** to the file to move and the third the full path to which it should be
+** moved. The callback function should return SQLITE_OK to indicate
+** success. If an error occurs, it should return an SQLite error code.
+** In this case the RBU operation will be abandoned and the error returned
+** to the RBU user.
+**
+** Passing a NULL pointer in place of the xRename argument to this function
+** restores the default behaviour.
+*/
+SQLITE_API void sqlite3rbu_rename_handler(
+ sqlite3rbu *pRbu,
+ void *pArg,
+ int (*xRename)(void *pArg, const char *zOld, const char *zNew)
+);
+
+
+/*
** Create an RBU VFS named zName that accesses the underlying file-system
** via existing VFS zParent. Or, if the zParent parameter is passed NULL,
** then the new RBU VFS uses the default system VFS to access the file-system.
@@ -204174,6 +208239,8 @@ struct sqlite3rbu {
int nPagePerSector; /* Pages per sector for pTargetFd */
i64 iOalSz;
i64 nPhaseOneStep;
+ void *pRenameArg;
+ int (*xRename)(void*, const char*, const char*);
/* The following state variables are used as part of the incremental
** checkpoint stage (eStage==RBU_STAGE_CKPT). See comments surrounding
@@ -206562,7 +210629,7 @@ static void rbuOpenDatabase(sqlite3rbu *p, sqlite3 *dbMain, int *pbRetry){
sqlite3_file_control(p->dbRbu, "main", SQLITE_FCNTL_RBUCNT, (void*)p);
if( p->zState==0 ){
const char *zFile = sqlite3_db_filename(p->dbRbu, "main");
- p->zState = rbuMPrintf(p, "file://%s-vacuum?modeof=%s", zFile, zFile);
+ p->zState = rbuMPrintf(p, "file:///%s-vacuum?modeof=%s", zFile, zFile);
}
}
@@ -207022,32 +211089,7 @@ static void rbuMoveOalFile(sqlite3rbu *p){
}
if( p->rc==SQLITE_OK ){
-#if defined(_WIN32_WCE)
- {
- LPWSTR zWideOal;
- LPWSTR zWideWal;
-
- zWideOal = rbuWinUtf8ToUnicode(zOal);
- if( zWideOal ){
- zWideWal = rbuWinUtf8ToUnicode(zWal);
- if( zWideWal ){
- if( MoveFileW(zWideOal, zWideWal) ){
- p->rc = SQLITE_OK;
- }else{
- p->rc = SQLITE_IOERR;
- }
- sqlite3_free(zWideWal);
- }else{
- p->rc = SQLITE_IOERR_NOMEM;
- }
- sqlite3_free(zWideOal);
- }else{
- p->rc = SQLITE_IOERR_NOMEM;
- }
- }
-#else
- p->rc = rename(zOal, zWal) ? SQLITE_IOERR : SQLITE_OK;
-#endif
+ p->rc = p->xRename(p->pRenameArg, zOal, zWal);
}
if( p->rc!=SQLITE_OK
@@ -207786,6 +211828,7 @@ static sqlite3rbu *openRbuHandle(
/* Create the custom VFS. */
memset(p, 0, sizeof(sqlite3rbu));
+ sqlite3rbu_rename_handler(p, 0, 0);
rbuCreateVfs(p);
/* Open the target, RBU and state databases */
@@ -208177,6 +212220,54 @@ SQLITE_API int sqlite3rbu_savestate(sqlite3rbu *p){
return rc;
}
+/*
+** Default xRename callback for RBU.
+*/
+static int xDefaultRename(void *pArg, const char *zOld, const char *zNew){
+ int rc = SQLITE_OK;
+#if defined(_WIN32_WCE)
+ {
+ LPWSTR zWideOld;
+ LPWSTR zWideNew;
+
+ zWideOld = rbuWinUtf8ToUnicode(zOld);
+ if( zWideOld ){
+ zWideNew = rbuWinUtf8ToUnicode(zNew);
+ if( zWideNew ){
+ if( MoveFileW(zWideOld, zWideNew) ){
+ rc = SQLITE_OK;
+ }else{
+ rc = SQLITE_IOERR;
+ }
+ sqlite3_free(zWideNew);
+ }else{
+ rc = SQLITE_IOERR_NOMEM;
+ }
+ sqlite3_free(zWideOld);
+ }else{
+ rc = SQLITE_IOERR_NOMEM;
+ }
+ }
+#else
+ rc = rename(zOld, zNew) ? SQLITE_IOERR : SQLITE_OK;
+#endif
+ return rc;
+}
+
+SQLITE_API void sqlite3rbu_rename_handler(
+ sqlite3rbu *pRbu,
+ void *pArg,
+ int (*xRename)(void *pArg, const char *zOld, const char *zNew)
+){
+ if( xRename ){
+ pRbu->xRename = xRename;
+ pRbu->pRenameArg = pArg;
+ }else{
+ pRbu->xRename = xDefaultRename;
+ pRbu->pRenameArg = 0;
+ }
+}
+
/**************************************************************************
** Beginning of RBU VFS shim methods. The VFS shim modifies the behaviour
** of a standard VFS in the following ways:
@@ -209285,7 +213376,7 @@ struct StatTable {
*/
static int statConnect(
sqlite3 *db,
- void *pAux __maybe_unused,
+ void *pAux __maybe_unused,
int argc, const char *const*argv,
sqlite3_vtab **ppVtab,
char **pzErr
@@ -210189,7 +214280,7 @@ static int dbpageBestIndex(sqlite3_vtab *tab, sqlite3_index_info *pIdxInfo){
){
pIdxInfo->orderByConsumed = 1;
}
- sqlite3VtabWriteAll(pIdxInfo);
+ sqlite3VtabUsesAllSchemas(pIdxInfo);
return SQLITE_OK;
}
@@ -210307,12 +214398,18 @@ static int dbpageColumn(
}
case 1: { /* data */
DbPage *pDbPage = 0;
- rc = sqlite3PagerGet(pCsr->pPager, pCsr->pgno, (DbPage**)&pDbPage, 0);
- if( rc==SQLITE_OK ){
- sqlite3_result_blob(ctx, sqlite3PagerGetData(pDbPage), pCsr->szPage,
- SQLITE_TRANSIENT);
+ if( pCsr->pgno==((PENDING_BYTE/pCsr->szPage)+1) ){
+ /* The pending byte page. Assume it is zeroed out. Attempting to
+ ** request this page from the page is an SQLITE_CORRUPT error. */
+ sqlite3_result_zeroblob(ctx, pCsr->szPage);
+ }else{
+ rc = sqlite3PagerGet(pCsr->pPager, pCsr->pgno, (DbPage**)&pDbPage, 0);
+ if( rc==SQLITE_OK ){
+ sqlite3_result_blob(ctx, sqlite3PagerGetData(pDbPage), pCsr->szPage,
+ SQLITE_TRANSIENT);
+ }
+ sqlite3PagerUnref(pDbPage);
}
- sqlite3PagerUnref(pDbPage);
break;
}
default: { /* schema */
@@ -210321,7 +214418,7 @@ static int dbpageColumn(
break;
}
}
- return SQLITE_OK;
+ return rc;
}
static int dbpageRowid(sqlite3_vtab_cursor *pCursor, sqlite_int64 *pRowid){
@@ -210367,7 +214464,7 @@ static int dbpageUpdate(
goto update_fail;
}
pBt = pTab->db->aDb[iDb].pBt;
- if( pgno<1 || pBt==0 || pgno>(int)sqlite3BtreeLastPage(pBt) ){
+ if( pgno<1 || pBt==0 || pgno>sqlite3BtreeLastPage(pBt) ){
zErr = "bad page number";
goto update_fail;
}
@@ -210381,11 +214478,12 @@ static int dbpageUpdate(
pPager = sqlite3BtreePager(pBt);
rc = sqlite3PagerGet(pPager, pgno, (DbPage**)&pDbPage, 0);
if( rc==SQLITE_OK ){
- rc = sqlite3PagerWrite(pDbPage);
- if( rc==SQLITE_OK ){
- memcpy(sqlite3PagerGetData(pDbPage),
- sqlite3_value_blob(argv[3]),
- szPage);
+ const void *pData = sqlite3_value_blob(argv[3]);
+ assert( pData!=0 || pTab->db->mallocFailed );
+ if( pData
+ && (rc = sqlite3PagerWrite(pDbPage))==SQLITE_OK
+ ){
+ memcpy(sqlite3PagerGetData(pDbPage), pData, szPage);
}
}
sqlite3PagerUnref(pDbPage);
@@ -210405,11 +214503,12 @@ static int dbpageBegin(sqlite3_vtab *pVtab){
DbpageTable *pTab = (DbpageTable *)pVtab;
sqlite3 *db = pTab->db;
int i;
- for(i=0; i<db->nDb; i++){
+ int rc = SQLITE_OK;
+ for(i=0; rc==SQLITE_OK && i<db->nDb; i++){
Btree *pBt = db->aDb[i].pBt;
- if( pBt ) sqlite3BtreeBeginTrans(pBt, 1, 0);
+ if( pBt ) rc = sqlite3BtreeBeginTrans(pBt, 1, 0);
}
- return SQLITE_OK;
+ return rc;
}
@@ -213780,6 +217879,22 @@ static int sessionChangesetNextOne(
if( p->op==SQLITE_INSERT ) p->op = SQLITE_DELETE;
else if( p->op==SQLITE_DELETE ) p->op = SQLITE_INSERT;
}
+
+ /* If this is an UPDATE that is part of a changeset, then check that
+ ** there are no fields in the old.* record that are not (a) PK fields,
+ ** or (b) also present in the new.* record.
+ **
+ ** Such records are technically corrupt, but the rebaser was at one
+ ** point generating them. Under most circumstances this is benign, but
+ ** can cause spurious SQLITE_RANGE errors when applying the changeset. */
+ if( p->bPatchset==0 && p->op==SQLITE_UPDATE){
+ for(i=0; i<p->nCol; i++){
+ if( p->abPK[i]==0 && p->apValue[i+p->nCol]==0 ){
+ sqlite3ValueFree(p->apValue[i]);
+ p->apValue[i] = 0;
+ }
+ }
+ }
}
return SQLITE_ROW;
@@ -215976,7 +220091,7 @@ static void sessionAppendPartialUpdate(
if( !pIter->abPK[i] && a1[0] ) bData = 1;
memcpy(pOut, a1, n1);
pOut += n1;
- }else if( a2[0]!=0xFF ){
+ }else if( a2[0]!=0xFF && a1[0] ){
bData = 1;
memcpy(pOut, a2, n2);
pOut += n2;
@@ -217133,7 +221248,7 @@ static void sqlite3Fts5BufferAppendPrintf(int *, Fts5Buffer*, char *zFmt, ...);
static char *sqlite3Fts5Mprintf(int *pRc, const char *zFmt, ...);
#define fts5BufferZero(x) sqlite3Fts5BufferZero(x)
-#define fts5BufferAppendVarint(a,b,c) sqlite3Fts5BufferAppendVarint(a,b,c)
+#define fts5BufferAppendVarint(a,b,c) sqlite3Fts5BufferAppendVarint(a,b,(i64)c)
#define fts5BufferFree(a) sqlite3Fts5BufferFree(a)
#define fts5BufferAppendBlob(a,b,c,d) sqlite3Fts5BufferAppendBlob(a,b,c,d)
#define fts5BufferSet(a,b,c,d) sqlite3Fts5BufferSet(a,b,c,d)
@@ -222917,6 +227032,9 @@ static Fts5ExprNearset *sqlite3Fts5ParseNearset(
}else{
if( pRet->nPhrase>0 ){
Fts5ExprPhrase *pLast = pRet->apPhrase[pRet->nPhrase-1];
+ assert( pParse!=0 );
+ assert( pParse->apPhrase!=0 );
+ assert( pParse->nPhrase>=2 );
assert( pLast==pParse->apPhrase[pParse->nPhrase-2] );
if( pPhrase->nTerm==0 ){
fts5ExprPhraseFree(pPhrase);
@@ -225216,7 +229334,7 @@ struct Fts5Index {
sqlite3_stmt *pWriter; /* "INSERT ... %_data VALUES(?,?)" */
sqlite3_stmt *pDeleter; /* "DELETE FROM %_data ... id>=? AND id<=?" */
sqlite3_stmt *pIdxWriter; /* "INSERT ... %_idx VALUES(?,?,?,?)" */
- sqlite3_stmt *pIdxDeleter; /* "DELETE FROM %_idx WHERE segid=? */
+ sqlite3_stmt *pIdxDeleter; /* "DELETE FROM %_idx WHERE segid=?" */
sqlite3_stmt *pIdxSelect;
int nRead; /* Total number of blocks read */
@@ -229007,7 +233125,9 @@ static void fts5WriteAppendRowid(
fts5BufferAppendVarint(&p->rc, &pPage->buf, iRowid);
}else{
assert_nc( p->rc || iRowid>pWriter->iPrevRowid );
- fts5BufferAppendVarint(&p->rc, &pPage->buf, iRowid - pWriter->iPrevRowid);
+ fts5BufferAppendVarint(&p->rc, &pPage->buf,
+ (u64)iRowid - (u64)pWriter->iPrevRowid
+ );
}
pWriter->iPrevRowid = iRowid;
pWriter->bFirstRowidInDoclist = 0;
@@ -229771,7 +233891,7 @@ static int sqlite3Fts5IndexMerge(Fts5Index *p, int nMerge){
static void fts5AppendRowid(
Fts5Index *p,
- i64 iDelta,
+ u64 iDelta,
Fts5Iter *pUnused,
Fts5Buffer *pBuf
){
@@ -229781,7 +233901,7 @@ static void fts5AppendRowid(
static void fts5AppendPoslist(
Fts5Index *p,
- i64 iDelta,
+ u64 iDelta,
Fts5Iter *pMulti,
Fts5Buffer *pBuf
){
@@ -229856,10 +233976,10 @@ static void fts5MergeAppendDocid(
}
#endif
-#define fts5MergeAppendDocid(pBuf, iLastRowid, iRowid) { \
- assert( (pBuf)->n!=0 || (iLastRowid)==0 ); \
- fts5BufferSafeAppendVarint((pBuf), (iRowid) - (iLastRowid)); \
- (iLastRowid) = (iRowid); \
+#define fts5MergeAppendDocid(pBuf, iLastRowid, iRowid) { \
+ assert( (pBuf)->n!=0 || (iLastRowid)==0 ); \
+ fts5BufferSafeAppendVarint((pBuf), (u64)(iRowid) - (u64)(iLastRowid)); \
+ (iLastRowid) = (iRowid); \
}
/*
@@ -230130,7 +234250,7 @@ static void fts5SetupPrefixIter(
int nMerge = 1;
void (*xMerge)(Fts5Index*, Fts5Buffer*, int, Fts5Buffer*);
- void (*xAppend)(Fts5Index*, i64, Fts5Iter*, Fts5Buffer*);
+ void (*xAppend)(Fts5Index*, u64, Fts5Iter*, Fts5Buffer*);
if( p->pConfig->eDetail==FTS5_DETAIL_NONE ){
xMerge = fts5MergeRowidLists;
xAppend = fts5AppendRowid;
@@ -230169,7 +234289,7 @@ static void fts5SetupPrefixIter(
Fts5SegIter *pSeg = &p1->aSeg[ p1->aFirst[1].iFirst ];
p1->xSetOutputs(p1, pSeg);
if( p1->base.nData ){
- xAppend(p, p1->base.iRowid-iLastRowid, p1, &doclist);
+ xAppend(p, (u64)p1->base.iRowid-(u64)iLastRowid, p1, &doclist);
iLastRowid = p1->base.iRowid;
}
}
@@ -230217,7 +234337,7 @@ static void fts5SetupPrefixIter(
iLastRowid = 0;
}
- xAppend(p, p1->base.iRowid-iLastRowid, p1, &doclist);
+ xAppend(p, (u64)p1->base.iRowid-(u64)iLastRowid, p1, &doclist);
iLastRowid = p1->base.iRowid;
}
@@ -231196,6 +235316,7 @@ static int sqlite3Fts5IndexIntegrityCheck(Fts5Index *p, u64 cksum, int bUseCksum
/* If this is a new term, query for it. Update cksum3 with the results. */
fts5TestTerm(p, &term, z, n, cksum2, &cksum3);
+ if( p->rc ) break;
if( eDetail==FTS5_DETAIL_NONE ){
if( 0==fts5MultiIterIsEmpty(p, pIter) ){
@@ -232000,7 +236121,7 @@ static void fts5CheckTransactionState(Fts5FullTable *p, int op, int iSavepoint){
break;
case FTS5_SYNC:
- assert( p->ts.eState==1 );
+ assert( p->ts.eState==1 || p->ts.eState==2 );
p->ts.eState = 2;
break;
@@ -232015,21 +236136,21 @@ static void fts5CheckTransactionState(Fts5FullTable *p, int op, int iSavepoint){
break;
case FTS5_SAVEPOINT:
- assert( p->ts.eState==1 );
+ assert( p->ts.eState>=1 );
assert( iSavepoint>=0 );
assert( iSavepoint>=p->ts.iSavepoint );
p->ts.iSavepoint = iSavepoint;
break;
case FTS5_RELEASE:
- assert( p->ts.eState==1 );
+ assert( p->ts.eState>=1 );
assert( iSavepoint>=0 );
assert( iSavepoint<=p->ts.iSavepoint );
p->ts.iSavepoint = iSavepoint-1;
break;
case FTS5_ROLLBACKTO:
- assert( p->ts.eState==1 );
+ assert( p->ts.eState>=1 );
assert( iSavepoint>=-1 );
/* The following assert() can fail if another vtab strikes an error
** within an xSavepoint() call then SQLite calls xRollbackTo() - without
@@ -233365,7 +237486,7 @@ static int fts5UpdateMethod(
int rc = SQLITE_OK; /* Return code */
/* A transaction must be open when this is called. */
- assert( pTab->ts.eState==1 );
+ assert( pTab->ts.eState==1 || pTab->ts.eState==2 );
assert( pVtab->zErrMsg==0 );
assert( nArg==1 || nArg==(2+pConfig->nCol+2) );
@@ -234533,7 +238654,7 @@ static void fts5SourceIdFunc(
){
assert( nArg==0 );
UNUSED_PARAM2(nArg, apUnused);
- sqlite3_result_text(pCtx, "fts5: 2022-05-06 15:25:27 78d9c993d404cdfaa7fdd2973fa1052e3da9f66215cff9c5540ebe55c407d9fe", -1, SQLITE_TRANSIENT);
+ sqlite3_result_text(pCtx, "fts5: 2022-12-28 14:03:47 df5c253c0b3dd24916e4ec7cf77d3db5294cc9fd45ae7b9c5e82ad8197f38a24", -1, SQLITE_TRANSIENT);
}
/*
@@ -239204,6 +243325,16 @@ SQLITE_EXTENSION_INIT1
#ifndef SQLITE_OMIT_VIRTUALTABLE
+
+#define STMT_NUM_INTEGER_COLUMN 10
+typedef struct StmtRow StmtRow;
+struct StmtRow {
+ sqlite3_int64 iRowid; /* Rowid value */
+ char *zSql; /* column "sql" */
+ int aCol[STMT_NUM_INTEGER_COLUMN+1]; /* all other column values */
+ StmtRow *pNext; /* Next row to return */
+};
+
/* stmt_vtab is a subclass of sqlite3_vtab which will
** serve as the underlying representation of a stmt virtual table
*/
@@ -239221,8 +243352,7 @@ typedef struct stmt_cursor stmt_cursor;
struct stmt_cursor {
sqlite3_vtab_cursor base; /* Base class - must be first */
sqlite3 *db; /* Database connection for this cursor */
- sqlite3_stmt *pStmt; /* Statement cursor is currently pointing at */
- sqlite3_int64 iRowid; /* The rowid */
+ StmtRow *pRow; /* Current row */
};
/*
@@ -239266,7 +243396,7 @@ static int stmtConnect(
"CREATE TABLE x(sql,ncol,ro,busy,nscan,nsort,naidx,nstep,"
"reprep,run,mem)");
if( rc==SQLITE_OK ){
- pNew = sqlite3_malloc( sizeof(*pNew) );
+ pNew = sqlite3_malloc64( sizeof(*pNew) );
*ppVtab = (sqlite3_vtab*)pNew;
if( pNew==0 ) return SQLITE_NOMEM;
memset(pNew, 0, sizeof(*pNew));
@@ -239288,7 +243418,7 @@ static int stmtDisconnect(sqlite3_vtab *pVtab){
*/
static int stmtOpen(sqlite3_vtab *p, sqlite3_vtab_cursor **ppCursor){
stmt_cursor *pCur;
- pCur = sqlite3_malloc( sizeof(*pCur) );
+ pCur = sqlite3_malloc64( sizeof(*pCur) );
if( pCur==0 ) return SQLITE_NOMEM;
memset(pCur, 0, sizeof(*pCur));
pCur->db = ((stmt_vtab*)p)->db;
@@ -239296,10 +243426,21 @@ static int stmtOpen(sqlite3_vtab *p, sqlite3_vtab_cursor **ppCursor){
return SQLITE_OK;
}
+static void stmtCsrReset(stmt_cursor *pCur){
+ StmtRow *pRow = 0;
+ StmtRow *pNext = 0;
+ for(pRow=pCur->pRow; pRow; pRow=pNext){
+ pNext = pRow->pNext;
+ sqlite3_free(pRow);
+ }
+ pCur->pRow = 0;
+}
+
/*
** Destructor for a stmt_cursor.
*/
static int stmtClose(sqlite3_vtab_cursor *cur){
+ stmtCsrReset((stmt_cursor*)cur);
sqlite3_free(cur);
return SQLITE_OK;
}
@@ -239310,8 +243451,9 @@ static int stmtClose(sqlite3_vtab_cursor *cur){
*/
static int stmtNext(sqlite3_vtab_cursor *cur){
stmt_cursor *pCur = (stmt_cursor*)cur;
- pCur->iRowid++;
- pCur->pStmt = sqlite3_next_stmt(pCur->db, pCur->pStmt);
+ StmtRow *pNext = pCur->pRow->pNext;
+ sqlite3_free(pCur->pRow);
+ pCur->pRow = pNext;
return SQLITE_OK;
}
@@ -239325,39 +243467,11 @@ static int stmtColumn(
int i /* Which column to return */
){
stmt_cursor *pCur = (stmt_cursor*)cur;
- switch( i ){
- case STMT_COLUMN_SQL: {
- sqlite3_result_text(ctx, sqlite3_sql(pCur->pStmt), -1, SQLITE_TRANSIENT);
- break;
- }
- case STMT_COLUMN_NCOL: {
- sqlite3_result_int(ctx, sqlite3_column_count(pCur->pStmt));
- break;
- }
- case STMT_COLUMN_RO: {
- sqlite3_result_int(ctx, sqlite3_stmt_readonly(pCur->pStmt));
- break;
- }
- case STMT_COLUMN_BUSY: {
- sqlite3_result_int(ctx, sqlite3_stmt_busy(pCur->pStmt));
- break;
- }
- default: {
- assert( i==STMT_COLUMN_MEM );
- i = SQLITE_STMTSTATUS_MEMUSED +
- STMT_COLUMN_NSCAN - SQLITE_STMTSTATUS_FULLSCAN_STEP;
- /* Fall thru */
- }
- case STMT_COLUMN_NSCAN:
- case STMT_COLUMN_NSORT:
- case STMT_COLUMN_NAIDX:
- case STMT_COLUMN_NSTEP:
- case STMT_COLUMN_REPREP:
- case STMT_COLUMN_RUN: {
- sqlite3_result_int(ctx, sqlite3_stmt_status(pCur->pStmt,
- i-STMT_COLUMN_NSCAN+SQLITE_STMTSTATUS_FULLSCAN_STEP, 0));
- break;
- }
+ StmtRow *pRow = pCur->pRow;
+ if( i==STMT_COLUMN_SQL ){
+ sqlite3_result_text(ctx, pRow->zSql, -1, SQLITE_TRANSIENT);
+ }else{
+ sqlite3_result_int(ctx, pRow->aCol[i]);
}
return SQLITE_OK;
}
@@ -239368,7 +243482,7 @@ static int stmtColumn(
*/
static int stmtRowid(sqlite3_vtab_cursor *cur, sqlite_int64 *pRowid){
stmt_cursor *pCur = (stmt_cursor*)cur;
- *pRowid = pCur->iRowid;
+ *pRowid = pCur->pRow->iRowid;
return SQLITE_OK;
}
@@ -239378,7 +243492,7 @@ static int stmtRowid(sqlite3_vtab_cursor *cur, sqlite_int64 *pRowid){
*/
static int stmtEof(sqlite3_vtab_cursor *cur){
stmt_cursor *pCur = (stmt_cursor*)cur;
- return pCur->pStmt==0;
+ return pCur->pRow==0;
}
/*
@@ -239393,9 +243507,53 @@ static int stmtFilter(
int argc, sqlite3_value **argv
){
stmt_cursor *pCur = (stmt_cursor *)pVtabCursor;
- pCur->pStmt = 0;
- pCur->iRowid = 0;
- return stmtNext(pVtabCursor);
+ sqlite3_stmt *p = 0;
+ sqlite3_int64 iRowid = 1;
+ StmtRow **ppRow = 0;
+
+ stmtCsrReset(pCur);
+ ppRow = &pCur->pRow;
+ for(p=sqlite3_next_stmt(pCur->db, 0); p; p=sqlite3_next_stmt(pCur->db, p)){
+ const char *zSql = sqlite3_sql(p);
+ sqlite3_int64 nSql = zSql ? strlen(zSql)+1 : 0;
+ StmtRow *pNew = (StmtRow*)sqlite3_malloc64(sizeof(StmtRow) + nSql);
+
+ if( pNew==0 ) return SQLITE_NOMEM;
+ memset(pNew, 0, sizeof(StmtRow));
+ if( zSql ){
+ pNew->zSql = (char*)&pNew[1];
+ memcpy(pNew->zSql, zSql, nSql);
+ }
+ pNew->aCol[STMT_COLUMN_NCOL] = sqlite3_column_count(p);
+ pNew->aCol[STMT_COLUMN_RO] = sqlite3_stmt_readonly(p);
+ pNew->aCol[STMT_COLUMN_BUSY] = sqlite3_stmt_busy(p);
+ pNew->aCol[STMT_COLUMN_NSCAN] = sqlite3_stmt_status(
+ p, SQLITE_STMTSTATUS_FULLSCAN_STEP, 0
+ );
+ pNew->aCol[STMT_COLUMN_NSORT] = sqlite3_stmt_status(
+ p, SQLITE_STMTSTATUS_SORT, 0
+ );
+ pNew->aCol[STMT_COLUMN_NAIDX] = sqlite3_stmt_status(
+ p, SQLITE_STMTSTATUS_AUTOINDEX, 0
+ );
+ pNew->aCol[STMT_COLUMN_NSTEP] = sqlite3_stmt_status(
+ p, SQLITE_STMTSTATUS_VM_STEP, 0
+ );
+ pNew->aCol[STMT_COLUMN_REPREP] = sqlite3_stmt_status(
+ p, SQLITE_STMTSTATUS_REPREPARE, 0
+ );
+ pNew->aCol[STMT_COLUMN_RUN] = sqlite3_stmt_status(
+ p, SQLITE_STMTSTATUS_RUN, 0
+ );
+ pNew->aCol[STMT_COLUMN_MEM] = sqlite3_stmt_status(
+ p, SQLITE_STMTSTATUS_MEMUSED, 0
+ );
+ pNew->iRowid = iRowid++;
+ *ppRow = pNew;
+ ppRow = &pNew->pNext;
+ }
+
+ return SQLITE_OK;
}
/*
diff --git a/database/sqlite/sqlite3.h b/database/sqlite/sqlite3.h
index de393da9..24b91675 100644
--- a/database/sqlite/sqlite3.h
+++ b/database/sqlite/sqlite3.h
@@ -146,9 +146,9 @@ extern "C" {
** [sqlite3_libversion_number()], [sqlite3_sourceid()],
** [sqlite_version()] and [sqlite_source_id()].
*/
-#define SQLITE_VERSION "3.38.5"
-#define SQLITE_VERSION_NUMBER 3038005
-#define SQLITE_SOURCE_ID "2022-05-06 15:25:27 78d9c993d404cdfaa7fdd2973fa1052e3da9f66215cff9c5540ebe55c407d9fe"
+#define SQLITE_VERSION "3.40.1"
+#define SQLITE_VERSION_NUMBER 3040001
+#define SQLITE_SOURCE_ID "2022-12-28 14:03:47 df5c253c0b3dd24916e4ec7cf77d3db5294cc9fd45ae7b9c5e82ad8197f38a24"
/*
** CAPI3REF: Run-Time Library Version Numbers
@@ -670,13 +670,17 @@ SQLITE_API int sqlite3_exec(
**
** SQLite uses one of these integer values as the second
** argument to calls it makes to the xLock() and xUnlock() methods
-** of an [sqlite3_io_methods] object.
+** of an [sqlite3_io_methods] object. These values are ordered from
+** lest restrictive to most restrictive.
+**
+** The argument to xLock() is always SHARED or higher. The argument to
+** xUnlock is either SHARED or NONE.
*/
-#define SQLITE_LOCK_NONE 0
-#define SQLITE_LOCK_SHARED 1
-#define SQLITE_LOCK_RESERVED 2
-#define SQLITE_LOCK_PENDING 3
-#define SQLITE_LOCK_EXCLUSIVE 4
+#define SQLITE_LOCK_NONE 0 /* xUnlock() only */
+#define SQLITE_LOCK_SHARED 1 /* xLock() or xUnlock() */
+#define SQLITE_LOCK_RESERVED 2 /* xLock() only */
+#define SQLITE_LOCK_PENDING 3 /* xLock() only */
+#define SQLITE_LOCK_EXCLUSIVE 4 /* xLock() only */
/*
** CAPI3REF: Synchronization Type Flags
@@ -754,7 +758,14 @@ struct sqlite3_file {
** <li> [SQLITE_LOCK_PENDING], or
** <li> [SQLITE_LOCK_EXCLUSIVE].
** </ul>
-** xLock() increases the lock. xUnlock() decreases the lock.
+** xLock() upgrades the database file lock. In other words, xLock() moves the
+** database file lock in the direction NONE toward EXCLUSIVE. The argument to
+** xLock() is always on of SHARED, RESERVED, PENDING, or EXCLUSIVE, never
+** SQLITE_LOCK_NONE. If the database file lock is already at or above the
+** requested lock, then the call to xLock() is a no-op.
+** xUnlock() downgrades the database file lock to either SHARED or NONE.
+* If the lock is already at or below the requested lock state, then the call
+** to xUnlock() is a no-op.
** The xCheckReservedLock() method checks whether any database connection,
** either in this process or in some other process, is holding a RESERVED,
** PENDING, or EXCLUSIVE lock on the file. It returns true
@@ -859,9 +870,8 @@ struct sqlite3_io_methods {
** opcode causes the xFileControl method to write the current state of
** the lock (one of [SQLITE_LOCK_NONE], [SQLITE_LOCK_SHARED],
** [SQLITE_LOCK_RESERVED], [SQLITE_LOCK_PENDING], or [SQLITE_LOCK_EXCLUSIVE])
-** into an integer that the pArg argument points to. This capability
-** is used during testing and is only available when the SQLITE_TEST
-** compile-time option is used.
+** into an integer that the pArg argument points to.
+** This capability is only available if SQLite is compiled with [SQLITE_DEBUG].
**
** <li>[[SQLITE_FCNTL_SIZE_HINT]]
** The [SQLITE_FCNTL_SIZE_HINT] opcode is used by SQLite to give the VFS
@@ -1182,6 +1192,12 @@ struct sqlite3_io_methods {
**
** <li>[[SQLITE_FCNTL_CKSM_FILE]]
** Used by the cksmvfs VFS module only.
+**
+** <li>[[SQLITE_FCNTL_RESET_CACHE]]
+** If there is currently no transaction open on the database, and the
+** database is not a temp db, then this file-control purges the contents
+** of the in-memory page cache. If there is an open transaction, or if
+** the db is a temp-db, it is a no-op, not an error.
** </ul>
*/
#define SQLITE_FCNTL_LOCKSTATE 1
@@ -1224,6 +1240,7 @@ struct sqlite3_io_methods {
#define SQLITE_FCNTL_CKPT_START 39
#define SQLITE_FCNTL_EXTERNAL_READER 40
#define SQLITE_FCNTL_CKSM_FILE 41
+#define SQLITE_FCNTL_RESET_CACHE 42
/* deprecated names */
#define SQLITE_GET_LOCKPROXYFILE SQLITE_FCNTL_GET_LOCKPROXYFILE
@@ -1254,6 +1271,26 @@ typedef struct sqlite3_mutex sqlite3_mutex;
typedef struct sqlite3_api_routines sqlite3_api_routines;
/*
+** CAPI3REF: File Name
+**
+** Type [sqlite3_filename] is used by SQLite to pass filenames to the
+** xOpen method of a [VFS]. It may be cast to (const char*) and treated
+** as a normal, nul-terminated, UTF-8 buffer containing the filename, but
+** may also be passed to special APIs such as:
+**
+** <ul>
+** <li> sqlite3_filename_database()
+** <li> sqlite3_filename_journal()
+** <li> sqlite3_filename_wal()
+** <li> sqlite3_uri_parameter()
+** <li> sqlite3_uri_boolean()
+** <li> sqlite3_uri_int64()
+** <li> sqlite3_uri_key()
+** </ul>
+*/
+typedef const char *sqlite3_filename;
+
+/*
** CAPI3REF: OS Interface Object
**
** An instance of the sqlite3_vfs object defines the interface between
@@ -1431,7 +1468,7 @@ struct sqlite3_vfs {
sqlite3_vfs *pNext; /* Next registered VFS */
const char *zName; /* Name of this virtual file system */
void *pAppData; /* Pointer to application-specific data */
- int (*xOpen)(sqlite3_vfs*, const char *zName, sqlite3_file*,
+ int (*xOpen)(sqlite3_vfs*, sqlite3_filename zName, sqlite3_file*,
int flags, int *pOutFlags);
int (*xDelete)(sqlite3_vfs*, const char *zName, int syncDir);
int (*xAccess)(sqlite3_vfs*, const char *zName, int flags, int *pResOut);
@@ -2309,6 +2346,7 @@ struct sqlite3_mem_methods {
** <ul>
** <li> The [PRAGMA writable_schema=ON] statement.
** <li> The [PRAGMA journal_mode=OFF] statement.
+** <li> The [PRAGMA schema_version=N] statement.
** <li> Writes to the [sqlite_dbpage] virtual table.
** <li> Direct writes to [shadow tables].
** </ul>
@@ -3424,6 +3462,9 @@ SQLITE_API void sqlite3_progress_handler(sqlite3*, int, int(*)(void*), void*);
** <dd>The database is opened [shared cache] enabled, overriding
** the default shared cache setting provided by
** [sqlite3_enable_shared_cache()].)^
+** The [use of shared cache mode is discouraged] and hence shared cache
+** capabilities may be omitted from many builds of SQLite. In such cases,
+** this option is a no-op.
**
** ^(<dt>[SQLITE_OPEN_PRIVATECACHE]</dt>
** <dd>The database is opened [shared cache] disabled, overriding
@@ -3439,7 +3480,7 @@ SQLITE_API void sqlite3_progress_handler(sqlite3*, int, int(*)(void*), void*);
** to return an extended result code.</dd>
**
** [[OPEN_NOFOLLOW]] ^(<dt>[SQLITE_OPEN_NOFOLLOW]</dt>
-** <dd>The database filename is not allowed to be a symbolic link</dd>
+** <dd>The database filename is not allowed to contain a symbolic link</dd>
** </dl>)^
**
** If the 3rd parameter to sqlite3_open_v2() is not one of the
@@ -3698,10 +3739,10 @@ SQLITE_API int sqlite3_open_v2(
**
** See the [URI filename] documentation for additional information.
*/
-SQLITE_API const char *sqlite3_uri_parameter(const char *zFilename, const char *zParam);
-SQLITE_API int sqlite3_uri_boolean(const char *zFile, const char *zParam, int bDefault);
-SQLITE_API sqlite3_int64 sqlite3_uri_int64(const char*, const char*, sqlite3_int64);
-SQLITE_API const char *sqlite3_uri_key(const char *zFilename, int N);
+SQLITE_API const char *sqlite3_uri_parameter(sqlite3_filename z, const char *zParam);
+SQLITE_API int sqlite3_uri_boolean(sqlite3_filename z, const char *zParam, int bDefault);
+SQLITE_API sqlite3_int64 sqlite3_uri_int64(sqlite3_filename, const char*, sqlite3_int64);
+SQLITE_API const char *sqlite3_uri_key(sqlite3_filename z, int N);
/*
** CAPI3REF: Translate filenames
@@ -3730,9 +3771,9 @@ SQLITE_API const char *sqlite3_uri_key(const char *zFilename, int N);
** return value from [sqlite3_db_filename()], then the result is
** undefined and is likely a memory access violation.
*/
-SQLITE_API const char *sqlite3_filename_database(const char*);
-SQLITE_API const char *sqlite3_filename_journal(const char*);
-SQLITE_API const char *sqlite3_filename_wal(const char*);
+SQLITE_API const char *sqlite3_filename_database(sqlite3_filename);
+SQLITE_API const char *sqlite3_filename_journal(sqlite3_filename);
+SQLITE_API const char *sqlite3_filename_wal(sqlite3_filename);
/*
** CAPI3REF: Database File Corresponding To A Journal
@@ -3798,14 +3839,14 @@ SQLITE_API sqlite3_file *sqlite3_database_file_object(const char*);
** then the corresponding [sqlite3_module.xClose() method should also be
** invoked prior to calling sqlite3_free_filename(Y).
*/
-SQLITE_API char *sqlite3_create_filename(
+SQLITE_API sqlite3_filename sqlite3_create_filename(
const char *zDatabase,
const char *zJournal,
const char *zWal,
int nParam,
const char **azParam
);
-SQLITE_API void sqlite3_free_filename(char*);
+SQLITE_API void sqlite3_free_filename(sqlite3_filename);
/*
** CAPI3REF: Error Codes And Messages
@@ -5508,6 +5549,16 @@ SQLITE_API SQLITE_DEPRECATED int sqlite3_memory_alarm(void(*)(void*,sqlite3_int6
** then the conversion is performed. Otherwise no conversion occurs.
** The [SQLITE_INTEGER | datatype] after conversion is returned.)^
**
+** ^(The sqlite3_value_encoding(X) interface returns one of [SQLITE_UTF8],
+** [SQLITE_UTF16BE], or [SQLITE_UTF16LE] according to the current encoding
+** of the value X, assuming that X has type TEXT.)^ If sqlite3_value_type(X)
+** returns something other than SQLITE_TEXT, then the return value from
+** sqlite3_value_encoding(X) is meaningless. ^Calls to
+** sqlite3_value_text(X), sqlite3_value_text16(X), sqlite3_value_text16be(X),
+** sqlite3_value_text16le(X), sqlite3_value_bytes(X), or
+** sqlite3_value_bytes16(X) might change the encoding of the value X and
+** thus change the return from subsequent calls to sqlite3_value_encoding(X).
+**
** ^Within the [xUpdate] method of a [virtual table], the
** sqlite3_value_nochange(X) interface returns true if and only if
** the column corresponding to X is unchanged by the UPDATE operation
@@ -5572,6 +5623,7 @@ SQLITE_API int sqlite3_value_type(sqlite3_value*);
SQLITE_API int sqlite3_value_numeric_type(sqlite3_value*);
SQLITE_API int sqlite3_value_nochange(sqlite3_value*);
SQLITE_API int sqlite3_value_frombind(sqlite3_value*);
+SQLITE_API int sqlite3_value_encoding(sqlite3_value*);
/*
** CAPI3REF: Finding The Subtype Of SQL Values
@@ -5593,7 +5645,8 @@ SQLITE_API unsigned int sqlite3_value_subtype(sqlite3_value*);
** object D and returns a pointer to that copy. ^The [sqlite3_value] returned
** is a [protected sqlite3_value] object even if the input is not.
** ^The sqlite3_value_dup(V) interface returns NULL if V is NULL or if a
-** memory allocation fails.
+** memory allocation fails. ^If V is a [pointer value], then the result
+** of sqlite3_value_dup(V) is a NULL value.
**
** ^The sqlite3_value_free(V) interface frees an [sqlite3_value] object
** previously obtained from [sqlite3_value_dup()]. ^If V is a NULL pointer
@@ -5624,7 +5677,7 @@ SQLITE_API void sqlite3_value_free(sqlite3_value*);
**
** ^The sqlite3_aggregate_context(C,N) routine returns a NULL pointer
** when first called if N is less than or equal to zero or if a memory
-** allocate error occurs.
+** allocation error occurs.
**
** ^(The amount of space allocated by sqlite3_aggregate_context(C,N) is
** determined by the N parameter on first successful call. Changing the
@@ -5829,9 +5882,10 @@ typedef void (*sqlite3_destructor_type)(void*);
** of [SQLITE_UTF8], [SQLITE_UTF16], [SQLITE_UTF16BE], or [SQLITE_UTF16LE].
** ^SQLite takes the text result from the application from
** the 2nd parameter of the sqlite3_result_text* interfaces.
-** ^If the 3rd parameter to the sqlite3_result_text* interfaces
-** is negative, then SQLite takes result text from the 2nd parameter
-** through the first zero character.
+** ^If the 3rd parameter to any of the sqlite3_result_text* interfaces
+** other than sqlite3_result_text64() is negative, then SQLite computes
+** the string length itself by searching the 2nd parameter for the first
+** zero character.
** ^If the 3rd parameter to the sqlite3_result_text* interfaces
** is non-negative, then as many bytes (not characters) of the text
** pointed to by the 2nd parameter are taken as the application-defined
@@ -6276,6 +6330,28 @@ SQLITE_API int sqlite3_get_autocommit(sqlite3*);
SQLITE_API sqlite3 *sqlite3_db_handle(sqlite3_stmt*);
/*
+** CAPI3REF: Return The Schema Name For A Database Connection
+** METHOD: sqlite3
+**
+** ^The sqlite3_db_name(D,N) interface returns a pointer to the schema name
+** for the N-th database on database connection D, or a NULL pointer of N is
+** out of range. An N value of 0 means the main database file. An N of 1 is
+** the "temp" schema. Larger values of N correspond to various ATTACH-ed
+** databases.
+**
+** Space to hold the string that is returned by sqlite3_db_name() is managed
+** by SQLite itself. The string might be deallocated by any operation that
+** changes the schema, including [ATTACH] or [DETACH] or calls to
+** [sqlite3_serialize()] or [sqlite3_deserialize()], even operations that
+** occur on a different thread. Applications that need to
+** remember the string long-term should make their own copy. Applications that
+** are accessing the same database connection simultaneously on multiple
+** threads should mutex-protect calls to this API and should make their own
+** private copy of the result prior to releasing the mutex.
+*/
+SQLITE_API const char *sqlite3_db_name(sqlite3 *db, int N);
+
+/*
** CAPI3REF: Return The Filename For A Database Connection
** METHOD: sqlite3
**
@@ -6305,7 +6381,7 @@ SQLITE_API sqlite3 *sqlite3_db_handle(sqlite3_stmt*);
** <li> [sqlite3_filename_wal()]
** </ul>
*/
-SQLITE_API const char *sqlite3_db_filename(sqlite3 *db, const char *zDbName);
+SQLITE_API sqlite3_filename sqlite3_db_filename(sqlite3 *db, const char *zDbName);
/*
** CAPI3REF: Determine if a database is read-only
@@ -6442,7 +6518,7 @@ SQLITE_API void *sqlite3_rollback_hook(sqlite3*, void(*)(void *), void*);
** function C that is invoked prior to each autovacuum of the database
** file. ^The callback is passed a copy of the generic data pointer (P),
** the schema-name of the attached database that is being autovacuumed,
-** the the size of the database file in pages, the number of free pages,
+** the size of the database file in pages, the number of free pages,
** and the number of bytes per page, respectively. The callback should
** return the number of free pages that should be removed by the
** autovacuum. ^If the callback returns zero, then no autovacuum happens.
@@ -6563,6 +6639,11 @@ SQLITE_API void *sqlite3_update_hook(
** to the same database. Sharing is enabled if the argument is true
** and disabled if the argument is false.)^
**
+** This interface is omitted if SQLite is compiled with
+** [-DSQLITE_OMIT_SHARED_CACHE]. The [-DSQLITE_OMIT_SHARED_CACHE]
+** compile-time option is recommended because the
+** [use of shared cache mode is discouraged].
+**
** ^Cache sharing is enabled and disabled for an entire process.
** This is a change as of SQLite [version 3.5.0] ([dateof:3.5.0]).
** In prior versions of SQLite,
@@ -6661,7 +6742,7 @@ SQLITE_API int sqlite3_db_release_memory(sqlite3*);
** ^The soft heap limit may not be greater than the hard heap limit.
** ^If the hard heap limit is enabled and if sqlite3_soft_heap_limit(N)
** is invoked with a value of N that is greater than the hard heap limit,
-** the the soft heap limit is set to the value of the hard heap limit.
+** the soft heap limit is set to the value of the hard heap limit.
** ^The soft heap limit is automatically enabled whenever the hard heap
** limit is enabled. ^When sqlite3_hard_heap_limit64(N) is invoked and
** the soft heap limit is outside the range of 1..N, then the soft heap
@@ -8956,7 +9037,7 @@ typedef struct sqlite3_backup sqlite3_backup;
** if the application incorrectly accesses the destination [database connection]
** and so no error code is reported, but the operations may malfunction
** nevertheless. Use of the destination database connection while a
-** backup is in progress might also also cause a mutex deadlock.
+** backup is in progress might also cause a mutex deadlock.
**
** If running in [shared cache mode], the application must
** guarantee that the shared cache used by the destination database
@@ -9384,7 +9465,7 @@ SQLITE_API int sqlite3_wal_checkpoint_v2(
*/
#define SQLITE_CHECKPOINT_PASSIVE 0 /* Do as much as possible w/o blocking */
#define SQLITE_CHECKPOINT_FULL 1 /* Wait for writers, then checkpoint */
-#define SQLITE_CHECKPOINT_RESTART 2 /* Like FULL but wait for for readers */
+#define SQLITE_CHECKPOINT_RESTART 2 /* Like FULL but wait for readers */
#define SQLITE_CHECKPOINT_TRUNCATE 3 /* Like RESTART but also truncate WAL */
/*
@@ -9554,8 +9635,8 @@ SQLITE_API SQLITE_EXPERIMENTAL const char *sqlite3_vtab_collation(sqlite3_index_
** of a [virtual table] implementation. The result of calling this
** interface from outside of xBestIndex() is undefined and probably harmful.
**
-** ^The sqlite3_vtab_distinct() interface returns an integer that is
-** either 0, 1, or 2. The integer returned by sqlite3_vtab_distinct()
+** ^The sqlite3_vtab_distinct() interface returns an integer between 0 and
+** 3. The integer returned by sqlite3_vtab_distinct()
** gives the virtual table additional information about how the query
** planner wants the output to be ordered. As long as the virtual table
** can meet the ordering requirements of the query planner, it may set
@@ -9587,6 +9668,13 @@ SQLITE_API SQLITE_EXPERIMENTAL const char *sqlite3_vtab_collation(sqlite3_index_
** that have the same value for all columns identified by "aOrderBy".
** ^However omitting the extra rows is optional.
** This mode is used for a DISTINCT query.
+** <li value="3"><p>
+** ^(If the sqlite3_vtab_distinct() interface returns 3, that means
+** that the query planner needs only distinct rows but it does need the
+** rows to be sorted.)^ ^The virtual table implementation is free to omit
+** rows that are identical in all aOrderBy columns, if it wants to, but
+** it is not required to omit any rows. This mode is used for queries
+** that have both DISTINCT and ORDER BY clauses.
** </ol>
**
** ^For the purposes of comparing virtual table output values to see if the
diff --git a/database/sqlite/sqlite_aclk.c b/database/sqlite/sqlite_aclk.c
index 7e3a9b2e..3b0c4052 100644
--- a/database/sqlite/sqlite_aclk.c
+++ b/database/sqlite/sqlite_aclk.c
@@ -10,10 +10,140 @@ void sanity_check(void) {
BUILD_BUG_ON(WORKER_UTILIZATION_MAX_JOB_TYPES < ACLK_MAX_ENUMERATIONS_DEFINED);
}
-const char *aclk_sync_config[] = {
+static int sql_check_aclk_table(void *data, int argc, char **argv, char **column)
+{
+ struct aclk_database_worker_config *wc = data;
+ UNUSED(argc);
+ UNUSED(column);
- NULL,
-};
+ debug(D_ACLK_SYNC,"Scheduling aclk sync table check for node %s", (char *) argv[0]);
+ struct aclk_database_cmd cmd;
+ memset(&cmd, 0, sizeof(cmd));
+ cmd.opcode = ACLK_DATABASE_DELETE_HOST;
+ cmd.data = strdupz((char *) argv[0]);
+ aclk_database_enq_cmd_noblock(wc, &cmd);
+ return 0;
+}
+
+#define SQL_SELECT_ACLK_ACTIVE_LIST "SELECT REPLACE(SUBSTR(name,19),'_','-') FROM sqlite_schema " \
+ "WHERE name LIKE 'aclk_chart_latest_%' AND type IN ('table');"
+
+static void sql_check_aclk_table_list(struct aclk_database_worker_config *wc)
+{
+ char *err_msg = NULL;
+ debug(D_ACLK_SYNC,"Cleaning tables for nodes that do not exist");
+ int rc = sqlite3_exec_monitored(db_meta, SQL_SELECT_ACLK_ACTIVE_LIST, sql_check_aclk_table, (void *) wc, &err_msg);
+ if (rc != SQLITE_OK) {
+ error_report("Query failed when trying to check for obsolete ACLK sync tables, %s", err_msg);
+ sqlite3_free(err_msg);
+ }
+}
+
+static void sql_maint_aclk_sync_database(struct aclk_database_worker_config *wc, struct aclk_database_cmd cmd)
+{
+ UNUSED(cmd);
+
+ debug(D_ACLK, "Checking database for %s", wc->host_guid);
+
+ BUFFER *sql = buffer_create(ACLK_SYNC_QUERY_SIZE, &netdata_buffers_statistics.buffers_sqlite);
+
+ buffer_sprintf(sql,"DELETE FROM aclk_alert_%s WHERE date_submitted IS NOT NULL AND "
+ "CAST(date_cloud_ack AS INT) < unixepoch()-%d;", wc->uuid_str, ACLK_DELETE_ACK_ALERTS_INTERNAL);
+ db_execute(buffer_tostring(sql));
+
+ buffer_free(sql);
+}
+
+
+#define SQL_SELECT_HOST_BY_UUID "SELECT host_id FROM host WHERE host_id = @host_id;"
+
+static int is_host_available(uuid_t *host_id)
+{
+ sqlite3_stmt *res = NULL;
+ int rc;
+
+ if (unlikely(!db_meta)) {
+ if (default_rrd_memory_mode == RRD_MEMORY_MODE_DBENGINE)
+ error_report("Database has not been initialized");
+ return 1;
+ }
+
+ rc = sqlite3_prepare_v2(db_meta, SQL_SELECT_HOST_BY_UUID, -1, &res, 0);
+ if (unlikely(rc != SQLITE_OK)) {
+ error_report("Failed to prepare statement to select node instance information for a node");
+ return 1;
+ }
+
+ rc = sqlite3_bind_blob(res, 1, host_id, sizeof(*host_id), SQLITE_STATIC);
+ if (unlikely(rc != SQLITE_OK)) {
+ error_report("Failed to bind host_id parameter to select node instance information");
+ goto failed;
+ }
+ rc = sqlite3_step_monitored(res);
+
+failed:
+ if (unlikely(sqlite3_finalize(res) != SQLITE_OK))
+ error_report("Failed to finalize the prepared statement when checking host existence");
+
+ return (rc == SQLITE_ROW);
+}
+
+// OPCODE: ACLK_DATABASE_DELETE_HOST
+void sql_delete_aclk_table_list(struct aclk_database_worker_config *wc, struct aclk_database_cmd cmd)
+{
+ UNUSED(wc);
+ char uuid_str[GUID_LEN + 1];
+ char host_str[GUID_LEN + 1];
+
+ int rc;
+ uuid_t host_uuid;
+ char *host_guid = (char *)cmd.data;
+
+ if (unlikely(!host_guid))
+ return;
+
+ rc = uuid_parse(host_guid, host_uuid);
+ freez(host_guid);
+ if (rc)
+ return;
+
+ uuid_unparse_lower(host_uuid, host_str);
+ uuid_unparse_lower_fix(&host_uuid, uuid_str);
+
+ debug(D_ACLK_SYNC, "Checking if I should delete aclk tables for node %s", host_str);
+
+ if (is_host_available(&host_uuid)) {
+ debug(D_ACLK_SYNC, "Host %s exists, not deleting aclk sync tables", host_str);
+ return;
+ }
+
+ debug(D_ACLK_SYNC, "Host %s does NOT exist, can delete aclk sync tables", host_str);
+
+ sqlite3_stmt *res = NULL;
+ BUFFER *sql = buffer_create(ACLK_SYNC_QUERY_SIZE, &netdata_buffers_statistics.buffers_sqlite);
+
+ buffer_sprintf(sql,"SELECT 'drop '||type||' IF EXISTS '||name||';' FROM sqlite_schema " \
+ "WHERE name LIKE 'aclk_%%_%s' AND type IN ('table', 'trigger', 'index');", uuid_str);
+
+ rc = sqlite3_prepare_v2(db_meta, buffer_tostring(sql), -1, &res, 0);
+ if (rc != SQLITE_OK) {
+ error_report("Failed to prepare statement to clean up aclk tables");
+ goto fail;
+ }
+ buffer_flush(sql);
+
+ while (sqlite3_step_monitored(res) == SQLITE_ROW)
+ buffer_strcat(sql, (char *) sqlite3_column_text(res, 0));
+
+ rc = sqlite3_finalize(res);
+ if (unlikely(rc != SQLITE_OK))
+ error_report("Failed to finalize statement to clean up aclk tables, rc = %d", rc);
+
+ db_execute(buffer_tostring(sql));
+
+fail:
+ buffer_free(sql);
+}
uv_mutex_t aclk_async_lock;
struct aclk_database_worker_config *aclk_thread_head = NULL;
@@ -38,7 +168,6 @@ void aclk_add_worker_thread(struct aclk_database_worker_config *wc)
aclk_thread_head = wc;
}
uv_mutex_unlock(&aclk_async_lock);
- return;
}
void aclk_del_worker_thread(struct aclk_database_worker_config *wc)
@@ -53,7 +182,6 @@ void aclk_del_worker_thread(struct aclk_database_worker_config *wc)
if (*tmp)
*tmp = wc->next;
uv_mutex_unlock(&aclk_async_lock);
- return;
}
int aclk_worker_thread_exists(char *guid)
@@ -199,7 +327,6 @@ void aclk_sync_exit_all()
uv_mutex_unlock(&aclk_async_lock);
}
-#ifdef ENABLE_ACLK
enum {
IDX_HOST_ID,
IDX_HOSTNAME,
@@ -228,6 +355,8 @@ static int create_host_callback(void *data, int argc, char **argv, char **column
uuid_unparse_lower(*(uuid_t *)argv[IDX_HOST_ID], guid);
struct rrdhost_system_info *system_info = callocz(1, sizeof(struct rrdhost_system_info));
+ __atomic_sub_fetch(&netdata_buffers_statistics.rrdhost_allocations_size, sizeof(struct rrdhost_system_info), __ATOMIC_RELAXED);
+
system_info->hops = str2i((const char *) argv[IDX_HOPS]);
sql_build_host_system_info((uuid_t *)argv[IDX_HOST_ID], system_info);
@@ -268,9 +397,9 @@ static int create_host_callback(void *data, int argc, char **argv, char **column
#endif
return 0;
}
-#endif
-int aclk_start_sync_thread(void *data, int argc, char **argv, char **column)
+#ifdef ENABLE_ACLK
+static int aclk_start_sync_thread(void *data, int argc, char **argv, char **column)
{
char uuid_str[GUID_LEN + 1];
UNUSED(data);
@@ -286,10 +415,9 @@ int aclk_start_sync_thread(void *data, int argc, char **argv, char **column)
sql_create_aclk_table(host, (uuid_t *) argv[0], (uuid_t *) argv[1]);
return 0;
}
-
+#endif
void sql_aclk_sync_init(void)
{
-#ifdef ENABLE_ACLK
char *err_msg = NULL;
int rc;
@@ -301,21 +429,7 @@ void sql_aclk_sync_init(void)
return;
}
- info("SQLite aclk sync initialization");
-
- for (int i = 0; aclk_sync_config[i]; i++) {
- debug(D_ACLK_SYNC, "Executing %s", aclk_sync_config[i]);
- rc = sqlite3_exec_monitored(db_meta, aclk_sync_config[i], 0, 0, &err_msg);
- if (rc != SQLITE_OK) {
- error_report("SQLite error aclk sync initialization setup, rc = %d (%s)", rc, err_msg);
- error_report("SQLite failed statement %s", aclk_sync_config[i]);
- sqlite3_free(err_msg);
- return;
- }
- }
- info("SQLite aclk sync initialization completed");
- fatal_assert(0 == uv_mutex_init(&aclk_async_lock));
-
+ info("Creating archived hosts");
rc = sqlite3_exec_monitored(db_meta, "SELECT host_id, hostname, registry_hostname, update_every, os, "
"timezone, tags, hops, memory_mode, abbrev_timezone, utc_offset, program_name, "
"program_version, entries, health_enabled FROM host WHERE hops >0;",
@@ -325,14 +439,16 @@ void sql_aclk_sync_init(void)
sqlite3_free(err_msg);
}
+#ifdef ENABLE_ACLK
+ fatal_assert(0 == uv_mutex_init(&aclk_async_lock));
rc = sqlite3_exec_monitored(db_meta, "SELECT ni.host_id, ni.node_id FROM host h, node_instance ni WHERE "
"h.host_id = ni.host_id AND ni.node_id IS NOT NULL;", aclk_start_sync_thread, NULL, &err_msg);
if (rc != SQLITE_OK) {
error_report("SQLite error when starting ACLK sync threads, rc = %d (%s)", rc, err_msg);
sqlite3_free(err_msg);
}
+ info("ACLK sync initialization completed");
#endif
- return;
}
static void async_cb(uv_async_t *handle)
@@ -374,10 +490,9 @@ static void timer_cb(uv_timer_t* handle)
#endif
}
-#define MAX_CMD_BATCH_SIZE (256)
-
-void aclk_database_worker(void *arg)
+static void aclk_database_worker(void *arg)
{
+ service_register(SERVICE_THREAD_TYPE_EVENT_LOOP, NULL, NULL, NULL, true);
worker_register("ACLKSYNC");
worker_register_job_name(ACLK_DATABASE_NOOP, "noop");
worker_register_job_name(ACLK_DATABASE_ORPHAN_HOST, "node orphan");
@@ -398,15 +513,12 @@ void aclk_database_worker(void *arg)
enum aclk_database_opcode opcode;
uv_timer_t timer_req;
struct aclk_database_cmd cmd;
- unsigned cmd_batch_size;
-
- //aclk_database_init_cmd_queue(wc);
char threadname[NETDATA_THREAD_NAME_MAX+1];
if (wc->host)
- snprintfz(threadname, NETDATA_THREAD_NAME_MAX, "AS_%s", rrdhost_hostname(wc->host));
+ snprintfz(threadname, NETDATA_THREAD_NAME_MAX, "ACLK[%s]", rrdhost_hostname(wc->host));
else {
- snprintfz(threadname, NETDATA_THREAD_NAME_MAX, "AS_%s", wc->uuid_str);
+ snprintfz(threadname, NETDATA_THREAD_NAME_MAX, "ACLK[%s]", wc->uuid_str);
threadname[11] = '\0';
}
uv_thread_set_name_np(wc->thread, threadname);
@@ -449,17 +561,13 @@ void aclk_database_worker(void *arg)
uv_run(loop, UV_RUN_DEFAULT);
/* wait for commands */
- cmd_batch_size = 0;
do {
- if (unlikely(cmd_batch_size >= MAX_CMD_BATCH_SIZE))
- break;
cmd = aclk_database_deq_cmd(wc);
if (netdata_exit)
break;
opcode = cmd.opcode;
- ++cmd_batch_size;
if(likely(opcode != ACLK_DATABASE_NOOP))
worker_is_busy(opcode);
@@ -535,7 +643,7 @@ void aclk_database_worker(void *arg)
wc->host = rrdhost_find_by_guid(wc->host_guid);
if (wc->host) {
info("HOST %s (%s) detected as active", rrdhost_hostname(wc->host), wc->host_guid);
- snprintfz(threadname, NETDATA_THREAD_NAME_MAX, "AS_%s", rrdhost_hostname(wc->host));
+ snprintfz(threadname, NETDATA_THREAD_NAME_MAX, "ACLK[%s]", rrdhost_hostname(wc->host));
uv_thread_set_name_np(wc->thread, threadname);
wc->host->dbsync_worker = wc;
if (unlikely(!wc->hostname))
@@ -584,10 +692,8 @@ void aclk_database_worker(void *arg)
info("Shutting down ACLK sync event loop complete for host %s", wc->host_guid);
/* TODO: don't let the API block by waiting to enqueue commands */
uv_cond_destroy(&wc->cmd_cond);
-/* uv_mutex_destroy(&wc->cmd_mutex); */
- //fatal_assert(0 == uv_loop_close(loop));
- int rc;
+ int rc;
do {
rc = uv_loop_close(loop);
} while (rc != UV_EBUSY);
@@ -628,7 +734,7 @@ void sql_create_aclk_table(RRDHOST *host, uuid_t *host_uuid, uuid_t *node_id)
uuid_unparse_lower(*host_uuid, host_guid);
- BUFFER *sql = buffer_create(ACLK_SYNC_QUERY_SIZE);
+ BUFFER *sql = buffer_create(ACLK_SYNC_QUERY_SIZE, &netdata_buffers_statistics.buffers_sqlite);
buffer_sprintf(sql, TABLE_ACLK_ALERT, uuid_str);
db_execute(buffer_tostring(sql));
@@ -648,6 +754,10 @@ void sql_create_aclk_table(RRDHOST *host, uuid_t *host_uuid, uuid_t *node_id)
if (likely(host)) {
host->dbsync_worker = (void *)wc;
wc->hostname = strdupz(rrdhost_hostname(host));
+ if (node_id && !host->node_id) {
+ host->node_id = mallocz(sizeof(*host->node_id));
+ uuid_copy(*host->node_id, *node_id);
+ }
}
else
wc->hostname = get_hostname_by_node_id(wc->node_id);
@@ -663,142 +773,4 @@ void sql_create_aclk_table(RRDHOST *host, uuid_t *host_uuid, uuid_t *node_id)
UNUSED(host_uuid);
UNUSED(node_id);
#endif
- return;
-}
-
-void sql_maint_aclk_sync_database(struct aclk_database_worker_config *wc, struct aclk_database_cmd cmd)
-{
- UNUSED(cmd);
-
- debug(D_ACLK, "Checking database for %s", wc->host_guid);
-
- BUFFER *sql = buffer_create(ACLK_SYNC_QUERY_SIZE);
-
- buffer_sprintf(sql,"DELETE FROM aclk_alert_%s WHERE date_submitted IS NOT NULL AND "
- "CAST(date_cloud_ack AS INT) < unixepoch()-%d;", wc->uuid_str, ACLK_DELETE_ACK_ALERTS_INTERNAL);
- db_execute(buffer_tostring(sql));
-
- buffer_free(sql);
- return;
-}
-
-#define SQL_SELECT_HOST_BY_UUID "SELECT host_id FROM host WHERE host_id = @host_id;"
-
-static int is_host_available(uuid_t *host_id)
-{
- sqlite3_stmt *res = NULL;
- int rc;
-
- if (unlikely(!db_meta)) {
- if (default_rrd_memory_mode == RRD_MEMORY_MODE_DBENGINE)
- error_report("Database has not been initialized");
- return 1;
- }
-
- rc = sqlite3_prepare_v2(db_meta, SQL_SELECT_HOST_BY_UUID, -1, &res, 0);
- if (unlikely(rc != SQLITE_OK)) {
- error_report("Failed to prepare statement to select node instance information for a node");
- return 1;
- }
-
- rc = sqlite3_bind_blob(res, 1, host_id, sizeof(*host_id), SQLITE_STATIC);
- if (unlikely(rc != SQLITE_OK)) {
- error_report("Failed to bind host_id parameter to select node instance information");
- goto failed;
- }
- rc = sqlite3_step_monitored(res);
-
- failed:
- if (unlikely(sqlite3_finalize(res) != SQLITE_OK))
- error_report("Failed to finalize the prepared statement when checking host existence");
-
- return (rc == SQLITE_ROW);
-}
-
-// OPCODE: ACLK_DATABASE_DELETE_HOST
-void sql_delete_aclk_table_list(struct aclk_database_worker_config *wc, struct aclk_database_cmd cmd)
-{
- UNUSED(wc);
- char uuid_str[GUID_LEN + 1];
- char host_str[GUID_LEN + 1];
-
- int rc;
- uuid_t host_uuid;
- char *host_guid = (char *)cmd.data;
-
- if (unlikely(!host_guid))
- return;
-
- rc = uuid_parse(host_guid, host_uuid);
- freez(host_guid);
- if (rc)
- return;
-
- uuid_unparse_lower(host_uuid, host_str);
- uuid_unparse_lower_fix(&host_uuid, uuid_str);
-
- debug(D_ACLK_SYNC, "Checking if I should delete aclk tables for node %s", host_str);
-
- if (is_host_available(&host_uuid)) {
- debug(D_ACLK_SYNC, "Host %s exists, not deleting aclk sync tables", host_str);
- return;
- }
-
- debug(D_ACLK_SYNC, "Host %s does NOT exist, can delete aclk sync tables", host_str);
-
- sqlite3_stmt *res = NULL;
- BUFFER *sql = buffer_create(ACLK_SYNC_QUERY_SIZE);
-
- buffer_sprintf(sql,"SELECT 'drop '||type||' IF EXISTS '||name||';' FROM sqlite_schema " \
- "WHERE name LIKE 'aclk_%%_%s' AND type IN ('table', 'trigger', 'index');", uuid_str);
-
- rc = sqlite3_prepare_v2(db_meta, buffer_tostring(sql), -1, &res, 0);
- if (rc != SQLITE_OK) {
- error_report("Failed to prepare statement to clean up aclk tables");
- goto fail;
- }
- buffer_flush(sql);
-
- while (sqlite3_step_monitored(res) == SQLITE_ROW)
- buffer_strcat(sql, (char *) sqlite3_column_text(res, 0));
-
- rc = sqlite3_finalize(res);
- if (unlikely(rc != SQLITE_OK))
- error_report("Failed to finalize statement to clean up aclk tables, rc = %d", rc);
-
- db_execute(buffer_tostring(sql));
-
-fail:
- buffer_free(sql);
- return;
-}
-
-static int sql_check_aclk_table(void *data, int argc, char **argv, char **column)
-{
- struct aclk_database_worker_config *wc = data;
- UNUSED(argc);
- UNUSED(column);
-
- debug(D_ACLK_SYNC,"Scheduling aclk sync table check for node %s", (char *) argv[0]);
- struct aclk_database_cmd cmd;
- memset(&cmd, 0, sizeof(cmd));
- cmd.opcode = ACLK_DATABASE_DELETE_HOST;
- cmd.data = strdupz((char *) argv[0]);
- aclk_database_enq_cmd_noblock(wc, &cmd);
- return 0;
-}
-
-#define SQL_SELECT_ACLK_ACTIVE_LIST "SELECT REPLACE(SUBSTR(name,19),'_','-') FROM sqlite_schema " \
- "WHERE name LIKE 'aclk_chart_latest_%' AND type IN ('table');"
-
-void sql_check_aclk_table_list(struct aclk_database_worker_config *wc)
-{
- char *err_msg = NULL;
- debug(D_ACLK_SYNC,"Cleaning tables for nodes that do not exist");
- int rc = sqlite3_exec_monitored(db_meta, SQL_SELECT_ACLK_ACTIVE_LIST, sql_check_aclk_table, (void *) wc, &err_msg);
- if (rc != SQLITE_OK) {
- error_report("Query failed when trying to check for obsolete ACLK sync tables, %s", err_msg);
- sqlite3_free(err_msg);
- }
- return;
-}
+} \ No newline at end of file
diff --git a/database/sqlite/sqlite_aclk.h b/database/sqlite/sqlite_aclk.h
index 06d5d027..208177e4 100644
--- a/database/sqlite/sqlite_aclk.h
+++ b/database/sqlite/sqlite_aclk.h
@@ -99,7 +99,7 @@ struct aclk_database_cmd {
struct aclk_completion *completion;
};
-#define ACLK_DATABASE_CMD_Q_MAX_SIZE (16384)
+#define ACLK_DATABASE_CMD_Q_MAX_SIZE (1024)
struct aclk_database_cmdqueue {
unsigned head, tail;
@@ -166,9 +166,6 @@ int aclk_database_enq_cmd_noblock(struct aclk_database_worker_config *wc, struct
void aclk_database_enq_cmd(struct aclk_database_worker_config *wc, struct aclk_database_cmd *cmd);
void sql_create_aclk_table(RRDHOST *host, uuid_t *host_uuid, uuid_t *node_id);
void sql_aclk_sync_init(void);
-void sql_check_aclk_table_list(struct aclk_database_worker_config *wc);
-void sql_delete_aclk_table_list(struct aclk_database_worker_config *wc, struct aclk_database_cmd cmd);
-void sql_maint_aclk_sync_database(struct aclk_database_worker_config *wc, struct aclk_database_cmd cmd);
int claimed();
void aclk_sync_exit_all();
struct aclk_database_worker_config *find_inactive_wc_by_node_id(char *node_id);
diff --git a/database/sqlite/sqlite_aclk_alert.c b/database/sqlite/sqlite_aclk_alert.c
index 47663a8d..ce284ebc 100644
--- a/database/sqlite/sqlite_aclk_alert.c
+++ b/database/sqlite/sqlite_aclk_alert.c
@@ -43,6 +43,34 @@ void update_filtered(ALARM_ENTRY *ae, uint32_t unique_id, char *uuid_str) {
ae->flags |= HEALTH_ENTRY_FLAG_ACLK_QUEUED;
}
+static inline bool is_event_from_alert_variable_config(uint32_t unique_id, char *uuid_str) {
+ sqlite3_stmt *res = NULL;
+ int rc = 0;
+ bool ret = false;
+
+ char sql[ACLK_SYNC_QUERY_SIZE];
+ snprintfz(sql,ACLK_SYNC_QUERY_SIZE-1, "select hl.unique_id from health_log_%s hl, alert_hash ah where hl.unique_id = %u " \
+ "and hl.config_hash_id = ah.hash_id " \
+ "and ah.warn is null and ah.crit is null;", uuid_str, unique_id);
+
+ rc = sqlite3_prepare_v2(db_meta, sql, -1, &res, 0);
+ if (rc != SQLITE_OK) {
+ error_report("Failed to prepare statement when trying to check for alert variables.");
+ return false;
+ }
+
+ rc = sqlite3_step_monitored(res);
+ if (likely(rc == SQLITE_ROW)) {
+ ret = true;
+ }
+
+ rc = sqlite3_finalize(res);
+ if (unlikely(rc != SQLITE_OK))
+ error_report("Failed to finalize statement when trying to check for alert variables, rc = %d", rc);
+
+ return ret;
+}
+
#define MAX_REMOVED_PERIOD 86400
//decide if some events should be sent or not
int should_send_to_cloud(RRDHOST *host, ALARM_ENTRY *ae)
@@ -59,6 +87,9 @@ int should_send_to_cloud(RRDHOST *host, ALARM_ENTRY *ae)
if (unlikely(uuid_is_null(ae->config_hash_id)))
return 0;
+ if (is_event_from_alert_variable_config(ae->unique_id, uuid_str))
+ return 0;
+
char sql[ACLK_SYNC_QUERY_SIZE];
uuid_t config_hash_id;
RRDCALC_STATUS status;
@@ -133,6 +164,9 @@ done:
// and handle both cases
int sql_queue_alarm_to_aclk(RRDHOST *host, ALARM_ENTRY *ae, int skip_filter)
{
+ if(!service_running(SERVICE_ACLK))
+ return 0;
+
if (!claimed())
return 0;
@@ -153,7 +187,7 @@ int sql_queue_alarm_to_aclk(RRDHOST *host, ALARM_ENTRY *ae, int skip_filter)
char uuid_str[GUID_LEN + 1];
uuid_unparse_lower_fix(&host->host_uuid, uuid_str);
- BUFFER *sql = buffer_create(1024);
+ BUFFER *sql = buffer_create(1024, &netdata_buffers_statistics.buffers_sqlite);
buffer_sprintf(
sql,
@@ -242,7 +276,7 @@ void aclk_push_alert_event(struct aclk_database_worker_config *wc, struct aclk_d
return;
}
- BUFFER *sql = buffer_create(1024);
+ BUFFER *sql = buffer_create(1024, &netdata_buffers_statistics.buffers_sqlite);
if (wc->alerts_start_seq_id != 0) {
buffer_sprintf(
@@ -267,20 +301,38 @@ void aclk_push_alert_event(struct aclk_database_worker_config *wc, struct aclk_d
sqlite3_stmt *res = NULL;
- buffer_sprintf(sql, "select aa.sequence_id, hl.unique_id, hl.alarm_id, hl.config_hash_id, hl.updated_by_id, hl.when_key, \
- hl.duration, hl.non_clear_duration, hl.flags, hl.exec_run_timestamp, hl.delay_up_to_timestamp, hl.name, \
- hl.chart, hl.family, hl.exec, hl.recipient, hl.source, hl.units, hl.info, hl.exec_code, hl.new_status, \
- hl.old_status, hl.delay, hl.new_value, hl.old_value, hl.last_repeat, hl.chart_context \
- from health_log_%s hl, aclk_alert_%s aa \
- where hl.unique_id = aa.alert_unique_id and aa.date_submitted is null \
- order by aa.sequence_id asc limit %d;", wc->uuid_str, wc->uuid_str, limit);
+ buffer_sprintf(sql, "select aa.sequence_id, hl.unique_id, hl.alarm_id, hl.config_hash_id, hl.updated_by_id, hl.when_key, " \
+ " hl.duration, hl.non_clear_duration, hl.flags, hl.exec_run_timestamp, hl.delay_up_to_timestamp, hl.name, " \
+ " hl.chart, hl.family, hl.exec, hl.recipient, hl.source, hl.units, hl.info, hl.exec_code, hl.new_status, " \
+ " hl.old_status, hl.delay, hl.new_value, hl.old_value, hl.last_repeat, hl.chart_context " \
+ " from health_log_%s hl, aclk_alert_%s aa " \
+ " where hl.unique_id = aa.alert_unique_id and aa.date_submitted is null " \
+ " order by aa.sequence_id asc limit %d;", wc->uuid_str, wc->uuid_str, limit);
rc = sqlite3_prepare_v2(db_meta, buffer_tostring(sql), -1, &res, 0);
if (rc != SQLITE_OK) {
- error_report("Failed to prepare statement when trying to send an alert update via ACLK");
- buffer_free(sql);
- freez(claim_id);
- return;
+
+ // Try to create tables
+ if (wc->host)
+ sql_create_health_log_table(wc->host);
+
+ BUFFER *sql_fix = buffer_create(1024, &netdata_buffers_statistics.buffers_sqlite);
+ buffer_sprintf(sql_fix, TABLE_ACLK_ALERT, wc->uuid_str);
+ db_execute(buffer_tostring(sql_fix));
+ buffer_flush(sql_fix);
+ buffer_sprintf(sql_fix, INDEX_ACLK_ALERT, wc->uuid_str, wc->uuid_str);
+ db_execute(buffer_tostring(sql_fix));
+ buffer_free(sql_fix);
+
+ // Try again
+ rc = sqlite3_prepare_v2(db_meta, buffer_tostring(sql), -1, &res, 0);
+ if (rc != SQLITE_OK) {
+ error_report("Failed to prepare statement when trying to send an alert update via ACLK");
+
+ buffer_free(sql);
+ freez(claim_id);
+ return;
+ }
}
char uuid_str[GUID_LEN + 1];
@@ -311,7 +363,7 @@ void aclk_push_alert_event(struct aclk_database_worker_config *wc, struct aclk_d
alarm_log.utc_offset = wc->host->utc_offset;
alarm_log.timezone = strdupz(rrdhost_abbrev_timezone(wc->host));
alarm_log.exec_path = sqlite3_column_bytes(res, 14) > 0 ? strdupz((char *)sqlite3_column_text(res, 14)) :
- strdupz((char *)string2str(wc->host->health_default_exec));
+ strdupz((char *)string2str(wc->host->health.health_default_exec));
alarm_log.conf_source = strdupz((char *)sqlite3_column_text(res, 16));
char *edit_command = sqlite3_column_bytes(res, 16) > 0 ?
@@ -407,7 +459,7 @@ void sql_queue_existing_alerts_to_aclk(RRDHOST *host)
{
char uuid_str[GUID_LEN + 1];
uuid_unparse_lower_fix(&host->host_uuid, uuid_str);
- BUFFER *sql = buffer_create(1024);
+ BUFFER *sql = buffer_create(1024, &netdata_buffers_statistics.buffers_sqlite);
buffer_sprintf(sql,"delete from aclk_alert_%s; " \
"insert into aclk_alert_%s (alert_unique_id, date_created, filtered_alert_unique_id) " \
@@ -484,7 +536,7 @@ void aclk_push_alarm_health_log(struct aclk_database_worker_config *wc, struct a
struct timeval first_timestamp;
struct timeval last_timestamp;
- BUFFER *sql = buffer_create(1024);
+ BUFFER *sql = buffer_create(1024, &netdata_buffers_statistics.buffers_sqlite);
sqlite3_stmt *res = NULL;
@@ -528,7 +580,7 @@ void aclk_push_alarm_health_log(struct aclk_database_worker_config *wc, struct a
alarm_log.node_id = wc->node_id;
alarm_log.log_entries = log_entries;
alarm_log.status = wc->alert_updates == 0 ? 2 : 1;
- alarm_log.enabled = (int)host->health_enabled;
+ alarm_log.enabled = (int)host->health.health_enabled;
wc->alert_sequence_id = last_sequence;
@@ -541,6 +593,8 @@ void aclk_push_alarm_health_log(struct aclk_database_worker_config *wc, struct a
freez(claim_id);
buffer_free(sql);
+
+ aclk_alert_reloaded = 1;
#endif
return;
@@ -651,7 +705,7 @@ int aclk_push_alert_config_event(struct aclk_database_worker_config *wc, struct
alarm_config.p_db_lookup_dimensions = sqlite3_column_bytes(res, 27) > 0 ? strdupz((char *)sqlite3_column_text(res, 27)) : NULL;
alarm_config.p_db_lookup_method = sqlite3_column_bytes(res, 28) > 0 ? strdupz((char *)sqlite3_column_text(res, 28)) : NULL;
- BUFFER *tmp_buf = buffer_create(1024);
+ BUFFER *tmp_buf = buffer_create(1024, &netdata_buffers_statistics.buffers_sqlite);
buffer_data_options2string(tmp_buf, sqlite3_column_int(res, 29));
alarm_config.p_db_lookup_options = strdupz((char *)buffer_tostring(tmp_buf));
buffer_free(tmp_buf);
@@ -706,7 +760,7 @@ void aclk_start_alert_streaming(char *node_id, uint64_t batch_id, uint64_t start
(struct aclk_database_worker_config *)host->dbsync_worker :
(struct aclk_database_worker_config *)find_inactive_wc_by_node_id(node_id);
- if (unlikely(!host->health_enabled)) {
+ if (unlikely(!host->health.health_enabled)) {
log_access("ACLK STA [%s (N/A)]: Ignoring request to stream alert state changes, health is disabled.", node_id);
return;
}
@@ -735,7 +789,7 @@ void sql_process_queue_removed_alerts_to_aclk(struct aclk_database_worker_config
{
UNUSED(cmd);
- BUFFER *sql = buffer_create(1024);
+ BUFFER *sql = buffer_create(1024, &netdata_buffers_statistics.buffers_sqlite);
buffer_sprintf(sql,"insert into aclk_alert_%s (alert_unique_id, date_created, filtered_alert_unique_id) " \
"select unique_id alert_unique_id, unixepoch(), unique_id alert_unique_id from health_log_%s " \
@@ -813,7 +867,7 @@ void aclk_process_send_alarm_snapshot(char *node_id, char *claim_id, uint64_t sn
void aclk_mark_alert_cloud_ack(char *uuid_str, uint64_t alerts_ack_sequence_id)
{
- BUFFER *sql = buffer_create(1024);
+ BUFFER *sql = buffer_create(1024, &netdata_buffers_statistics.buffers_sqlite);
if (alerts_ack_sequence_id != 0) {
buffer_sprintf(
@@ -846,7 +900,7 @@ void health_alarm_entry2proto_nolock(struct alarm_log_entry *alarm_log, ALARM_EN
alarm_log->utc_offset = host->utc_offset;
alarm_log->timezone = strdupz(rrdhost_abbrev_timezone(host));
- alarm_log->exec_path = ae->exec ? strdupz(ae_exec(ae)) : strdupz((char *)string2str(host->health_default_exec));
+ alarm_log->exec_path = ae->exec ? strdupz(ae_exec(ae)) : strdupz((char *)string2str(host->health.health_default_exec));
alarm_log->conf_source = ae->source ? strdupz(ae_source(ae)) : strdupz((char *)"");
alarm_log->command = strdupz((char *)edit_command);
@@ -1022,7 +1076,7 @@ void sql_aclk_alert_clean_dead_entries(RRDHOST *host)
char uuid_str[GUID_LEN + 1];
uuid_unparse_lower_fix(&host->host_uuid, uuid_str);
- BUFFER *sql = buffer_create(1024);
+ BUFFER *sql = buffer_create(1024, &netdata_buffers_statistics.buffers_sqlite);
buffer_sprintf(sql,"delete from aclk_alert_%s where filtered_alert_unique_id not in "
" (select unique_id from health_log_%s); ", uuid_str, uuid_str);
@@ -1048,7 +1102,7 @@ int get_proto_alert_status(RRDHOST *host, struct proto_alert_status *proto_alert
proto_alert_status->alert_updates = wc->alert_updates;
proto_alert_status->alerts_batch_id = wc->alerts_batch_id;
- BUFFER *sql = buffer_create(1024);
+ BUFFER *sql = buffer_create(1024, &netdata_buffers_statistics.buffers_sqlite);
sqlite3_stmt *res = NULL;
buffer_sprintf(sql, "SELECT MIN(sequence_id), MAX(sequence_id), " \
diff --git a/database/sqlite/sqlite_context.c b/database/sqlite/sqlite_context.c
index deca8458..892292cc 100644
--- a/database/sqlite/sqlite_context.c
+++ b/database/sqlite/sqlite_context.c
@@ -283,8 +283,8 @@ void ctx_get_context_list(uuid_t *host_uuid, void (*dict_cb)(VERSIONED_CONTEXT_D
context_data.chart_type = (char *) sqlite3_column_text(res, 3);
context_data.units = (char *) sqlite3_column_text(res, 4);
context_data.priority = sqlite3_column_int64(res, 5);
- context_data.first_time_t = sqlite3_column_int64(res, 6);
- context_data.last_time_t = sqlite3_column_int64(res, 7);
+ context_data.first_time_s = sqlite3_column_int64(res, 6);
+ context_data.last_time_s = sqlite3_column_int64(res, 7);
context_data.deleted = sqlite3_column_int(res, 8);
context_data.family = (char *) sqlite3_column_text(res, 9);
dict_cb(&context_data, data);
@@ -360,13 +360,13 @@ int ctx_store_context(uuid_t *host_uuid, VERSIONED_CONTEXT_DATA *context_data)
goto skip_store;
}
- rc = sqlite3_bind_int64(res, 8, (time_t) context_data->first_time_t);
+ rc = sqlite3_bind_int64(res, 8, (time_t) context_data->first_time_s);
if (unlikely(rc != SQLITE_OK)) {
error_report("Failed to bind first_time_t to store context details");
goto skip_store;
}
- rc = sqlite3_bind_int64(res, 9, (time_t) context_data->last_time_t);
+ rc = sqlite3_bind_int64(res, 9, (time_t) context_data->last_time_s);
if (unlikely(rc != SQLITE_OK)) {
error_report("Failed to bind last_time_t to store context details");
goto skip_store;
@@ -478,8 +478,8 @@ static void dict_ctx_get_context_list_cb(VERSIONED_CONTEXT_DATA *context_data, v
context_data->chart_type,
context_data->units,
context_data->priority,
- context_data->first_time_t,
- context_data->last_time_t,
+ context_data->first_time_s,
+ context_data->last_time_s,
context_data->deleted,
context_data->family);
}
@@ -504,8 +504,8 @@ int ctx_unittest(void)
context_data.family = strdupz("TestContextFamily");
context_data.priority = 50000;
context_data.deleted = 0;
- context_data.first_time_t = 1657781000;
- context_data.last_time_t = 1657781100;
+ context_data.first_time_s = 1657781000;
+ context_data.last_time_s = 1657781100;
context_data.version = now_realtime_usec();
if (likely(!ctx_store_context(&host_uuid, &context_data)))
@@ -519,8 +519,8 @@ int ctx_unittest(void)
info("Entry %s not inserted", context_data.id);
// This will change end time
- context_data.first_time_t = 1657781000;
- context_data.last_time_t = 1657782001;
+ context_data.first_time_s = 1657781000;
+ context_data.last_time_s = 1657782001;
if (likely(!ctx_update_context(&host_uuid, &context_data)))
info("Entry %s updated", context_data.id);
else
@@ -530,8 +530,8 @@ int ctx_unittest(void)
info("List context end after insert");
// This will change start time
- context_data.first_time_t = 1657782000;
- context_data.last_time_t = 1657782001;
+ context_data.first_time_s = 1657782000;
+ context_data.last_time_s = 1657782001;
if (likely(!ctx_update_context(&host_uuid, &context_data)))
info("Entry %s updated", context_data.id);
else
diff --git a/database/sqlite/sqlite_context.h b/database/sqlite/sqlite_context.h
index 2e52b9bf..2586916e 100644
--- a/database/sqlite/sqlite_context.h
+++ b/database/sqlite/sqlite_context.h
@@ -45,8 +45,8 @@ typedef struct versioned_context_data {
uint64_t priority; // the chart priority of the context
- uint64_t first_time_t; // the first entry in the database, in seconds
- uint64_t last_time_t; // the last point in the database, in seconds
+ uint64_t first_time_s; // the first entry in the database, in seconds
+ uint64_t last_time_s; // the last point in the database, in seconds
bool deleted; // true when this is deleted
diff --git a/database/sqlite/sqlite_functions.c b/database/sqlite/sqlite_functions.c
index ce5487fb..1d03cfc2 100644
--- a/database/sqlite/sqlite_functions.c
+++ b/database/sqlite/sqlite_functions.c
@@ -22,9 +22,8 @@ const char *database_config[] = {
"multiplier int, divisor int , algorithm int, options text);",
"CREATE TABLE IF NOT EXISTS metadata_migration(filename text, file_size, date_created int);",
- "CREATE INDEX IF NOT EXISTS ind_d1 on dimension (chart_id, id, name);",
- "CREATE INDEX IF NOT EXISTS ind_c1 on chart (host_id, id, type, name);",
- "CREATE INDEX IF NOT EXISTS ind_c2 on chart (host_id, context);",
+ "CREATE INDEX IF NOT EXISTS ind_d2 on dimension (chart_id);",
+ "CREATE INDEX IF NOT EXISTS ind_c3 on chart (host_id);",
"CREATE TABLE IF NOT EXISTS chart_label(chart_id blob, source_type int, label_key text, "
"label_value text, date_created int, PRIMARY KEY (chart_id, label_key));",
"CREATE TABLE IF NOT EXISTS node_instance (host_id blob PRIMARY KEY, claim_id, node_id, date_created);",
@@ -55,6 +54,9 @@ const char *database_cleanup[] = {
"DELETE FROM host_info WHERE host_id NOT IN (SELECT host_id FROM host);",
"DELETE FROM host_label WHERE host_id NOT IN (SELECT host_id FROM host);",
"DROP TRIGGER IF EXISTS tr_dim_del;",
+ "DROP INDEX IF EXISTS ind_d1;",
+ "DROP INDEX IF EXISTS ind_c1;",
+ "DROP INDEX IF EXISTS ind_c2;",
NULL
};
@@ -504,211 +506,6 @@ skip:
return result;
}
-
-
-//
-// Support for archived charts (TO BE REMOVED)
-//
-#define SELECT_DIMENSION "select d.id, d.name from dimension d where d.chart_id = @chart_uuid;"
-
-static void sql_rrdim2json(sqlite3_stmt *res_dim, uuid_t *chart_uuid, BUFFER *wb, size_t *dimensions_count)
-{
- int rc;
-
- rc = sqlite3_bind_blob(res_dim, 1, chart_uuid, sizeof(*chart_uuid), SQLITE_STATIC);
- if (rc != SQLITE_OK)
- return;
-
- int dimensions = 0;
- buffer_sprintf(wb, "\t\t\t\"dimensions\": {\n");
-
- while (sqlite3_step_monitored(res_dim) == SQLITE_ROW) {
- if (dimensions)
- buffer_strcat(wb, ",\n\t\t\t\t\"");
- else
- buffer_strcat(wb, "\t\t\t\t\"");
- buffer_strcat_jsonescape(wb, (const char *) sqlite3_column_text(res_dim, 0));
- buffer_strcat(wb, "\": { \"name\": \"");
- buffer_strcat_jsonescape(wb, (const char *) sqlite3_column_text(res_dim, 1));
- buffer_strcat(wb, "\" }");
- dimensions++;
- }
- *dimensions_count += dimensions;
- buffer_sprintf(wb, "\n\t\t\t}");
-}
-
-#define SELECT_CHART "select chart_id, id, name, type, family, context, title, priority, plugin, " \
- "module, unit, chart_type, update_every from chart " \
- "where host_id = @host_uuid and chart_id not in (select chart_id from chart_active) order by chart_id asc;"
-
-void sql_rrdset2json(RRDHOST *host, BUFFER *wb)
-{
- // time_t first_entry_t = 0; //= rrdset_first_entry_t(st);
- // time_t last_entry_t = 0; //rrdset_last_entry_t(st);
- static char *custom_dashboard_info_js_filename = NULL;
- int rc;
-
- sqlite3_stmt *res_chart = NULL;
- sqlite3_stmt *res_dim = NULL;
- time_t now = now_realtime_sec();
-
- rc = sqlite3_prepare_v2(db_meta, SELECT_CHART, -1, &res_chart, 0);
- if (unlikely(rc != SQLITE_OK)) {
- error_report("Failed to prepare statement to fetch host archived charts");
- return;
- }
-
- rc = sqlite3_bind_blob(res_chart, 1, &host->host_uuid, sizeof(host->host_uuid), SQLITE_STATIC);
- if (unlikely(rc != SQLITE_OK)) {
- error_report("Failed to bind host parameter to fetch archived charts");
- goto failed;
- }
-
- rc = sqlite3_prepare_v2(db_meta, SELECT_DIMENSION, -1, &res_dim, 0);
- if (unlikely(rc != SQLITE_OK)) {
- error_report("Failed to prepare statement to fetch chart archived dimensions");
- goto failed;
- };
-
- if(unlikely(!custom_dashboard_info_js_filename))
- custom_dashboard_info_js_filename = config_get(CONFIG_SECTION_WEB, "custom dashboard_info.js", "");
-
- buffer_sprintf(wb, "{\n"
- "\t\"hostname\": \"%s\""
- ",\n\t\"version\": \"%s\""
- ",\n\t\"release_channel\": \"%s\""
- ",\n\t\"os\": \"%s\""
- ",\n\t\"timezone\": \"%s\""
- ",\n\t\"update_every\": %d"
- ",\n\t\"history\": %ld"
- ",\n\t\"memory_mode\": \"%s\""
- ",\n\t\"custom_info\": \"%s\""
- ",\n\t\"charts\": {"
- , rrdhost_hostname(host)
- , rrdhost_program_version(host)
- , get_release_channel()
- , rrdhost_os(host)
- , rrdhost_timezone(host)
- , host->rrd_update_every
- , host->rrd_history_entries
- , rrd_memory_mode_name(host->rrd_memory_mode)
- , custom_dashboard_info_js_filename
- );
-
- size_t c = 0;
- size_t dimensions = 0;
-
- while (sqlite3_step_monitored(res_chart) == SQLITE_ROW) {
- char id[512];
- sprintf(id, "%s.%s", sqlite3_column_text(res_chart, 3), sqlite3_column_text(res_chart, 1));
- RRDSET *st = rrdset_find(host, id);
- if (st && !rrdset_flag_check(st, RRDSET_FLAG_ARCHIVED))
- continue;
-
- if (c)
- buffer_strcat(wb, ",\n\t\t\"");
- else
- buffer_strcat(wb, "\n\t\t\"");
- c++;
-
- buffer_strcat(wb, id);
- buffer_strcat(wb, "\": ");
-
- buffer_sprintf(
- wb,
- "\t\t{\n"
- "\t\t\t\"id\": \"%s\",\n"
- "\t\t\t\"name\": \"%s\",\n"
- "\t\t\t\"type\": \"%s\",\n"
- "\t\t\t\"family\": \"%s\",\n"
- "\t\t\t\"context\": \"%s\",\n"
- "\t\t\t\"title\": \"%s (%s)\",\n"
- "\t\t\t\"priority\": %ld,\n"
- "\t\t\t\"plugin\": \"%s\",\n"
- "\t\t\t\"module\": \"%s\",\n"
- "\t\t\t\"enabled\": %s,\n"
- "\t\t\t\"units\": \"%s\",\n"
- "\t\t\t\"data_url\": \"/api/v1/data?chart=%s\",\n"
- "\t\t\t\"chart_type\": \"%s\",\n",
- id //sqlite3_column_text(res_chart, 1)
- ,
- id // sqlite3_column_text(res_chart, 2)
- ,
- sqlite3_column_text(res_chart, 3), sqlite3_column_text(res_chart, 4), sqlite3_column_text(res_chart, 5),
- sqlite3_column_text(res_chart, 6), id //sqlite3_column_text(res_chart, 2)
- ,
- (long ) sqlite3_column_int(res_chart, 7),
- (const char *) sqlite3_column_text(res_chart, 8) ? (const char *) sqlite3_column_text(res_chart, 8) : (char *) "",
- (const char *) sqlite3_column_text(res_chart, 9) ? (const char *) sqlite3_column_text(res_chart, 9) : (char *) "", (char *) "false",
- (const char *) sqlite3_column_text(res_chart, 10), id //sqlite3_column_text(res_chart, 2)
- ,
- rrdset_type_name(sqlite3_column_int(res_chart, 11)));
-
- sql_rrdim2json(res_dim, (uuid_t *) sqlite3_column_blob(res_chart, 0), wb, &dimensions);
-
- rc = sqlite3_reset(res_dim);
- if (unlikely(rc != SQLITE_OK))
- error_report("Failed to reset the prepared statement when reading archived chart dimensions");
- buffer_strcat(wb, "\n\t\t}");
- }
-
- buffer_sprintf(wb
- , "\n\t}"
- ",\n\t\"charts_count\": %zu"
- ",\n\t\"dimensions_count\": %zu"
- ",\n\t\"alarms_count\": %zu"
- ",\n\t\"rrd_memory_bytes\": %zu"
- ",\n\t\"hosts_count\": %zu"
- ",\n\t\"hosts\": ["
- , c
- , dimensions
- , (size_t) 0
- , (size_t) 0
- , rrd_hosts_available
- );
-
- if(unlikely(rrd_hosts_available > 1)) {
- rrd_rdlock();
-
- size_t found = 0;
- RRDHOST *h;
- rrdhost_foreach_read(h) {
- if(!rrdhost_should_be_removed(h, host, now) && !rrdhost_flag_check(h, RRDHOST_FLAG_ARCHIVED)) {
- buffer_sprintf(wb
- , "%s\n\t\t{"
- "\n\t\t\t\"hostname\": \"%s\""
- "\n\t\t}"
- , (found > 0) ? "," : ""
- , rrdhost_hostname(h)
- );
-
- found++;
- }
- }
-
- rrd_unlock();
- }
- else {
- buffer_sprintf(wb
- , "\n\t\t{"
- "\n\t\t\t\"hostname\": \"%s\""
- "\n\t\t}"
- , rrdhost_hostname(host)
- );
- }
-
- buffer_sprintf(wb, "\n\t]\n}\n");
-
- rc = sqlite3_finalize(res_dim);
- if (unlikely(rc != SQLITE_OK))
- error_report("Failed to finalize the prepared statement when reading archived chart dimensions");
-
-failed:
- rc = sqlite3_finalize(res_chart);
- if (unlikely(rc != SQLITE_OK))
- error_report("Failed to finalize the prepared statement when reading archived charts");
-}
-
void db_execute(const char *cmd)
{
int rc;
@@ -732,116 +529,6 @@ void db_execute(const char *cmd)
}
}
-#define SELECT_MIGRATED_FILE "select 1 from metadata_migration where filename = @path;"
-
-int file_is_migrated(char *path)
-{
- sqlite3_stmt *res = NULL;
- int rc;
-
- rc = sqlite3_prepare_v2(db_meta, SELECT_MIGRATED_FILE, -1, &res, 0);
- if (unlikely(rc != SQLITE_OK)) {
- error_report("Failed to prepare statement to fetch host");
- return 0;
- }
-
- rc = sqlite3_bind_text(res, 1, path, -1, SQLITE_STATIC);
- if (unlikely(rc != SQLITE_OK)) {
- error_report("Failed to bind filename parameter to check migration");
- return 0;
- }
-
- rc = sqlite3_step_monitored(res);
-
- if (unlikely(sqlite3_finalize(res) != SQLITE_OK))
- error_report("Failed to finalize the prepared statement when checking if metadata file is migrated");
-
- return (rc == SQLITE_ROW);
-}
-
-#define STORE_MIGRATED_FILE "insert or replace into metadata_migration (filename, file_size, date_created) " \
- "values (@file, @size, unixepoch());"
-
-void add_migrated_file(char *path, uint64_t file_size)
-{
- sqlite3_stmt *res = NULL;
- int rc;
-
- rc = sqlite3_prepare_v2(db_meta, STORE_MIGRATED_FILE, -1, &res, 0);
- if (unlikely(rc != SQLITE_OK)) {
- error_report("Failed to prepare statement to fetch host");
- return;
- }
-
- rc = sqlite3_bind_text(res, 1, path, -1, SQLITE_STATIC);
- if (unlikely(rc != SQLITE_OK)) {
- error_report("Failed to bind filename parameter to store migration information");
- return;
- }
-
- rc = sqlite3_bind_int64(res, 2, (sqlite_int64) file_size);
- if (unlikely(rc != SQLITE_OK)) {
- error_report("Failed to bind size parameter to store migration information");
- return;
- }
-
- rc = execute_insert(res);
- if (unlikely(rc != SQLITE_DONE))
- error_report("Failed to store migrated file, rc = %d", rc);
-
- if (unlikely(sqlite3_finalize(res) != SQLITE_OK))
- error_report("Failed to finalize the prepared statement when checking if metadata file is migrated");
-}
-
-
-
-#define SQL_STORE_CLAIM_ID "insert into node_instance " \
- "(host_id, claim_id, date_created) values (@host_id, @claim_id, unixepoch()) " \
- "on conflict(host_id) do update set claim_id = excluded.claim_id;"
-
-void store_claim_id(uuid_t *host_id, uuid_t *claim_id)
-{
- sqlite3_stmt *res = NULL;
- int rc;
-
- if (unlikely(!db_meta)) {
- if (default_rrd_memory_mode == RRD_MEMORY_MODE_DBENGINE)
- error_report("Database has not been initialized");
- return;
- }
-
- rc = sqlite3_prepare_v2(db_meta, SQL_STORE_CLAIM_ID, -1, &res, 0);
- if (unlikely(rc != SQLITE_OK)) {
- error_report("Failed to prepare statement store chart labels");
- return;
- }
-
- rc = sqlite3_bind_blob(res, 1, host_id, sizeof(*host_id), SQLITE_STATIC);
- if (unlikely(rc != SQLITE_OK)) {
- error_report("Failed to bind host_id parameter to store node instance information");
- goto failed;
- }
-
- if (claim_id)
- rc = sqlite3_bind_blob(res, 2, claim_id, sizeof(*claim_id), SQLITE_STATIC);
- else
- rc = sqlite3_bind_null(res, 2);
- if (unlikely(rc != SQLITE_OK)) {
- error_report("Failed to bind claim_id parameter to store node instance information");
- goto failed;
- }
-
- rc = execute_insert(res);
- if (unlikely(rc != SQLITE_DONE))
- error_report("Failed to store node instance information, rc = %d", rc);
-
-failed:
- if (unlikely(sqlite3_finalize(res) != SQLITE_OK))
- error_report("Failed to finalize the prepared statement when storing node instance information");
-
- return;
-}
-
static inline void set_host_node_id(RRDHOST *host, uuid_t *node_id)
{
if (unlikely(!host))
@@ -1268,112 +955,3 @@ int sql_metadata_cache_stats(int op)
netdata_thread_enable_cancelability();
return count;
}
-
-#define SQL_FIND_CHART_UUID \
- "SELECT chart_id FROM chart WHERE host_id = @host AND type=@type AND id=@id AND (name IS NULL OR name=@name) AND chart_id IS NOT NULL;"
-
-#define SQL_FIND_DIMENSION_UUID \
- "SELECT dim_id FROM dimension WHERE chart_id=@chart AND id=@id AND name=@name AND LENGTH(dim_id)=16;"
-
-
-//Do a database lookup to find the UUID of a chart
-//If found store it in store_uuid and return 0
-int sql_find_chart_uuid(RRDHOST *host, RRDSET *st, uuid_t *store_uuid)
-{
- static __thread sqlite3_stmt *res = NULL;
- int rc;
-
- const char *name = string2str(st->parts.name);
-
- if (unlikely(!db_meta) && default_rrd_memory_mode != RRD_MEMORY_MODE_DBENGINE)
- return 1;
-
- if (unlikely(!res)) {
- rc = prepare_statement(db_meta, SQL_FIND_CHART_UUID, &res);
- if (rc != SQLITE_OK) {
- error_report("Failed to prepare statement to lookup chart UUID in the database");
- return 1;
- }
- }
-
- rc = sqlite3_bind_blob(res, 1, &host->host_uuid, sizeof(host->host_uuid), SQLITE_STATIC);
- if (unlikely(rc != SQLITE_OK))
- goto bind_fail;
-
- rc = sqlite3_bind_text(res, 2, string2str(st->parts.type), -1, SQLITE_STATIC);
- if (unlikely(rc != SQLITE_OK))
- goto bind_fail;
-
- rc = sqlite3_bind_text(res, 3, string2str(st->parts.id), -1, SQLITE_STATIC);
- if (unlikely(rc != SQLITE_OK))
- goto bind_fail;
-
- rc = sqlite3_bind_text(res, 4, name && *name ? name : string2str(st->parts.id), -1, SQLITE_STATIC);
- if (unlikely(rc != SQLITE_OK))
- goto bind_fail;
-
- int status = 1;
- rc = sqlite3_step_monitored(res);
- if (likely(rc == SQLITE_ROW)) {
- uuid_copy(*store_uuid, sqlite3_column_blob(res, 0));
- status = 0;
- }
-
- rc = sqlite3_reset(res);
- if (unlikely(rc != SQLITE_OK))
- error_report("Failed to reset statement when searching for a chart UUID, rc = %d", rc);
-
- return status;
-
-bind_fail:
- error_report("Failed to bind input parameter to perform chart UUID database lookup, rc = %d", rc);
- rc = sqlite3_reset(res);
- if (unlikely(rc != SQLITE_OK))
- error_report("Failed to reset statement when searching for a chart UUID, rc = %d", rc);
- return 1;
-}
-
-int sql_find_dimension_uuid(RRDSET *st, RRDDIM *rd, uuid_t *store_uuid)
-{
- static __thread sqlite3_stmt *res = NULL;
- int rc;
- int status = 1;
-
- if (unlikely(!db_meta) && default_rrd_memory_mode != RRD_MEMORY_MODE_DBENGINE)
- return 1;
-
- if (unlikely(!res)) {
- rc = prepare_statement(db_meta, SQL_FIND_DIMENSION_UUID, &res);
- if (rc != SQLITE_OK) {
- error_report("Failed to bind prepare statement to lookup dimension UUID in the database");
- return 1;
- }
- }
-
- rc = sqlite3_bind_blob(res, 1, st->chart_uuid, sizeof(*st->chart_uuid), SQLITE_STATIC);
- if (unlikely(rc != SQLITE_OK))
- goto bind_fail;
-
- rc = sqlite3_bind_text(res, 2, rrddim_id(rd), -1, SQLITE_STATIC);
- if (unlikely(rc != SQLITE_OK))
- goto bind_fail;
-
- rc = sqlite3_bind_text(res, 3, rrddim_name(rd), -1, SQLITE_STATIC);
- if (unlikely(rc != SQLITE_OK))
- goto bind_fail;
-
- rc = sqlite3_step_monitored(res);
- if (likely(rc == SQLITE_ROW)) {
- uuid_copy(*store_uuid, *((uuid_t *) sqlite3_column_blob(res, 0)));
- status = 0;
- }
-
- rc = sqlite3_reset(res);
- if (unlikely(rc != SQLITE_OK))
- error_report("Failed to reset statement find dimension uuid, rc = %d", rc);
- return status;
-
-bind_fail:
- error_report("Failed to bind input parameter to perform dimension UUID database lookup, rc = %d", rc);
- return 1;
-}
diff --git a/database/sqlite/sqlite_functions.h b/database/sqlite/sqlite_functions.h
index 5731d5c9..40abd010 100644
--- a/database/sqlite/sqlite_functions.h
+++ b/database/sqlite/sqlite_functions.h
@@ -54,9 +54,7 @@ void sql_close_database(void);
int bind_text_null(sqlite3_stmt *res, int position, const char *text, bool can_be_null);
int prepare_statement(sqlite3 *database, const char *query, sqlite3_stmt **statement);
int execute_insert(sqlite3_stmt *res);
-int file_is_migrated(char *path);
int exec_statement_with_uuid(const char *sql, uuid_t *uuid);
-void add_migrated_file(char *path, uint64_t file_size);
void db_execute(const char *cmd);
// Look up functions
@@ -65,16 +63,11 @@ int get_host_id(uuid_t *node_id, uuid_t *host_id);
struct node_instance_list *get_node_list(void);
void sql_load_node_id(RRDHOST *host);
char *get_hostname_by_node_id(char *node_id);
-int sql_find_chart_uuid(RRDHOST *host, RRDSET *st, uuid_t *store_uuid);
-int sql_find_dimension_uuid(RRDSET *st, RRDDIM *rd, uuid_t *store_uuid);
// Help build archived hosts in memory when agent starts
void sql_build_host_system_info(uuid_t *host_id, struct rrdhost_system_info *system_info);
DICTIONARY *sql_load_host_labels(uuid_t *host_id);
-// For queries: To be removed when context queries are implemented
-void sql_rrdset2json(RRDHOST *host, BUFFER *wb);
-
// TODO: move to metadata
int update_node_id(uuid_t *host_id, uuid_t *node_id);
diff --git a/database/sqlite/sqlite_health.c b/database/sqlite/sqlite_health.c
index c189305b..471fa3ad 100644
--- a/database/sqlite/sqlite_health.c
+++ b/database/sqlite/sqlite_health.c
@@ -61,8 +61,12 @@ void sql_health_alarm_log_update(RRDHOST *host, ALARM_ENTRY *ae) {
rc = sqlite3_prepare_v2(db_meta, command, -1, &res, 0);
if (unlikely(rc != SQLITE_OK)) {
- error_report("HEALTH [%s]: Failed to prepare statement for SQL_UPDATE_HEALTH_LOG", rrdhost_hostname(host));
- return;
+ sql_create_health_log_table(host);
+ rc = sqlite3_prepare_v2(db_meta, command, -1, &res, 0);
+ if (unlikely(rc != SQLITE_OK)) {
+ error_report("HEALTH [%s]: Failed to prepare statement for SQL_INSERT_HEALTH_LOG", rrdhost_hostname(host));
+ return;
+ }
}
rc = sqlite3_bind_int64(res, 1, (sqlite3_int64) ae->updated_by_id);
@@ -103,8 +107,6 @@ void sql_health_alarm_log_update(RRDHOST *host, ALARM_ENTRY *ae) {
failed:
if (unlikely(sqlite3_finalize(res) != SQLITE_OK))
error_report("HEALTH [%s]: Failed to finalize the prepared statement for updating health log.", rrdhost_hostname(host));
-
- return;
}
/* Health related SQL queries
@@ -134,8 +136,12 @@ void sql_health_alarm_log_insert(RRDHOST *host, ALARM_ENTRY *ae) {
rc = sqlite3_prepare_v2(db_meta, command, -1, &res, 0);
if (unlikely(rc != SQLITE_OK)) {
- error_report("HEALTH [%s]: Failed to prepare statement for SQL_INSERT_HEALTH_LOG", rrdhost_hostname(host));
- return;
+ sql_create_health_log_table(host);
+ rc = sqlite3_prepare_v2(db_meta, command, -1, &res, 0);
+ if (unlikely(rc != SQLITE_OK)) {
+ error_report("HEALTH [%s]: Failed to prepare statement for SQL_INSERT_HEALTH_LOG", rrdhost_hostname(host));
+ return;
+ }
}
rc = sqlite3_bind_text(res, 1, rrdhost_hostname(host), -1, SQLITE_STATIC);
@@ -337,13 +343,11 @@ void sql_health_alarm_log_insert(RRDHOST *host, ALARM_ENTRY *ae) {
}
ae->flags |= HEALTH_ENTRY_FLAG_SAVED;
- host->health_log_entries_written++;
+ host->health.health_log_entries_written++;
failed:
if (unlikely(sqlite3_finalize(res) != SQLITE_OK))
error_report("HEALTH [%s]: Failed to finalize the prepared statement for inserting to health log.", rrdhost_hostname(host));
-
- return;
}
void sql_health_alarm_log_save(RRDHOST *host, ALARM_ENTRY *ae)
@@ -369,7 +373,7 @@ void sql_health_alarm_log_cleanup(RRDHOST *host) {
if(rotate_every < 100) rotate_every = 100;
}
- if(likely(host->health_log_entries_written < rotate_every)) {
+ if(likely(host->health.health_log_entries_written < rotate_every)) {
return;
}
@@ -382,7 +386,7 @@ void sql_health_alarm_log_cleanup(RRDHOST *host) {
char uuid_str[GUID_LEN + 1];
uuid_unparse_lower_fix(&host->host_uuid, uuid_str);
- snprintfz(command, MAX_HEALTH_SQL_SIZE, SQL_CLEANUP_HEALTH_LOG(uuid_str, uuid_str, (unsigned long int) (host->health_log_entries_written - rotate_every)));
+ snprintfz(command, MAX_HEALTH_SQL_SIZE, SQL_CLEANUP_HEALTH_LOG(uuid_str, uuid_str, (unsigned long int) (host->health.health_log_entries_written - rotate_every)));
rc = sqlite3_prepare_v2(db_meta, command, -1, &res, 0);
if (unlikely(rc != SQLITE_OK)) {
@@ -398,7 +402,7 @@ void sql_health_alarm_log_cleanup(RRDHOST *host) {
if (unlikely(rc != SQLITE_OK))
error_report("Failed to finalize the prepared statement to cleanup health log table");
- host->health_log_entries_written = rotate_every;
+ host->health.health_log_entries_written = rotate_every;
sql_aclk_alert_clean_dead_entries(host);
}
@@ -431,13 +435,13 @@ void sql_health_alarm_log_count(RRDHOST *host) {
rc = sqlite3_step_monitored(res);
if (likely(rc == SQLITE_ROW))
- host->health_log_entries_written = (size_t) sqlite3_column_int64(res, 0);
+ host->health.health_log_entries_written = (size_t) sqlite3_column_int64(res, 0);
rc = sqlite3_finalize(res);
if (unlikely(rc != SQLITE_OK))
error_report("Failed to finalize the prepared statement to count health log entries from db");
- info("HEALTH [%s]: Table health_log_%s, contains %lu entries.", rrdhost_hostname(host), uuid_str, (unsigned long int) host->health_log_entries_written);
+ info("HEALTH [%s]: Table health_log_%s, contains %lu entries.", rrdhost_hostname(host), uuid_str, (unsigned long int) host->health.health_log_entries_written);
}
#define SQL_INJECT_REMOVED(guid, guid2) "insert into health_log_%s (hostname, unique_id, alarm_id, alarm_event_id, config_hash_id, updated_by_id, updates_id, when_key, duration, non_clear_duration, flags, exec_run_timestamp, " \
@@ -537,8 +541,6 @@ void sql_inject_removed_status(char *uuid_str, uint32_t alarm_id, uint32_t alarm
failed:
if (unlikely(sqlite3_finalize(res) != SQLITE_OK))
error_report("HEALTH [N/A]: Failed to finalize the prepared statement for injecting removed event.");
- return;
-
}
#define SQL_SELECT_MAX_UNIQUE_ID(guid) "SELECT MAX(unique_id) from health_log_%s", guid
@@ -612,7 +614,7 @@ void sql_health_alarm_log_load(RRDHOST *host) {
ssize_t errored = 0, loaded = 0;
char command[MAX_HEALTH_SQL_SIZE + 1];
- host->health_log_entries_written = 0;
+ host->health.health_log_entries_written = 0;
if (unlikely(!db_meta)) {
if (default_rrd_memory_mode == RRD_MEMORY_MODE_DBENGINE)
diff --git a/database/sqlite/sqlite_metadata.c b/database/sqlite/sqlite_metadata.c
index 4eb21215..35f928ff 100644
--- a/database/sqlite/sqlite_metadata.c
+++ b/database/sqlite/sqlite_metadata.c
@@ -4,9 +4,9 @@
// SQL statements
-#define SQL_STORE_CLAIM_ID "insert into node_instance " \
- "(host_id, claim_id, date_created) values (@host_id, @claim_id, unixepoch()) " \
- "on conflict(host_id) do update set claim_id = excluded.claim_id;"
+#define SQL_STORE_CLAIM_ID "INSERT INTO node_instance " \
+ "(host_id, claim_id, date_created) VALUES (@host_id, @claim_id, unixepoch()) " \
+ "ON CONFLICT(host_id) DO UPDATE SET claim_id = excluded.claim_id;"
#define SQL_DELETE_HOST_LABELS "DELETE FROM host_label WHERE host_id = @uuid;"
@@ -56,24 +56,13 @@
#define MAX_METADATA_CLEANUP (500) // Maximum metadata write operations (e.g deletes before retrying)
#define METADATA_MAX_BATCH_SIZE (512) // Maximum commands to execute before running the event loop
-#define METADATA_MAX_TRANSACTION_BATCH (128) // Maximum commands to add in a transaction
enum metadata_opcode {
METADATA_DATABASE_NOOP = 0,
METADATA_DATABASE_TIMER,
- METADATA_ADD_CHART,
- METADATA_ADD_CHART_LABEL,
- METADATA_ADD_DIMENSION,
METADATA_DEL_DIMENSION,
- METADATA_ADD_DIMENSION_OPTION,
- METADATA_ADD_HOST_SYSTEM_INFO,
- METADATA_ADD_HOST_INFO,
METADATA_STORE_CLAIM_ID,
- METADATA_STORE_HOST_LABELS,
- METADATA_STORE_BUFFER,
-
- METADATA_SKIP_TRANSACTION, // Dummy -- OPCODES less than this one can be in a tranasction
-
+ METADATA_ADD_HOST_INFO,
METADATA_SCAN_HOSTS,
METADATA_MAINTENANCE,
METADATA_SYNC_SHUTDOWN,
@@ -105,14 +94,14 @@ typedef enum {
struct metadata_wc {
uv_thread_t thread;
+ uv_loop_t *loop;
+ uv_async_t async;
+ uv_timer_t timer_req;
time_t check_metadata_after;
time_t check_hosts_after;
volatile unsigned queue_size;
- uv_loop_t *loop;
- uv_async_t async;
METADATA_FLAG flags;
uint64_t row_id;
- uv_timer_t timer_req;
struct completion init_complete;
/* FIFO command queue */
uv_mutex_t cmd_mutex;
@@ -339,7 +328,7 @@ static int sql_store_host_info(RRDHOST *host)
if (unlikely(rc != SQLITE_OK))
goto bind_fail;
- rc = sqlite3_bind_int(res, ++param, (int ) host->health_enabled);
+ rc = sqlite3_bind_int(res, ++param, (int ) host->health.health_enabled);
if (unlikely(rc != SQLITE_OK))
goto bind_fail;
@@ -383,7 +372,7 @@ static BUFFER *sql_store_host_system_info(RRDHOST *host)
if (unlikely(!system_info))
return NULL;
- BUFFER *work_buffer = buffer_create(1024);
+ BUFFER *work_buffer = buffer_create(1024, &netdata_buffers_statistics.buffers_sqlite);
struct query_build key_data = {.sql = work_buffer, .count = 0};
uuid_unparse_lower(host->host_uuid, key_data.uuid_str);
@@ -418,49 +407,6 @@ static BUFFER *sql_store_host_system_info(RRDHOST *host)
/*
- * Store set option for a dimension
- */
-static int sql_set_dimension_option(uuid_t *dim_uuid, char *option)
-{
- sqlite3_stmt *res = NULL;
- int rc;
-
- if (unlikely(!db_meta)) {
- if (default_rrd_memory_mode != RRD_MEMORY_MODE_DBENGINE)
- return 0;
- error_report("Database has not been initialized");
- return 1;
- }
-
- rc = sqlite3_prepare_v2(db_meta, "UPDATE dimension SET options = @options WHERE dim_id = @dim_id", -1, &res, 0);
- if (unlikely(rc != SQLITE_OK)) {
- error_report("Failed to prepare statement to update dimension options");
- return 0;
- };
-
- rc = sqlite3_bind_blob(res, 2, dim_uuid, sizeof(*dim_uuid), SQLITE_STATIC);
- if (unlikely(rc != SQLITE_OK))
- goto bind_fail;
-
- if (!option || !strcmp(option,"unhide"))
- rc = sqlite3_bind_null(res, 1);
- else
- rc = sqlite3_bind_text(res, 1, option, -1, SQLITE_STATIC);
- if (unlikely(rc != SQLITE_OK))
- goto bind_fail;
-
- rc = execute_insert(res);
- if (unlikely(rc != SQLITE_DONE))
- error_report("Failed to update dimension option, rc = %d", rc);
-
-bind_fail:
- rc = sqlite3_finalize(res);
- if (unlikely(rc != SQLITE_OK))
- error_report("Failed to finalize statement in update dimension options, rc = %d", rc);
- return 0;
-}
-
-/*
* Store a chart in the database
*/
@@ -665,22 +611,26 @@ bind_fail:
return 1;
}
-static bool dimension_can_be_deleted(uuid_t *dim_uuid)
+static bool dimension_can_be_deleted(uuid_t *dim_uuid __maybe_unused)
{
#ifdef ENABLE_DBENGINE
- bool no_retention = true;
- for (size_t tier = 0; tier < storage_tiers; tier++) {
- if (!multidb_ctx[tier])
- continue;
- time_t first_time_t = 0, last_time_t = 0;
- if (rrdeng_metric_retention_by_uuid((void *) multidb_ctx[tier], dim_uuid, &first_time_t, &last_time_t) == 0) {
- if (first_time_t > 0) {
- no_retention = false;
- break;
+ if(dbengine_enabled) {
+ bool no_retention = true;
+ for (size_t tier = 0; tier < storage_tiers; tier++) {
+ if (!multidb_ctx[tier])
+ continue;
+ time_t first_time_t = 0, last_time_t = 0;
+ if (rrdeng_metric_retention_by_uuid((void *) multidb_ctx[tier], dim_uuid, &first_time_t, &last_time_t)) {
+ if (first_time_t > 0) {
+ no_retention = false;
+ break;
+ }
}
}
+ return no_retention;
}
- return no_retention;
+ else
+ return false;
#else
return false;
#endif
@@ -736,6 +686,16 @@ skip_run:
error_report("Failed to finalize the prepared statement when reading dimensions");
}
+static void cleanup_health_log(void)
+{
+ RRDHOST *host;
+ dfe_start_reentrant(rrdhost_root_index, host) {
+ if (rrdhost_flag_check(host, RRDHOST_FLAG_ARCHIVED))
+ continue;
+ sql_health_alarm_log_cleanup(host);
+ }
+ dfe_done(host);
+}
//
// EVENT LOOP STARTS HERE
@@ -817,7 +777,7 @@ static void metadata_enq_cmd(struct metadata_wc *wc, struct metadata_cmd *cmd)
(void) uv_async_send(&wc->async);
}
-static struct metadata_cmd metadata_deq_cmd(struct metadata_wc *wc, enum metadata_opcode *next_opcode)
+static struct metadata_cmd metadata_deq_cmd(struct metadata_wc *wc)
{
struct metadata_cmd ret;
unsigned queue_size;
@@ -828,7 +788,6 @@ static struct metadata_cmd metadata_deq_cmd(struct metadata_wc *wc, enum metadat
memset(&ret, 0, sizeof(ret));
ret.opcode = METADATA_DATABASE_NOOP;
ret.completion = NULL;
- *next_opcode = METADATA_DATABASE_NOOP;
} else {
/* dequeue command */
ret = wc->cmd_queue.cmd_array[wc->cmd_queue.head];
@@ -840,10 +799,6 @@ static struct metadata_cmd metadata_deq_cmd(struct metadata_wc *wc, enum metadat
wc->cmd_queue.head + 1 : 0;
}
wc->queue_size = queue_size - 1;
- if (wc->queue_size > 0)
- *next_opcode = wc->cmd_queue.cmd_array[wc->cmd_queue.head].opcode;
- else
- *next_opcode = METADATA_DATABASE_NOOP;
/* wake up producers */
uv_cond_signal(&wc->cmd_cond);
}
@@ -892,10 +847,16 @@ static void after_metadata_cleanup(uv_work_t *req, int status)
struct metadata_wc *wc = req->data;
metadata_flag_clear(wc, METADATA_FLAG_CLEANUP);
}
+
static void start_metadata_cleanup(uv_work_t *req)
{
+ register_libuv_worker_jobs();
+
+ worker_is_busy(UV_EVENT_METADATA_CLEANUP);
struct metadata_wc *wc = req->data;
check_dimension_metadata(wc);
+ cleanup_health_log();
+ worker_is_idle();
}
struct scan_metadata_payload {
@@ -920,13 +881,13 @@ static void after_metadata_hosts(uv_work_t *req, int status __maybe_unused)
freez(data);
}
-static bool metadata_scan_host(RRDHOST *host, uint32_t max_count) {
+static bool metadata_scan_host(RRDHOST *host, uint32_t max_count, size_t *query_counter) {
RRDSET *st;
int rc;
bool more_to_do = false;
uint32_t scan_count = 1;
- BUFFER *work_buffer = buffer_create(1024);
+ BUFFER *work_buffer = buffer_create(1024, &netdata_buffers_statistics.buffers_sqlite);
rrdset_foreach_reentrant(st, host) {
if (scan_count == max_count) {
@@ -934,6 +895,8 @@ static bool metadata_scan_host(RRDHOST *host, uint32_t max_count) {
break;
}
if(rrdset_flag_check(st, RRDSET_FLAG_METADATA_UPDATE)) {
+ (*query_counter)++;
+
rrdset_flag_clear(st, RRDSET_FLAG_METADATA_UPDATE);
scan_count++;
@@ -963,8 +926,15 @@ static bool metadata_scan_host(RRDHOST *host, uint32_t max_count) {
RRDDIM *rd;
rrddim_foreach_read(rd, st) {
if(rrddim_flag_check(rd, RRDDIM_FLAG_METADATA_UPDATE)) {
+ (*query_counter)++;
+
rrddim_flag_clear(rd, RRDDIM_FLAG_METADATA_UPDATE);
+ if (rrddim_option_check(rd, RRDDIM_OPTION_HIDDEN))
+ rrddim_flag_set(rd, RRDDIM_FLAG_META_HIDDEN);
+ else
+ rrddim_flag_clear(rd, RRDDIM_FLAG_META_HIDDEN);
+
rc = sql_store_dimension(
&rd->metric_uuid,
&rd->rrdset->chart_uuid,
@@ -990,52 +960,119 @@ static bool metadata_scan_host(RRDHOST *host, uint32_t max_count) {
// Worker thread to scan hosts for pending metadata to store
static void start_metadata_hosts(uv_work_t *req __maybe_unused)
{
+ register_libuv_worker_jobs();
+
RRDHOST *host;
struct scan_metadata_payload *data = req->data;
struct metadata_wc *wc = data->wc;
+ usec_t all_started_ut = now_monotonic_usec(); (void)all_started_ut;
+ internal_error(true, "METADATA: checking all hosts...");
+
bool run_again = false;
+ worker_is_busy(UV_EVENT_METADATA_STORE);
+
+ if (!data->max_count)
+ db_execute("BEGIN TRANSACTION;");
dfe_start_reentrant(rrdhost_root_index, host) {
if (rrdhost_flag_check(host, RRDHOST_FLAG_ARCHIVED) || !rrdhost_flag_check(host, RRDHOST_FLAG_METADATA_UPDATE))
continue;
- internal_error(true, "METADATA: Scanning host %s", rrdhost_hostname(host));
+
+ size_t query_counter = 0; (void)query_counter;
+ usec_t started_ut = now_monotonic_usec(); (void)started_ut;
+
rrdhost_flag_clear(host,RRDHOST_FLAG_METADATA_UPDATE);
- if (unlikely(metadata_scan_host(host, data->max_count))) {
+
+ if (unlikely(rrdhost_flag_check(host, RRDHOST_FLAG_METADATA_LABELS))) {
+ rrdhost_flag_clear(host, RRDHOST_FLAG_METADATA_LABELS);
+ int rc = exec_statement_with_uuid(SQL_DELETE_HOST_LABELS, &host->host_uuid);
+ if (likely(rc == SQLITE_OK)) {
+ BUFFER *work_buffer = buffer_create(1024, &netdata_buffers_statistics.buffers_sqlite);
+ struct query_build tmp = {.sql = work_buffer, .count = 0};
+ uuid_unparse_lower(host->host_uuid, tmp.uuid_str);
+ rrdlabels_walkthrough_read(host->rrdlabels, host_label_store_to_sql_callback, &tmp);
+ db_execute(buffer_tostring(work_buffer));
+ buffer_free(work_buffer);
+ query_counter++;
+ }
+ }
+
+ if (unlikely(rrdhost_flag_check(host, RRDHOST_FLAG_METADATA_CLAIMID))) {
+ rrdhost_flag_clear(host, RRDHOST_FLAG_METADATA_CLAIMID);
+ uuid_t uuid;
+
+ if (likely(host->aclk_state.claimed_id && !uuid_parse(host->aclk_state.claimed_id, uuid)))
+ store_claim_id(&host->host_uuid, &uuid);
+ else
+ store_claim_id(&host->host_uuid, NULL);
+
+ query_counter++;
+ }
+
+ if (unlikely(rrdhost_flag_check(host, RRDHOST_FLAG_METADATA_INFO))) {
+ rrdhost_flag_clear(host, RRDHOST_FLAG_METADATA_INFO);
+
+ BUFFER *work_buffer = sql_store_host_system_info(host);
+ if(work_buffer) {
+ db_execute(buffer_tostring(work_buffer));
+ buffer_free(work_buffer);
+ query_counter++;
+ }
+
+ int rc = sql_store_host_info(host);
+ if (unlikely(rc))
+ error_report("METADATA: 'host:%s': failed to store host info", string2str(host->hostname));
+ else
+ query_counter++;
+ }
+
+ if (data->max_count)
+ db_execute("BEGIN TRANSACTION;");
+ if (unlikely(metadata_scan_host(host, data->max_count, &query_counter))) {
run_again = true;
rrdhost_flag_set(host,RRDHOST_FLAG_METADATA_UPDATE);
- internal_error(true,"METADATA: Rescheduling host %s to run; more charts to store", rrdhost_hostname(host));
+ internal_error(true,"METADATA: 'host:%s': scheduling another run, more charts to store", rrdhost_hostname(host));
}
+ if (data->max_count)
+ db_execute("COMMIT TRANSACTION;");
+
+ usec_t ended_ut = now_monotonic_usec(); (void)ended_ut;
+ internal_error(true, "METADATA: 'host:%s': saved metadata with %zu SQL statements, in %0.2f ms",
+ rrdhost_hostname(host), query_counter,
+ (double)(ended_ut - started_ut) / USEC_PER_MS);
}
dfe_done(host);
+ if (!data->max_count)
+ db_execute("COMMIT TRANSACTION;");
+
+ usec_t all_ended_ut = now_monotonic_usec(); (void)all_ended_ut;
+ internal_error(true, "METADATA: checking all hosts completed in %0.2f ms",
+ (double)(all_ended_ut - all_started_ut) / USEC_PER_MS);
+
if (unlikely(run_again))
wc->check_hosts_after = now_realtime_sec() + METADATA_HOST_CHECK_IMMEDIATE;
else
wc->check_hosts_after = now_realtime_sec() + METADATA_HOST_CHECK_INTERVAL;
+ worker_is_idle();
}
static void metadata_event_loop(void *arg)
{
+ service_register(SERVICE_THREAD_TYPE_EVENT_LOOP, NULL, NULL, NULL, true);
worker_register("METASYNC");
worker_register_job_name(METADATA_DATABASE_NOOP, "noop");
worker_register_job_name(METADATA_DATABASE_TIMER, "timer");
- worker_register_job_name(METADATA_ADD_CHART, "add chart");
- worker_register_job_name(METADATA_ADD_CHART_LABEL, "add chart label");
- worker_register_job_name(METADATA_ADD_DIMENSION, "add dimension");
worker_register_job_name(METADATA_DEL_DIMENSION, "delete dimension");
- worker_register_job_name(METADATA_ADD_DIMENSION_OPTION, "dimension option");
- worker_register_job_name(METADATA_ADD_HOST_SYSTEM_INFO, "host system info");
- worker_register_job_name(METADATA_ADD_HOST_INFO, "host info");
worker_register_job_name(METADATA_STORE_CLAIM_ID, "add claim id");
- worker_register_job_name(METADATA_STORE_HOST_LABELS, "host labels");
+ worker_register_job_name(METADATA_ADD_HOST_INFO, "add host info");
worker_register_job_name(METADATA_MAINTENANCE, "maintenance");
-
int ret;
uv_loop_t *loop;
unsigned cmd_batch_size;
struct metadata_wc *wc = arg;
- enum metadata_opcode opcode, next_opcode;
+ enum metadata_opcode opcode;
uv_work_t metadata_cleanup_worker;
uv_thread_set_name_np(wc->thread, "METASYNC");
@@ -1073,20 +1110,12 @@ static void metadata_event_loop(void *arg)
wc->check_hosts_after = now_realtime_sec() + METADATA_HOST_CHECK_FIRST_CHECK;
int shutdown = 0;
- int in_transaction = 0;
- int commands_in_transaction = 0;
- // This can be used in the event loop for all opcodes (not workers)
- BUFFER *work_buffer = buffer_create(1024);
wc->row_id = 0;
completion_mark_complete(&wc->init_complete);
while (shutdown == 0 || (wc->flags & METADATA_WORKER_BUSY)) {
- RRDDIM *rd = NULL;
- RRDSET *st = NULL;
- RRDHOST *host = NULL;
- DICTIONARY_ITEM *dict_item = NULL;
- BUFFER *buffer = NULL;
uuid_t *uuid;
+ RRDHOST *host = NULL;
int rc;
worker_is_idle();
@@ -1098,7 +1127,7 @@ static void metadata_event_loop(void *arg)
if (unlikely(cmd_batch_size >= METADATA_MAX_BATCH_SIZE))
break;
- cmd = metadata_deq_cmd(wc, &next_opcode);
+ cmd = metadata_deq_cmd(wc);
opcode = cmd.opcode;
if (unlikely(opcode == METADATA_DATABASE_NOOP && metadata_flag_check(wc, METADATA_FLAG_SHUTDOWN))) {
@@ -1108,130 +1137,38 @@ static void metadata_event_loop(void *arg)
++cmd_batch_size;
- // If we are not in transaction and this command is the same with the next ; start a transaction
- if (!in_transaction && opcode < METADATA_SKIP_TRANSACTION && opcode == next_opcode) {
- if (opcode != METADATA_DATABASE_NOOP) {
- in_transaction = 1;
- db_execute("BEGIN TRANSACTION;");
- }
- }
-
- if (likely(in_transaction)) {
- commands_in_transaction++;
- }
-
if (likely(opcode != METADATA_DATABASE_NOOP))
- worker_is_busy(opcode);
+ worker_is_busy(opcode);
switch (opcode) {
case METADATA_DATABASE_NOOP:
case METADATA_DATABASE_TIMER:
break;
- case METADATA_ADD_CHART:
- dict_item = (DICTIONARY_ITEM * ) cmd.param[0];
- st = (RRDSET *) dictionary_acquired_item_value(dict_item);
-
- rc = sql_store_chart(
- &st->chart_uuid,
- &st->rrdhost->host_uuid,
- string2str(st->parts.type),
- string2str(st->parts.id),
- string2str(st->parts.name),
- rrdset_family(st),
- rrdset_context(st),
- rrdset_title(st),
- rrdset_units(st),
- rrdset_plugin_name(st),
- rrdset_module_name(st),
- st->priority,
- st->update_every,
- st->chart_type,
- st->rrd_memory_mode,
- st->entries);
-
- if (unlikely(rc))
- error_report("Failed to store chart %s", rrdset_id(st));
-
- dictionary_acquired_item_release(st->rrdhost->rrdset_root_index, dict_item);
- break;
- case METADATA_ADD_CHART_LABEL:
- dict_item = (DICTIONARY_ITEM * ) cmd.param[0];
- st = (RRDSET *) dictionary_acquired_item_value(dict_item);
- check_and_update_chart_labels(st, work_buffer);
- dictionary_acquired_item_release(st->rrdhost->rrdset_root_index, dict_item);
- break;
- case METADATA_ADD_DIMENSION:
- dict_item = (DICTIONARY_ITEM * ) cmd.param[0];
- rd = (RRDDIM *) dictionary_acquired_item_value(dict_item);
-
- rc = sql_store_dimension(
- &rd->metric_uuid,
- &rd->rrdset->chart_uuid,
- string2str(rd->id),
- string2str(rd->name),
- rd->multiplier,
- rd->divisor,
- rd->algorithm,
- rrddim_option_check(rd, RRDDIM_OPTION_HIDDEN));
- if (unlikely(rc))
- error_report("Failed to store dimension %s", rrddim_id(rd));
-
- dictionary_acquired_item_release(rd->rrdset->rrddim_root_index, dict_item);
- break;
case METADATA_DEL_DIMENSION:
uuid = (uuid_t *) cmd.param[0];
if (likely(dimension_can_be_deleted(uuid)))
delete_dimension_uuid(uuid);
freez(uuid);
break;
- case METADATA_ADD_DIMENSION_OPTION:
- dict_item = (DICTIONARY_ITEM * ) cmd.param[0];
- rd = (RRDDIM *) dictionary_acquired_item_value(dict_item);
- rc = sql_set_dimension_option(
- &rd->metric_uuid, rrddim_flag_check(rd, RRDDIM_FLAG_META_HIDDEN) ? "hidden" : NULL);
- if (unlikely(rc))
- error_report("Failed to store dimension option for %s", string2str(rd->id));
- dictionary_acquired_item_release(rd->rrdset->rrddim_root_index, dict_item);
- break;
- case METADATA_ADD_HOST_SYSTEM_INFO:
- buffer = (BUFFER *) cmd.param[0];
- db_execute(buffer_tostring(buffer));
- buffer_free(buffer);
- break;
- case METADATA_ADD_HOST_INFO:
- dict_item = (DICTIONARY_ITEM * ) cmd.param[0];
- host = (RRDHOST *) dictionary_acquired_item_value(dict_item);
- rc = sql_store_host_info(host);
- if (unlikely(rc))
- error_report("Failed to store host info in the database for %s", string2str(host->hostname));
- dictionary_acquired_item_release(rrdhost_root_index, dict_item);
- break;
case METADATA_STORE_CLAIM_ID:
store_claim_id((uuid_t *) cmd.param[0], (uuid_t *) cmd.param[1]);
freez((void *) cmd.param[0]);
freez((void *) cmd.param[1]);
break;
- case METADATA_STORE_HOST_LABELS:
- dict_item = (DICTIONARY_ITEM * ) cmd.param[0];
- host = (RRDHOST *) dictionary_acquired_item_value(dict_item);
- rc = exec_statement_with_uuid(SQL_DELETE_HOST_LABELS, &host->host_uuid);
-
- if (likely(rc == SQLITE_OK)) {
- buffer_flush(work_buffer);
- struct query_build tmp = {.sql = work_buffer, .count = 0};
- uuid_unparse_lower(host->host_uuid, tmp.uuid_str);
- rrdlabels_walkthrough_read(host->rrdlabels, host_label_store_to_sql_callback, &tmp);
- db_execute(buffer_tostring(work_buffer));
- }
-
- dictionary_acquired_item_release(rrdhost_root_index, dict_item);
+ case METADATA_ADD_HOST_INFO:
+ host = (RRDHOST *) cmd.param[0];
+ rc = sql_store_host_info(host);
+ if (unlikely(rc))
+ error_report("Failed to store host info in the database for %s", string2str(host->hostname));
break;
-
case METADATA_SCAN_HOSTS:
if (unlikely(metadata_flag_check(wc, METADATA_FLAG_SCANNING_HOSTS)))
break;
+ if (unittest_running)
+ break;
+
struct scan_metadata_payload *data = mallocz(sizeof(*data));
data->request.data = data;
data->wc = wc;
@@ -1242,7 +1179,7 @@ static void metadata_event_loop(void *arg)
cmd.completion = NULL; // Do not complete after launching worker (worker will do)
}
else
- data->max_count = 1000;
+ data->max_count = 5000;
metadata_flag_set(wc, METADATA_FLAG_SCANNING_HOSTS);
if (unlikely(
@@ -1255,11 +1192,6 @@ static void metadata_event_loop(void *arg)
metadata_flag_clear(wc, METADATA_FLAG_SCANNING_HOSTS);
}
break;
- case METADATA_STORE_BUFFER:
- buffer = (BUFFER *) cmd.param[0];
- db_execute(buffer_tostring(buffer));
- buffer_free(buffer);
- break;
case METADATA_MAINTENANCE:
if (unlikely(metadata_flag_check(wc, METADATA_FLAG_CLEANUP)))
break;
@@ -1279,11 +1211,6 @@ static void metadata_event_loop(void *arg)
default:
break;
}
- if (in_transaction && (commands_in_transaction >= METADATA_MAX_TRANSACTION_BATCH || opcode != next_opcode)) {
- in_transaction = 0;
- db_execute("COMMIT TRANSACTION;");
- commands_in_transaction = 0;
- }
if (cmd.completion)
completion_mark_complete(cmd.completion);
@@ -1302,8 +1229,6 @@ static void metadata_event_loop(void *arg)
uv_run(loop, UV_RUN_DEFAULT);
uv_cond_destroy(&wc->cmd_cond);
- /* uv_mutex_destroy(&wc->cmd_mutex); */
- //fatal_assert(0 == uv_loop_close(loop));
int rc;
do {
@@ -1313,7 +1238,6 @@ static void metadata_event_loop(void *arg)
freez(loop);
worker_unregister();
- buffer_free(work_buffer);
info("METADATA: Shutting down event loop");
completion_mark_complete(&wc->init_complete);
return;
@@ -1408,50 +1332,6 @@ static inline void queue_metadata_cmd(enum metadata_opcode opcode, const void *p
}
// Public
-void metaqueue_chart_update(RRDSET *st)
-{
- const DICTIONARY_ITEM *acquired_st = dictionary_get_and_acquire_item(st->rrdhost->rrdset_root_index, string2str(st->id));
- queue_metadata_cmd(METADATA_ADD_CHART, acquired_st, NULL);
-}
-
-//
-// RD may not be collected, so we may store it needlessly
-void metaqueue_dimension_update(RRDDIM *rd)
-{
- const DICTIONARY_ITEM *acquired_rd =
- dictionary_get_and_acquire_item(rd->rrdset->rrddim_root_index, string2str(rd->id));
-
- if (unlikely(rrdset_flag_check(rd->rrdset, RRDSET_FLAG_METADATA_UPDATE))) {
- metaqueue_chart_update(rd->rrdset);
- rrdset_flag_clear(rd->rrdset, RRDSET_FLAG_METADATA_UPDATE);
- }
-
- queue_metadata_cmd(METADATA_ADD_DIMENSION, acquired_rd, NULL);
-}
-
-void metaqueue_dimension_update_flags(RRDDIM *rd)
-{
- const DICTIONARY_ITEM *acquired_rd =
- dictionary_get_and_acquire_item(rd->rrdset->rrddim_root_index, string2str(rd->id));
- queue_metadata_cmd(METADATA_ADD_DIMENSION_OPTION, acquired_rd, NULL);
-}
-
-void metaqueue_host_update_system_info(RRDHOST *host)
-{
- BUFFER *work_buffer = sql_store_host_system_info(host);
-
- if (unlikely(!work_buffer))
- return;
-
- queue_metadata_cmd(METADATA_ADD_HOST_SYSTEM_INFO, work_buffer, NULL);
-}
-
-void metaqueue_host_update_info(const char *machine_guid)
-{
- const DICTIONARY_ITEM *acquired_host = dictionary_get_and_acquire_item(rrdhost_root_index, machine_guid);
- queue_metadata_cmd(METADATA_ADD_HOST_INFO, acquired_host, NULL);
-}
-
void metaqueue_delete_dimension_uuid(uuid_t *uuid)
{
if (unlikely(!metasync_worker.loop))
@@ -1477,24 +1357,13 @@ void metaqueue_store_claim_id(uuid_t *host_uuid, uuid_t *claim_uuid)
queue_metadata_cmd(METADATA_STORE_CLAIM_ID, local_host_uuid, local_claim_uuid);
}
-void metaqueue_store_host_labels(const char *machine_guid)
+void metaqueue_host_update_info(RRDHOST *host)
{
- const DICTIONARY_ITEM *acquired_host = dictionary_get_and_acquire_item(rrdhost_root_index, machine_guid);
- queue_metadata_cmd(METADATA_STORE_HOST_LABELS, acquired_host, NULL);
-}
-
-void metaqueue_buffer(BUFFER *buffer)
-{
- queue_metadata_cmd(METADATA_STORE_BUFFER, buffer, NULL);
-}
-
-void metaqueue_chart_labels(RRDSET *st)
-{
- const DICTIONARY_ITEM *acquired_st = dictionary_get_and_acquire_item(st->rrdhost->rrdset_root_index, string2str(st->id));
- queue_metadata_cmd(METADATA_ADD_CHART_LABEL, acquired_st, NULL);
+ if (unlikely(!metasync_worker.loop))
+ return;
+ queue_metadata_cmd(METADATA_ADD_HOST_INFO, host, NULL);
}
-
//
// unitests
//
@@ -1542,7 +1411,7 @@ static void *metadata_unittest_threads(void)
tu.join = 0;
for (int i = 0; i < threads_to_create; i++) {
char buf[100 + 1];
- snprintf(buf, 100, "meta%d", i);
+ snprintf(buf, 100, "META[%d]", i);
netdata_thread_create(
&threads[i],
buf,
@@ -1558,7 +1427,6 @@ static void *metadata_unittest_threads(void)
void *retval;
netdata_thread_join(threads[i], &retval);
}
-// uv_async_send(&metasync_worker.async);
sleep_usec(5 * USEC_PER_SEC);
fprintf(stderr, "Added %u elements, processed %u\n", tu.added, tu.processed);
diff --git a/database/sqlite/sqlite_metadata.h b/database/sqlite/sqlite_metadata.h
index 9293facf..d578b7a8 100644
--- a/database/sqlite/sqlite_metadata.h
+++ b/database/sqlite/sqlite_metadata.h
@@ -11,17 +11,10 @@ void metadata_sync_init(void);
void metadata_sync_shutdown(void);
void metadata_sync_shutdown_prepare(void);
-void metaqueue_dimension_update(RRDDIM *rd);
-void metaqueue_chart_update(RRDSET *st);
-void metaqueue_dimension_update_flags(RRDDIM *rd);
-void metaqueue_host_update_system_info(RRDHOST *host);
-void metaqueue_host_update_info(const char *machine_guid);
void metaqueue_delete_dimension_uuid(uuid_t *uuid);
void metaqueue_store_claim_id(uuid_t *host_uuid, uuid_t *claim_uuid);
-void metaqueue_store_host_labels(const char *machine_guid);
-void metaqueue_chart_labels(RRDSET *st);
+void metaqueue_host_update_info(RRDHOST *host);
void migrate_localhost(uuid_t *host_uuid);
-void metaqueue_buffer(BUFFER *buffer);
// UNIT TEST
int metadata_unittest(void);
diff --git a/database/storage_engine.c b/database/storage_engine.c
index edf017db..c5ba8655 100644
--- a/database/storage_engine.c
+++ b/database/storage_engine.c
@@ -6,23 +6,24 @@
#include "engine/rrdengineapi.h"
#endif
-#define im_collect_ops { \
- .init = rrddim_collect_init,\
- .store_metric = rrddim_collect_store_metric,\
- .flush = rrddim_store_metric_flush,\
- .finalize = rrddim_collect_finalize, \
+#define im_collect_ops { \
+ .init = rrddim_collect_init, \
+ .store_metric = rrddim_collect_store_metric, \
+ .flush = rrddim_store_metric_flush, \
+ .finalize = rrddim_collect_finalize, \
.change_collection_frequency = rrddim_store_metric_change_collection_frequency, \
- .metrics_group_get = rrddim_metrics_group_get, \
- .metrics_group_release = rrddim_metrics_group_release, \
+ .metrics_group_get = rrddim_metrics_group_get, \
+ .metrics_group_release = rrddim_metrics_group_release, \
}
-#define im_query_ops { \
- .init = rrddim_query_init, \
- .next_metric = rrddim_query_next_metric, \
- .is_finished = rrddim_query_is_finished, \
- .finalize = rrddim_query_finalize, \
- .latest_time = rrddim_query_latest_time, \
- .oldest_time = rrddim_query_oldest_time \
+#define im_query_ops { \
+ .init = rrddim_query_init, \
+ .next_metric = rrddim_query_next_metric, \
+ .is_finished = rrddim_query_is_finished, \
+ .finalize = rrddim_query_finalize, \
+ .latest_time_s = rrddim_query_latest_time_s, \
+ .oldest_time_s = rrddim_query_oldest_time_s, \
+ .align_to_optimal_before = rrddim_query_align_to_optimal_before, \
}
static STORAGE_ENGINE engines[] = {
@@ -34,8 +35,9 @@ static STORAGE_ENGINE engines[] = {
.metric_get_or_create = rrddim_metric_get_or_create,
.metric_dup = rrddim_metric_dup,
.metric_release = rrddim_metric_release,
+ .metric_retention_by_uuid = rrddim_metric_retention_by_uuid,
.collect_ops = im_collect_ops,
- .query_ops = im_query_ops
+ .query_ops = im_query_ops,
}
},
{
@@ -46,8 +48,9 @@ static STORAGE_ENGINE engines[] = {
.metric_get_or_create = rrddim_metric_get_or_create,
.metric_dup = rrddim_metric_dup,
.metric_release = rrddim_metric_release,
+ .metric_retention_by_uuid = rrddim_metric_retention_by_uuid,
.collect_ops = im_collect_ops,
- .query_ops = im_query_ops
+ .query_ops = im_query_ops,
}
},
{
@@ -58,8 +61,9 @@ static STORAGE_ENGINE engines[] = {
.metric_get_or_create = rrddim_metric_get_or_create,
.metric_dup = rrddim_metric_dup,
.metric_release = rrddim_metric_release,
+ .metric_retention_by_uuid = rrddim_metric_retention_by_uuid,
.collect_ops = im_collect_ops,
- .query_ops = im_query_ops
+ .query_ops = im_query_ops,
}
},
{
@@ -70,8 +74,9 @@ static STORAGE_ENGINE engines[] = {
.metric_get_or_create = rrddim_metric_get_or_create,
.metric_dup = rrddim_metric_dup,
.metric_release = rrddim_metric_release,
+ .metric_retention_by_uuid = rrddim_metric_retention_by_uuid,
.collect_ops = im_collect_ops,
- .query_ops = im_query_ops
+ .query_ops = im_query_ops,
}
},
{
@@ -82,8 +87,9 @@ static STORAGE_ENGINE engines[] = {
.metric_get_or_create = rrddim_metric_get_or_create,
.metric_dup = rrddim_metric_dup,
.metric_release = rrddim_metric_release,
+ .metric_retention_by_uuid = rrddim_metric_retention_by_uuid,
.collect_ops = im_collect_ops,
- .query_ops = im_query_ops
+ .query_ops = im_query_ops,
}
},
#ifdef ENABLE_DBENGINE
@@ -95,6 +101,7 @@ static STORAGE_ENGINE engines[] = {
.metric_get_or_create = rrdeng_metric_get_or_create,
.metric_dup = rrdeng_metric_dup,
.metric_release = rrdeng_metric_release,
+ .metric_retention_by_uuid = rrdeng_metric_retention_by_uuid,
.collect_ops = {
.init = rrdeng_store_metric_init,
.store_metric = rrdeng_store_metric_next,
@@ -109,8 +116,9 @@ static STORAGE_ENGINE engines[] = {
.next_metric = rrdeng_load_metric_next,
.is_finished = rrdeng_load_metric_is_finished,
.finalize = rrdeng_load_metric_finalize,
- .latest_time = rrdeng_metric_latest_time,
- .oldest_time = rrdeng_metric_oldest_time
+ .latest_time_s = rrdeng_metric_latest_time,
+ .oldest_time_s = rrdeng_metric_oldest_time,
+ .align_to_optimal_before = rrdeng_load_align_to_optimal_before,
}
}
},
diff --git a/diagrams/Makefile.am b/diagrams/Makefile.am
index 475ca89f..8844034d 100644
--- a/diagrams/Makefile.am
+++ b/diagrams/Makefile.am
@@ -9,7 +9,6 @@ dist_noinst_DATA = \
netdata-proxies-example.xml \
netdata-overview.xml \
data_structures/netdata_config.svg \
- data_structures/README.md \
data_structures/registry.svg \
data_structures/rrd.svg \
data_structures/web.svg \
diff --git a/diagrams/data_structures/README.md b/diagrams/data_structures/README.md
deleted file mode 100644
index 12ea1afa..00000000
--- a/diagrams/data_structures/README.md
+++ /dev/null
@@ -1,18 +0,0 @@
-<!--
-title: "Data structures"
-custom_edit_url: https://github.com/netdata/netdata/edit/master/diagrams/data_structures/README.md
--->
-
-# Data structures
-
-These are the main internal data structures of `netdata`. Created with `draw.io`.
-
-![Config](https://raw.githubusercontent.com/netdata/netdata/master/diagrams/data_structures/netdata_config.svg?sanitize=true)
-
-![Registry](https://raw.githubusercontent.com/netdata/netdata/master/diagrams/data_structures/registry.svg?sanitize=true)
-
-![RRD](https://raw.githubusercontent.com/netdata/netdata/master/diagrams/data_structures/rrd.svg?sanitize=true)
-
-![Web](https://raw.githubusercontent.com/netdata/netdata/master/diagrams/data_structures/web.svg?sanitize=true)
-
-
diff --git a/diagrams/netdata-overview.xml b/diagrams/netdata-overview.xml
index 4d9c3ba3..0db020f6 100644
--- a/diagrams/netdata-overview.xml
+++ b/diagrams/netdata-overview.xml
@@ -1 +1,751 @@
-<mxfile modified="2019-07-10T07:30:20.643Z" host="www.draw.io" agent="Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.80 Safari/537.36" version="10.9.2" etag="JRR-u-yNVbUJwUu5Enbm" type="device"><diagram id="319b92f5-ce0c-3638-1d72-e7e1d323a6f9" name="Page-1">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</diagram></mxfile> \ No newline at end of file
+<?xml version="1.0" encoding="UTF-8"?>
+<mxfile modified="2023-01-13T15:36:30.264Z" host="app.diagrams.net" agent="5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36" etag="dRNEPRCqKHk1SJtCQlLl" version="20.8.5" type="device">
+ <diagram id="319b92f5-ce0c-3638-1d72-e7e1d323a6f9" name="Page-1">
+ <mxGraphModel dx="2074" dy="1116" grid="0" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="850" pageHeight="1100" background="#ffffff" math="0" shadow="0">
+ <root>
+ <mxCell id="0" />
+ <mxCell id="1" parent="0" />
+ <UserObject label="" link="https://github.com/firehol/netdata/wiki/netdata-for-IoT" id="5">
+ <mxCell style="shape=image;verticalLabelPosition=bottom;labelBackgroundColor=#ffffff;verticalAlign=top;aspect=fixed;imageAspect=0;image=https://cloud.githubusercontent.com/assets/2662304/26528606/807278dc-43b8-11e7-9a0f-3122d40a297b.png;" parent="1" vertex="1">
+ <mxGeometry x="1154" y="2050" width="82" height="86" as="geometry" />
+ </mxCell>
+ </UserObject>
+ <UserObject label="" link="https://github.com/netdata/netdata/tree/master/health/notifications/messagebird" id="6">
+ <mxCell style="shape=image;verticalLabelPosition=bottom;labelBackgroundColor=#ffffff;verticalAlign=top;aspect=fixed;imageAspect=0;image=https://cloud.githubusercontent.com/assets/2662304/26528607/80773520-43b8-11e7-8010-5219c75fbe9b.png;" parent="1" vertex="1">
+ <mxGeometry x="576" y="410" width="195" height="38" as="geometry" />
+ </mxCell>
+ </UserObject>
+ <mxCell id="7" value="" style="whiteSpace=wrap;html=1;strokeColor=#59DE9C;fillColor=none;gradientColor=#CCFFFF;fontSize=13;align=center;dashed=1;perimeterSpacing=0;opacity=30;rounded=0;shadow=0;glass=0;comic=0;strokeWidth=3;dashPattern=1 1;" parent="1" vertex="1">
+ <mxGeometry x="70" y="552" width="1171" height="471" as="geometry" />
+ </mxCell>
+ <UserObject label="&lt;font&gt;&lt;b&gt;APM&lt;br&gt;Application Metrics&lt;/b&gt;&lt;/font&gt;&lt;div&gt;statsd protocol&lt;/div&gt;&lt;div&gt;(TCP or UDP)&lt;/div&gt;" link="https://github.com/netdata/netdata/tree/master/collectors/statsd.plugin" id="9">
+ <mxCell style="ellipse;shape=cloud;whiteSpace=wrap;html=1;strokeColor=#59DE9C;fillColor=#FFFFFF;fontSize=14;align=center;gradientColor=#CCFFFF;" parent="1" vertex="1">
+ <mxGeometry x="1022" y="1032" width="208" height="158" as="geometry" />
+ </mxCell>
+ </UserObject>
+ <UserObject label="&lt;b&gt;&lt;font style=&quot;font-size: 16px&quot;&gt;Streaming&lt;/font&gt;&lt;/b&gt;&lt;div&gt;Database Replication&lt;/div&gt;" link="https://github.com/netdata/netdata/tree/master/streaming" id="10">
+ <mxCell style="whiteSpace=wrap;html=1;strokeColor=#59DE9C;fillColor=#FFFFFF;fontSize=14;align=center;" parent="1" vertex="1">
+ <mxGeometry x="1034" y="690" width="202" height="97" as="geometry" />
+ </mxCell>
+ </UserObject>
+ <UserObject label="&lt;div&gt;&lt;b&gt;&lt;font style=&quot;font-size: 14px&quot;&gt;fping.plugin&lt;/font&gt;&lt;/b&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-size: 14px&quot;&gt;network latency&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-size: 14px&quot;&gt;monitoring&lt;/span&gt;&lt;/div&gt;" link="https://learn.netdata.cloud/docs/agent/collectors/fping.plugin" linkTarget="_blank" id="13">
+ <mxCell style="whiteSpace=wrap;html=1;fontSize=12;fillColor=#FFFFFF;strokeColor=#59DE9C;" parent="1" vertex="1">
+ <mxGeometry x="484.33349609375" y="1034" width="110" height="60" as="geometry" />
+ </mxCell>
+ </UserObject>
+ <UserObject label="&lt;div&gt;&lt;b&gt;&lt;font style=&quot;font-size: 14px&quot;&gt;freeipmi.plugin&lt;/font&gt;&lt;/b&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-size: 14px&quot;&gt;enterprise h/w&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-size: 14px&quot;&gt;monitoring&lt;/span&gt;&lt;/div&gt;" link="https://github.com/netdata/netdata/tree/master/collectors/freeipmi.plugin" id="14">
+ <mxCell style="whiteSpace=wrap;html=1;fontSize=12;fillColor=#FFFFFF;strokeColor=#59DE9C;" parent="1" vertex="1">
+ <mxGeometry x="163" y="1034" width="110" height="60" as="geometry" />
+ </mxCell>
+ </UserObject>
+ <UserObject label="&lt;span&gt;To another&lt;/span&gt;&lt;div&gt;netdata&lt;/div&gt;" link="https://github.com/netdata/netdata/tree/master/streaming#options-for-the-sending-node" id="15">
+ <mxCell style="ellipse;shape=cloud;whiteSpace=wrap;html=1;strokeColor=#59DE9C;fillColor=#FFFFFF;fontSize=14;align=center;" parent="1" vertex="1">
+ <mxGeometry x="1090" y="557" width="150" height="120" as="geometry" />
+ </mxCell>
+ </UserObject>
+ <mxCell id="21" value="" style="whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeColor=#59DE9C;" parent="1" vertex="1">
+ <mxGeometry x="113" y="1147" width="110" height="60" as="geometry" />
+ </mxCell>
+ <mxCell id="22" value="" style="whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeColor=#59DE9C;" parent="1" vertex="1">
+ <mxGeometry x="103" y="1137" width="110" height="60" as="geometry" />
+ </mxCell>
+ <mxCell id="23" value="" style="whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeColor=#59DE9C;" parent="1" vertex="1">
+ <mxGeometry x="264" y="1137" width="110" height="60" as="geometry" />
+ </mxCell>
+ <mxCell id="24" value="" style="whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeColor=#59DE9C;" parent="1" vertex="1">
+ <mxGeometry x="444" y="1159" width="110" height="60" as="geometry" />
+ </mxCell>
+ <mxCell id="25" value="" style="whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeColor=#59DE9C;" parent="1" vertex="1">
+ <mxGeometry x="434" y="1149" width="110" height="60" as="geometry" />
+ </mxCell>
+ <mxCell id="26" value="" style="whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeColor=#59DE9C;" parent="1" vertex="1">
+ <mxGeometry x="424" y="1139" width="110" height="60" as="geometry" />
+ </mxCell>
+ <mxCell id="27" value="" style="whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeColor=#59DE9C;" parent="1" vertex="1">
+ <mxGeometry x="414" y="1129" width="110" height="60" as="geometry" />
+ </mxCell>
+ <mxCell id="28" value="" style="whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeColor=#59DE9C;" parent="1" vertex="1">
+ <mxGeometry x="95" y="1125" width="110" height="60" as="geometry" />
+ </mxCell>
+ <mxCell id="29" value="" style="whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeColor=#59DE9C;" parent="1" vertex="1">
+ <mxGeometry x="254" y="1126" width="110" height="60" as="geometry" />
+ </mxCell>
+ <mxCell id="30" value="" style="whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeColor=#59DE9C;" parent="1" vertex="1">
+ <mxGeometry x="674" y="887" width="557" height="130" as="geometry" />
+ </mxCell>
+ <UserObject label="&lt;font style=&quot;font-size: 20px&quot;&gt;metrics database&lt;/font&gt;&lt;div&gt;&lt;span style=&quot;font-weight: normal&quot;&gt;&lt;font style=&quot;font-size: 15px&quot;&gt;(organized in hosts, charts and dimensions)&lt;/font&gt;&lt;/span&gt;&lt;/div&gt;" link="https://github.com/netdata/netdata/tree/master/database#netdata-database" id="31">
+ <mxCell style="whiteSpace=wrap;html=1;fontSize=18;fontStyle=1;fillColor=#FFFFFF;strokeColor=#59DE9C;" parent="1" vertex="1">
+ <mxGeometry x="254" y="690" width="770" height="93" as="geometry" />
+ </mxCell>
+ </UserObject>
+ <UserObject label="&lt;div&gt;&lt;font style=&quot;font-size: 16px&quot;&gt;&lt;b&gt;plugins.d&lt;/b&gt;&lt;/font&gt;&lt;/div&gt;&lt;font style=&quot;font-size: 16px&quot;&gt;&lt;b&gt;external plugins API&lt;/b&gt;&lt;/font&gt;&lt;div&gt;&lt;font style=&quot;font-size: 14px&quot;&gt;text format, over pipes&lt;/font&gt;&lt;/div&gt;" link="https://github.com/netdata/netdata/tree/master/collectors/plugins.d" id="32">
+ <mxCell style="whiteSpace=wrap;html=1;fontSize=16;fillColor=#FFFFFF;strokeColor=#59DE9C;" parent="1" vertex="1">
+ <mxGeometry x="74" y="887" width="580" height="130" as="geometry" />
+ </mxCell>
+ </UserObject>
+ <UserObject label="&lt;font style=&quot;font-size: 18px&quot;&gt;&lt;b&gt;internal&amp;nbsp;&lt;/b&gt;&lt;b&gt;data collection API&lt;/b&gt;&lt;/font&gt;&lt;div&gt;&lt;font style=&quot;font-size: 16px&quot;&gt;(with add-only access to the database)&lt;/font&gt;&lt;/div&gt;" link="https://github.com/netdata/netdata/tree/master/collectors#internal-plugins-api" id="33">
+ <mxCell style="whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeColor=#59DE9C;" parent="1" vertex="1">
+ <mxGeometry x="74" y="796" width="1160" height="77" as="geometry" />
+ </mxCell>
+ </UserObject>
+ <UserObject label="&lt;font style=&quot;font-size: 16px&quot;&gt;&lt;b&gt;Linux&lt;/b&gt;&lt;/font&gt;&lt;div&gt;&lt;span style=&quot;font-size: 14px&quot;&gt;data collection&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-size: 14px&quot;&gt;(mainly /proc and /sys)&lt;/span&gt;&lt;/div&gt;" link="https://github.com/netdata/netdata/tree/master/collectors/proc.plugin#procplugin" id="34">
+ <mxCell style="whiteSpace=wrap;html=1;strokeColor=#59DE9C;fillColor=#FFFFFF;" parent="1" vertex="1">
+ <mxGeometry x="684" y="897" width="177" height="60" as="geometry" />
+ </mxCell>
+ </UserObject>
+ <UserObject label="&lt;div&gt;&lt;b&gt;&lt;font style=&quot;font-size: 16px&quot;&gt;FreeBSD&lt;/font&gt;&lt;/b&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-size: 14px&quot;&gt;data collection&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-size: 14px&quot;&gt;(mainly sysctl)&lt;/span&gt;&lt;/div&gt;" link="https://github.com/netdata/netdata/tree/master/collectors/freebsd.plugin" id="35">
+ <mxCell style="whiteSpace=wrap;html=1;strokeColor=#59DE9C;fillColor=#FFFFFF;" parent="1" vertex="1">
+ <mxGeometry x="871" y="897" width="110" height="60" as="geometry" />
+ </mxCell>
+ </UserObject>
+ <UserObject label="&lt;div&gt;&lt;b&gt;&lt;font style=&quot;font-size: 16px&quot;&gt;MacOS&lt;/font&gt;&lt;/b&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-size: 14px&quot;&gt;data collection&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-size: 14px&quot;&gt;(mainly sysctl)&lt;/span&gt;&lt;/div&gt;" link="https://github.com/netdata/netdata/tree/master/collectors/macos.plugin" id="36">
+ <mxCell style="whiteSpace=wrap;html=1;strokeColor=#59DE9C;fillColor=#FFFFFF;" parent="1" vertex="1">
+ <mxGeometry x="991" y="897" width="110" height="60" as="geometry" />
+ </mxCell>
+ </UserObject>
+ <mxCell id="37" value="" style="html=1;shadow=0;dashed=0;align=center;verticalAlign=middle;shape=mxgraph.arrows2.arrow;dy=0.51;dx=19.57;direction=north;notch=0;fillColor=#fff2cc;strokeColor=#d6b656;gradientColor=#ffd966;" parent="1" vertex="1">
+ <mxGeometry x="369" y="863" width="70" height="30" as="geometry" />
+ </mxCell>
+ <mxCell id="38" value="&lt;div style=&quot;text-align: center&quot;&gt;&lt;span&gt;&lt;b&gt;&lt;font style=&quot;font-size: 16px&quot;&gt;netdata internal plugins&lt;/font&gt;&lt;/b&gt;&lt;/span&gt;&lt;/div&gt;&lt;div style=&quot;text-align: left&quot;&gt;(running as threads inside netdata)&lt;/div&gt;" style="text;html=1;resizable=0;points=[];autosize=1;align=center;verticalAlign=top;spacingTop=-4;fontSize=14;" parent="1" vertex="1">
+ <mxGeometry x="883" y="967" width="230" height="40" as="geometry" />
+ </mxCell>
+ <UserObject label="&lt;div&gt;&lt;b&gt;&lt;font style=&quot;font-size: 20px&quot;&gt;statsd&lt;/font&gt;&lt;/b&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-size: 14px&quot;&gt;(udp/tcp)&lt;/span&gt;&lt;/div&gt;" link="https://github.com/netdata/netdata/tree/master/collectors/statsd.plugin" id="39">
+ <mxCell style="whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeColor=#59DE9C;" parent="1" vertex="1">
+ <mxGeometry x="1111" y="897" width="110" height="110" as="geometry" />
+ </mxCell>
+ </UserObject>
+ <UserObject label="&lt;b&gt;&lt;font style=&quot;font-size: 14px&quot;&gt;charts.d&lt;/font&gt;&lt;/b&gt;&lt;div&gt;&lt;span style=&quot;font-size: 14px&quot;&gt;(bash plugins)&lt;/span&gt;&lt;/div&gt;" link="https://github.com/netdata/netdata/tree/master/collectors/charts.d.plugin" id="40">
+ <mxCell style="whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeColor=#59DE9C;" parent="1" vertex="1">
+ <mxGeometry x="85" y="1115" width="110" height="60" as="geometry" />
+ </mxCell>
+ </UserObject>
+ <UserObject label="&lt;div&gt;&lt;b&gt;&lt;font style=&quot;font-size: 14px&quot;&gt;go.d&lt;/font&gt;&lt;/b&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-size: 14px&quot;&gt;(go plugins)&lt;/span&gt;&lt;/div&gt;" link="https://learn.netdata.cloud/docs/agent/collectors/go.d.plugin" linkTarget="_blank" id="41">
+ <mxCell style="whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeColor=#59DE9C;" parent="1" vertex="1">
+ <mxGeometry x="244" y="1116" width="110" height="60" as="geometry" />
+ </mxCell>
+ </UserObject>
+ <UserObject label="&lt;div&gt;&lt;b&gt;&lt;font style=&quot;font-size: 14px&quot;&gt;python.d&lt;/font&gt;&lt;/b&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-size: 14px&quot;&gt;(python plugins)&lt;/span&gt;&lt;/div&gt;" link="https://github.com/netdata/netdata/tree/master/collectors/python.d.plugin" id="42">
+ <mxCell style="whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeColor=#59DE9C;" parent="1" vertex="1">
+ <mxGeometry x="404" y="1119" width="110" height="60" as="geometry" />
+ </mxCell>
+ </UserObject>
+ <UserObject label="&lt;b&gt;remote netdata servers&lt;/b&gt;&lt;div&gt;database replication, streaming metrics in realtime&lt;br&gt;over TCP&lt;/div&gt;" link="https://github.com/netdata/netdata/tree/master/streaming" id="43">
+ <mxCell style="whiteSpace=wrap;html=1;fontSize=16;fillColor=#FFFFFF;strokeColor=#59DE9C;" parent="1" vertex="1">
+ <mxGeometry x="706" y="1200" width="420" height="90" as="geometry" />
+ </mxCell>
+ </UserObject>
+ <mxCell id="44" value="" style="html=1;shadow=0;dashed=0;align=center;verticalAlign=middle;shape=mxgraph.arrows2.jumpInArrow;dy=15.79;dx=26.57;arrowHead=55;fontSize=16;direction=north;fillColor=#59DE9C;strokeColor=none;" parent="1" vertex="1">
+ <mxGeometry x="600" y="1007" width="130" height="223" as="geometry" />
+ </mxCell>
+ <mxCell id="45" value="" style="html=1;shadow=0;dashed=0;align=center;verticalAlign=middle;shape=mxgraph.arrows2.jumpInArrow;dy=15.21;dx=32.57;arrowHead=55;fontSize=16;direction=north;flipH=1;fillColor=#59DE9C;strokeColor=none;" parent="1" vertex="1">
+ <mxGeometry x="1150" y="997" width="75" height="93" as="geometry" />
+ </mxCell>
+ <UserObject label="&lt;div&gt;&lt;b&gt;&lt;font style=&quot;font-size: 16px&quot;&gt;cgroups&lt;/font&gt;&lt;/b&gt;&lt;/div&gt;" link="https://github.com/netdata/netdata/tree/master/collectors/cgroups.plugin#cgroupsplugin" id="46">
+ <mxCell style="whiteSpace=wrap;html=1;strokeColor=#59DE9C;fillColor=#FFFFFF;" parent="1" vertex="1">
+ <mxGeometry x="774" y="957" width="87" height="53" as="geometry" />
+ </mxCell>
+ </UserObject>
+ <mxCell id="47" value="" style="html=1;shadow=0;dashed=0;align=center;verticalAlign=middle;shape=mxgraph.arrows2.arrow;dy=0.51;dx=19.57;direction=north;notch=0;fillColor=#fff2cc;strokeColor=#d6b656;gradientColor=#ffd966;" parent="1" vertex="1">
+ <mxGeometry x="739" y="863" width="70" height="30" as="geometry" />
+ </mxCell>
+ <mxCell id="48" value="" style="html=1;shadow=0;dashed=0;align=center;verticalAlign=middle;shape=mxgraph.arrows2.arrow;dy=0.51;dx=19.57;direction=north;notch=0;fillColor=#fff2cc;strokeColor=#d6b656;gradientColor=#ffd966;" parent="1" vertex="1">
+ <mxGeometry x="891" y="863" width="70" height="30" as="geometry" />
+ </mxCell>
+ <mxCell id="49" value="" style="html=1;shadow=0;dashed=0;align=center;verticalAlign=middle;shape=mxgraph.arrows2.arrow;dy=0.51;dx=19.57;direction=north;notch=0;fillColor=#fff2cc;strokeColor=#d6b656;gradientColor=#ffd966;" parent="1" vertex="1">
+ <mxGeometry x="1011" y="863" width="70" height="30" as="geometry" />
+ </mxCell>
+ <mxCell id="54" value="" style="html=1;shadow=0;dashed=0;align=center;verticalAlign=middle;shape=mxgraph.arrows2.arrow;dy=0.51;dx=19.57;direction=north;notch=0;fillColor=#59DE9C;strokeColor=none;" parent="1" vertex="1">
+ <mxGeometry x="504" y="1007" width="70" height="30" as="geometry" />
+ </mxCell>
+ <mxCell id="55" value="" style="html=1;shadow=0;dashed=0;align=center;verticalAlign=middle;shape=mxgraph.arrows2.arrow;dy=0.51;dx=19.57;direction=north;notch=8.29;fillColor=#d5e8d4;strokeColor=#82b366;gradientColor=#97d077;" parent="1" vertex="1">
+ <mxGeometry x="264" y="1007" width="70" height="120" as="geometry" />
+ </mxCell>
+ <UserObject label="&lt;div&gt;&lt;b&gt;&lt;font style=&quot;font-size: 14px&quot;&gt;apps.plugin&lt;/font&gt;&lt;/b&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-size: 14px&quot;&gt;process tree&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-size: 14px&quot;&gt;monitoring&lt;/span&gt;&lt;/div&gt;" link="https://github.com/netdata/netdata/tree/master/collectors/apps.plugin" id="56">
+ <mxCell style="whiteSpace=wrap;html=1;fontSize=12;fillColor=#FFFFFF;strokeColor=#59DE9C;" parent="1" vertex="1">
+ <mxGeometry x="324" y="1034" width="110" height="60" as="geometry" />
+ </mxCell>
+ </UserObject>
+ <mxCell id="57" value="" style="html=1;shadow=0;dashed=0;align=center;verticalAlign=middle;shape=mxgraph.arrows2.arrow;dy=0.51;dx=19.57;direction=north;notch=0;fillColor=#59DE9C;strokeColor=none;" parent="1" vertex="1">
+ <mxGeometry x="344" y="1007" width="70" height="30" as="geometry" />
+ </mxCell>
+ <mxCell id="58" value="" style="html=1;shadow=0;dashed=0;align=center;verticalAlign=middle;shape=mxgraph.arrows2.arrow;dy=0.51;dx=19.57;direction=north;notch=0;fillColor=#59DE9C;strokeColor=none;" parent="1" vertex="1">
+ <mxGeometry x="424" y="1007" width="70" height="120" as="geometry" />
+ </mxCell>
+ <UserObject label="&lt;span style=&quot;font-size: 14px&quot;&gt;&lt;b&gt;QoS&lt;/b&gt;&lt;/span&gt;&lt;div&gt;&lt;span style=&quot;font-size: 14px&quot;&gt;(tc classes)&lt;/span&gt;&lt;/div&gt;" link="https://github.com/netdata/netdata/tree/master/collectors/tc.plugin#tcplugin" id="59">
+ <mxCell style="whiteSpace=wrap;html=1;strokeColor=#59DE9C;fillColor=#FFFFFF;" parent="1" vertex="1">
+ <mxGeometry x="684" y="957" width="80" height="53" as="geometry" />
+ </mxCell>
+ </UserObject>
+ <mxCell id="60" value="" style="html=1;shadow=0;dashed=0;align=center;verticalAlign=middle;shape=mxgraph.arrows2.arrow;dy=0.51;dx=19.57;direction=north;notch=5.29;fillColor=#d5e8d4;strokeColor=#82b366;gradientColor=#97d077;" parent="1" vertex="1">
+ <mxGeometry x="183" y="1007" width="70" height="30" as="geometry" />
+ </mxCell>
+ <UserObject label="&lt;b&gt;&lt;font style=&quot;font-size: 16px&quot;&gt;Exporting&lt;/font&gt;&lt;/b&gt;&lt;div&gt;Metrics Archiving&lt;/div&gt;" link="https://learn.netdata.cloud/docs/agent/exporting" linkTarget="_blank" id="73">
+ <mxCell style="whiteSpace=wrap;html=1;strokeColor=#59DE9C;fillColor=#FFFFFF;fontSize=14;align=center;" parent="1" vertex="1">
+ <mxGeometry x="870" y="607" width="149.75" height="70" as="geometry" />
+ </mxCell>
+ </UserObject>
+ <UserObject label="&lt;div&gt;&lt;b&gt;&lt;font style=&quot;font-size: 20px&quot;&gt;Alarms&lt;/font&gt;&lt;/b&gt;&lt;/div&gt;Health Monitoring" link="https://learn.netdata.cloud/docs/agent/health" linkTarget="_blank" id="74">
+ <mxCell style="whiteSpace=wrap;html=1;strokeColor=#59DE9C;fillColor=#FFFFFF;fontSize=14;align=center;" parent="1" vertex="1">
+ <mxGeometry x="650" y="607" width="211" height="70" as="geometry" />
+ </mxCell>
+ </UserObject>
+ <UserObject label="&lt;b&gt;&lt;font style=&quot;font-size: 16px&quot;&gt;HTTP API&lt;/font&gt;&lt;/b&gt;" link="https://github.com/netdata/netdata/tree/master/web/api" id="76">
+ <mxCell style="whiteSpace=wrap;html=1;strokeColor=#59DE9C;fillColor=#FFFFFF;fontSize=14;align=center;" parent="1" vertex="1">
+ <mxGeometry x="74" y="557" width="800" height="40" as="geometry" />
+ </mxCell>
+ </UserObject>
+ <UserObject label="&lt;b&gt;&lt;font style=&quot;font-size: 16px&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; Notifications&lt;/font&gt;&lt;/b&gt;" link="https://github.com/netdata/netdata/tree/master/health/notifications" id="77">
+ <mxCell style="whiteSpace=wrap;html=1;strokeColor=#59DE9C;fillColor=#FFFFFF;fontSize=14;align=center;" parent="1" vertex="1">
+ <mxGeometry x="675" y="470" width="163" height="77" as="geometry" />
+ </mxCell>
+ </UserObject>
+ <UserObject label="" link="https://learn.netdata.cloud/docs/agent/exporting/graphite" linkTarget="_blank" id="79">
+ <mxCell style="shape=image;verticalLabelPosition=bottom;labelBackgroundColor=#ffffff;verticalAlign=top;aspect=fixed;imageAspect=0;image=https://cloud.githubusercontent.com/assets/2662304/26529200/52a30280-43c4-11e7-85c6-b88321a49d61.png;" parent="1" vertex="1">
+ <mxGeometry x="900" y="307" width="179" height="47" as="geometry" />
+ </mxCell>
+ </UserObject>
+ <UserObject label="" link="https://learn.netdata.cloud/docs/agent/exporting/opentsdb" linkTarget="_blank" id="80">
+ <mxCell style="shape=image;imageAspect=0;aspect=fixed;verticalLabelPosition=bottom;verticalAlign=top;image=https://cloud.githubusercontent.com/assets/2662304/26528624/814fd1aa-43b8-11e7-9125-5e5c3c677aef.png;" parent="1" vertex="1">
+ <mxGeometry x="1000" y="423" width="198" height="41" as="geometry" />
+ </mxCell>
+ </UserObject>
+ <UserObject label="" link="https://learn.netdata.cloud/docs/agent/exporting" linkTarget="_blank" id="81">
+ <mxCell style="shape=image;verticalLabelPosition=bottom;labelBackgroundColor=#ffffff;verticalAlign=top;aspect=fixed;imageAspect=0;image=https://cloud.githubusercontent.com/assets/2662304/26528632/81c29fdc-43b8-11e7-8bfe-fc832d11b72d.png;" parent="1" vertex="1">
+ <mxGeometry x="1126" y="360" width="109" height="50" as="geometry" />
+ </mxCell>
+ </UserObject>
+ <UserObject label="" link="https://learn.netdata.cloud/docs/agent/exporting" linkTarget="_blank" id="85">
+ <mxCell style="shape=image;verticalLabelPosition=bottom;labelBackgroundColor=#ffffff;verticalAlign=top;aspect=fixed;imageAspect=0;image=https://cloud.githubusercontent.com/assets/2662304/26528636/81e18712-43b8-11e7-94b9-37ea1cf6c3a6.png;" parent="1" vertex="1">
+ <mxGeometry x="1106" y="300" width="114" height="40" as="geometry" />
+ </mxCell>
+ </UserObject>
+ <UserObject label="netdata&lt;div&gt;&lt;b&gt;&lt;font style=&quot;font-size: 16px&quot;&gt;registry&lt;/font&gt;&lt;/b&gt;&lt;/div&gt;&lt;div&gt;&lt;font style=&quot;font-size: 14px&quot;&gt;my-netdata menu&lt;/font&gt;&lt;/div&gt;" link="https://github.com/netdata/netdata/tree/master/registry#netdata-registry" id="89">
+ <mxCell style="whiteSpace=wrap;html=1;strokeColor=#59DE9C;fillColor=#FFFFFF;fontSize=14;align=center;" parent="1" vertex="1">
+ <mxGeometry x="74" y="607" width="170" height="176" as="geometry" />
+ </mxCell>
+ </UserObject>
+ <mxCell id="90" value="" style="html=1;shadow=0;dashed=0;align=center;verticalAlign=middle;shape=mxgraph.arrows2.twoWayArrow;dy=0.52;dx=21.1;strokeColor=#FFFFFF;fillColor=#59DE9C;fontSize=14;direction=south;" parent="1" vertex="1">
+ <mxGeometry x="133" y="577" width="60" height="50" as="geometry" />
+ </mxCell>
+ <mxCell id="93" value="" style="html=1;shadow=0;dashed=0;align=center;verticalAlign=middle;shape=mxgraph.arrows2.arrow;dy=0.51;dx=19.57;direction=north;notch=8.29;fillColor=#d5e8d4;strokeColor=#82b366;gradientColor=#97d077;" parent="1" vertex="1">
+ <mxGeometry x="103" y="1006" width="70" height="120" as="geometry" />
+ </mxCell>
+ <mxCell id="94" value="" style="shape=curlyBracket;whiteSpace=wrap;html=1;rounded=1;strokeColor=#59DE9C;fillColor=#ffffff;gradientColor=#FFF2F9;fontSize=14;align=center;direction=south;" parent="1" vertex="1">
+ <mxGeometry x="85" y="1293" width="1159" height="20" as="geometry" />
+ </mxCell>
+ <mxCell id="97" value="headless" style="text;html=1;resizable=0;points=[];autosize=1;align=center;verticalAlign=top;spacingTop=-4;fontSize=14;" parent="1" vertex="1">
+ <mxGeometry x="1106" y="802.5" width="70" height="20" as="geometry" />
+ </mxCell>
+ <mxCell id="98" value="store &amp;amp;&lt;div&gt;forward&lt;/div&gt;" style="text;html=1;resizable=0;points=[];autosize=1;align=center;verticalAlign=top;spacingTop=-4;fontSize=14;" parent="1" vertex="1">
+ <mxGeometry x="953" y="721" width="60" height="30" as="geometry" />
+ </mxCell>
+ <UserObject label="&lt;span&gt;from another&lt;/span&gt;&lt;div&gt;netdata&lt;/div&gt;" link="https://github.com/netdata/netdata/tree/master/streaming#options-for-the-receiving-node" id="99">
+ <mxCell style="ellipse;shape=cloud;whiteSpace=wrap;html=1;strokeColor=#59DE9C;fillColor=#FFFFFF;fontSize=14;align=center;" parent="1" vertex="1">
+ <mxGeometry x="1083" y="1184" width="150" height="120" as="geometry" />
+ </mxCell>
+ </UserObject>
+ <mxCell id="100" value="" style="curved=1;endArrow=block;html=1;fontSize=14;entryX=0.847;entryY=0.256;entryPerimeter=0;startArrow=oval;startFill=1;endFill=1;dashed=1;strokeWidth=3;fillColor=#f5f5f5;strokeColor=#59DE9C;dashPattern=1 1;endSize=8;exitX=0.8;exitY=0.8;exitPerimeter=0;" parent="1" source="15" target="99" edge="1">
+ <mxGeometry width="50" height="50" relative="1" as="geometry">
+ <mxPoint x="1239" y="641" as="sourcePoint" />
+ <mxPoint x="1364" y="727" as="targetPoint" />
+ <Array as="points">
+ <mxPoint x="1264" y="660" />
+ <mxPoint x="1254" y="900" />
+ <mxPoint x="1254" y="1110" />
+ </Array>
+ </mxGeometry>
+ </mxCell>
+ <UserObject label="" link="https://github.com/netdata/netdata/tree/master/health/notifications/slack" id="101">
+ <mxCell style="shape=image;verticalLabelPosition=bottom;labelBackgroundColor=#ffffff;verticalAlign=top;aspect=fixed;imageAspect=0;image=https://cloud.githubusercontent.com/assets/2662304/26529341/ebcbc5d0-43c6-11e7-9056-df89e0c10e6a.png;" parent="1" vertex="1">
+ <mxGeometry x="530" y="302" width="162" height="48" as="geometry" />
+ </mxCell>
+ </UserObject>
+ <UserObject label="" link="https://github.com/netdata/netdata/tree/master/health/notifications/pushbullet" id="102">
+ <mxCell style="shape=image;verticalLabelPosition=bottom;labelBackgroundColor=#ffffff;verticalAlign=top;aspect=fixed;imageAspect=0;image=https://cloud.githubusercontent.com/assets/2662304/26528627/81813ef2-43b8-11e7-9371-9dfcfb1f27f8.png;" parent="1" vertex="1">
+ <mxGeometry x="553" y="363" width="188" height="32" as="geometry" />
+ </mxCell>
+ </UserObject>
+ <UserObject label="" link="https://github.com/netdata/netdata/tree/master/health/notifications/discord" id="103">
+ <mxCell style="shape=image;imageAspect=0;aspect=fixed;verticalLabelPosition=bottom;verticalAlign=top;image=https://cloud.githubusercontent.com/assets/2662304/26528628/8195a284-43b8-11e7-8582-e94e53feb1c7.png;" parent="1" vertex="1">
+ <mxGeometry x="440" y="227" width="146" height="40" as="geometry" />
+ </mxCell>
+ </UserObject>
+ <UserObject label="" link="https://github.com/netdata/netdata/tree/master/health/notifications/pagerduty" id="104">
+ <mxCell style="shape=image;imageAspect=0;aspect=fixed;verticalLabelPosition=bottom;verticalAlign=top;image=https://cloud.githubusercontent.com/assets/2662304/26528630/81a3e33a-43b8-11e7-89a0-53c9d89ef2d6.png;" parent="1" vertex="1">
+ <mxGeometry x="500" y="188" width="140" height="26" as="geometry" />
+ </mxCell>
+ </UserObject>
+ <UserObject label="" link="https://github.com/netdata/netdata/tree/master/health/notifications/telegram" id="105">
+ <mxCell style="shape=image;imageAspect=0;aspect=fixed;verticalLabelPosition=bottom;verticalAlign=top;image=https://cloud.githubusercontent.com/assets/2662304/26529349/256417f2-43c7-11e7-8036-9aa59efe0605.png;" parent="1" vertex="1">
+ <mxGeometry x="452" y="363" width="77" height="83" as="geometry" />
+ </mxCell>
+ </UserObject>
+ <UserObject label="" link="https://github.com/netdata/netdata/tree/master/health/notifications/twilio" id="106">
+ <mxCell style="shape=image;imageAspect=0;aspect=fixed;verticalLabelPosition=bottom;verticalAlign=top;image=https://cloud.githubusercontent.com/assets/2662304/26528631/81c0f9b6-43b8-11e7-9088-77d895f50346.png;" parent="1" vertex="1">
+ <mxGeometry x="600" y="250" width="134" height="40" as="geometry" />
+ </mxCell>
+ </UserObject>
+ <mxCell id="107" value="" style="html=1;shadow=0;dashed=0;align=center;verticalAlign=middle;shape=mxgraph.arrows2.jumpInArrow;dy=15;dx=38;arrowHead=55;strokeColor=none;fillColor=#59DE9C;fontSize=14;flipH=1;direction=east;" parent="1" vertex="1">
+ <mxGeometry x="710" y="300" width="100" height="190" as="geometry" />
+ </mxCell>
+ <mxCell id="109" value="" style="shape=image;html=1;verticalAlign=top;verticalLabelPosition=bottom;labelBackgroundColor=#ffffff;imageAspect=0;aspect=fixed;image=https://cloud.githubusercontent.com/assets/2662304/26528634/81caae16-43b8-11e7-9c68-45b69b1e92ab.png;strokeColor=#6A9153;fillColor=#CCFFFF;gradientColor=#CCFFFF;fontSize=20;align=center;" parent="1" vertex="1">
+ <mxGeometry x="285" y="709" width="48" height="48" as="geometry" />
+ </mxCell>
+ <mxCell id="110" value="" style="shadow=0;dashed=0;html=1;strokeColor=none;labelPosition=center;verticalLabelPosition=bottom;verticalAlign=top;shape=mxgraph.mscae.cloud.api_management;fillColor=#0079D6;fontSize=20;align=center;" parent="1" vertex="1">
+ <mxGeometry x="91" y="562" width="35" height="30" as="geometry" />
+ </mxCell>
+ <UserObject label="&lt;div style=&quot;text-align: right&quot;&gt;&lt;b&gt;&lt;font style=&quot;font-size: 24px&quot;&gt;netdata&lt;/font&gt;&lt;/b&gt;&lt;/div&gt;&lt;div style=&quot;text-align: right&quot;&gt;simple. effective. awesome!&lt;/div&gt;&lt;div style=&quot;text-align: right&quot;&gt;&lt;b&gt;https://netdata.cloud&lt;/b&gt;&lt;/div&gt;&lt;div style=&quot;text-align: right&quot;&gt;&lt;span style=&quot;font-size: 14px&quot;&gt;&lt;br&gt;(C) Copyright 2023&lt;br&gt;&lt;/span&gt;&lt;span style=&quot;font-size: 14px&quot;&gt;Netdata, Inc.&lt;/span&gt;&lt;/div&gt;" link="https://netdata.cloud" id="111">
+ <mxCell style="text;html=1;resizable=0;points=[];autosize=1;align=left;verticalAlign=top;spacingTop=-4;fontSize=20;" parent="1" vertex="1">
+ <mxGeometry x="994" y="1763.666748046875" width="270" height="160" as="geometry" />
+ </mxCell>
+ </UserObject>
+ <mxCell id="112" value="&lt;font style=&quot;font-size: 26px&quot;&gt;high level feature set and architecture of &lt;b&gt;netdata&lt;/b&gt;&lt;/font&gt;&lt;div&gt;&lt;span&gt;the open-source, real-time, performance and health monitoring&lt;/span&gt;&lt;/div&gt;" style="text;html=1;resizable=0;points=[];autosize=1;align=center;verticalAlign=top;spacingTop=-4;fontSize=20;" parent="1" vertex="1">
+ <mxGeometry x="374" y="20.42" width="580" height="50" as="geometry" />
+ </mxCell>
+ <mxCell id="113" value="" style="endArrow=none;html=1;dashed=1;dashPattern=1 1;strokeColor=#59DE9C;strokeWidth=3;fontSize=20;" parent="1" edge="1">
+ <mxGeometry width="50" height="50" relative="1" as="geometry">
+ <mxPoint x="73" y="1754" as="sourcePoint" />
+ <mxPoint x="1254" y="1754" as="targetPoint" />
+ </mxGeometry>
+ </mxCell>
+ <mxCell id="114" value="" style="shadow=0;dashed=0;html=1;strokeColor=none;fillColor=#0079D6;labelPosition=center;verticalLabelPosition=bottom;verticalAlign=top;shape=mxgraph.azure.health_monitoring;fontSize=20;align=center;" parent="1" vertex="1">
+ <mxGeometry x="659" y="630" width="35" height="30" as="geometry" />
+ </mxCell>
+ <mxCell id="115" value="" style="shape=image;html=1;verticalAlign=top;verticalLabelPosition=bottom;labelBackgroundColor=#ffffff;imageAspect=0;aspect=fixed;image=https://cloud.githubusercontent.com/assets/2662304/26528633/81c95b24-43b8-11e7-808d-651a09e9ecca.png;strokeColor=#6A9153;fillColor=#CCFFFF;gradientColor=#CCFFFF;fontSize=20;align=center;" parent="1" vertex="1">
+ <mxGeometry x="685" y="490" width="31" height="29" as="geometry" />
+ </mxCell>
+ <mxCell id="116" value="" style="shadow=0;dashed=0;html=1;strokeColor=none;labelPosition=center;verticalLabelPosition=bottom;verticalAlign=top;shape=mxgraph.office.users.users;fillColor=#2072B8;fontSize=20;align=center;" parent="1" vertex="1">
+ <mxGeometry x="85" y="735" width="46" height="41" as="geometry" />
+ </mxCell>
+ <mxCell id="117" value="" style="shadow=0;dashed=0;html=1;strokeColor=none;fillColor=#505050;labelPosition=center;verticalLabelPosition=bottom;verticalAlign=top;shape=mxgraph.office.servers.server_farm;fillColor=#DA4026;fontSize=20;align=center;" parent="1" vertex="1">
+ <mxGeometry x="194" y="737" width="39" height="39" as="geometry" />
+ </mxCell>
+ <mxCell id="118" value="" style="shape=image;html=1;verticalAlign=top;verticalLabelPosition=bottom;labelBackgroundColor=#ffffff;imageAspect=0;aspect=fixed;image=https://cloud.githubusercontent.com/assets/2662304/26528635/81d4c720-43b8-11e7-8578-00c9ed60c219.png;strokeColor=#6A9153;fillColor=#CCFFFF;gradientColor=#CCFFFF;fontSize=20;align=center;" parent="1" vertex="1">
+ <mxGeometry x="1054" y="704" width="35" height="35" as="geometry" />
+ </mxCell>
+ <mxCell id="119" value="" style="shape=image;html=1;verticalAlign=top;verticalLabelPosition=bottom;labelBackgroundColor=#ffffff;imageAspect=0;aspect=fixed;image=https://cloud.githubusercontent.com/assets/2662304/26528645/8275abfe-43b8-11e7-9d21-306ab844f7bb.png;strokeColor=#6A9153;fillColor=#CCFFFF;gradientColor=#CCFFFF;fontSize=20;align=center;" parent="1" vertex="1">
+ <mxGeometry x="127" y="933" width="61" height="39" as="geometry" />
+ </mxCell>
+ <mxCell id="121" value="&lt;div&gt;netdata is a &lt;b&gt;monitoring agent&lt;/b&gt;: you install it on all your systems:&lt;/div&gt;&lt;div&gt;&amp;nbsp;- supports &lt;b&gt;auto-detection&lt;/b&gt;&amp;nbsp;and &lt;b&gt;zero configuration&lt;/b&gt;&amp;nbsp;for most applications and systems&lt;/div&gt;&amp;nbsp;- is &lt;b&gt;real-time&lt;/b&gt;: every metric is on your dashboard in less than 1-second (collection to visualization)&lt;div&gt;&amp;nbsp;- is &lt;b&gt;fast&lt;/b&gt;: for a few thousand metrics per second, it needs &amp;lt; 1% CPU of a single core&lt;/div&gt;&lt;div&gt;&amp;nbsp;- and&amp;nbsp;&lt;b&gt;efficient&lt;/b&gt;: it needs a few MB of RAM and no disk I/O at all while it runs&lt;/div&gt;&lt;div&gt;&amp;nbsp;- also, it is&amp;nbsp;&lt;b&gt;embeddable&lt;/b&gt;, &lt;b&gt;extensible&lt;/b&gt;, and&amp;nbsp;&lt;b&gt;open-source&lt;/b&gt;&amp;nbsp;(GPL v3+)&lt;/div&gt;&lt;div&gt;&lt;br&gt;&lt;/div&gt;&lt;div&gt;...and netdata runs everywhere:&lt;/div&gt;" style="text;html=1;resizable=0;points=[];autosize=1;align=left;verticalAlign=top;spacingTop=-4;fontSize=18;" parent="1" vertex="1">
+ <mxGeometry x="73" y="1774" width="800" height="170" as="geometry" />
+ </mxCell>
+ <mxCell id="133" value="" style="shape=image;verticalLabelPosition=bottom;labelBackgroundColor=#ffffff;verticalAlign=top;aspect=fixed;imageAspect=0;image=https://cloud.githubusercontent.com/assets/2662304/26528641/82468914-43b8-11e7-8018-af01bd67cba2.png;" parent="1" vertex="1">
+ <mxGeometry x="357" y="1960" width="92" height="73" as="geometry" />
+ </mxCell>
+ <mxCell id="134" value="" style="shape=image;verticalLabelPosition=bottom;labelBackgroundColor=#ffffff;verticalAlign=top;aspect=fixed;imageAspect=0;image=https://cloud.githubusercontent.com/assets/2662304/26528642/82562b62-43b8-11e7-85f5-31c7f70e057b.png;" parent="1" vertex="1">
+ <mxGeometry x="464" y="1965" width="76" height="69" as="geometry" />
+ </mxCell>
+ <mxCell id="135" value="" style="shape=image;imageAspect=0;aspect=fixed;verticalLabelPosition=bottom;verticalAlign=top;image=https://cloud.githubusercontent.com/assets/2662304/26528647/82db5e7c-43b8-11e7-837e-b2de7adbb878.png;" parent="1" vertex="1">
+ <mxGeometry x="556" y="1959" width="89" height="76" as="geometry" />
+ </mxCell>
+ <mxCell id="137" value="" style="shape=image;verticalLabelPosition=bottom;labelBackgroundColor=#ffffff;verticalAlign=top;aspect=fixed;imageAspect=0;image=https://cloud.githubusercontent.com/assets/2662304/26529330/b4c0293c-43c6-11e7-9751-b66fad47604a.png;" parent="1" vertex="1">
+ <mxGeometry x="188" y="2052" width="93" height="78" as="geometry" />
+ </mxCell>
+ <mxCell id="138" value="" style="shape=image;verticalLabelPosition=bottom;labelBackgroundColor=#ffffff;verticalAlign=top;aspect=fixed;imageAspect=0;image=https://cloud.githubusercontent.com/assets/2662304/26528649/82f60f6a-43b8-11e7-8d7e-aab838249843.png;" parent="1" vertex="1">
+ <mxGeometry x="871" y="1973" width="95" height="60" as="geometry" />
+ </mxCell>
+ <mxCell id="139" value="" style="shape=image;verticalLabelPosition=bottom;labelBackgroundColor=#ffffff;verticalAlign=top;aspect=fixed;imageAspect=0;image=https://cloud.githubusercontent.com/assets/2662304/26528650/8311b94a-43b8-11e7-8475-6096e0ab7025.png;" parent="1" vertex="1">
+ <mxGeometry x="994" y="1973" width="119" height="55" as="geometry" />
+ </mxCell>
+ <mxCell id="140" value="" style="shape=image;verticalLabelPosition=bottom;labelBackgroundColor=#ffffff;verticalAlign=top;aspect=fixed;imageAspect=0;image=https://cloud.githubusercontent.com/assets/2662304/26528651/832e5f1e-43b8-11e7-8ba2-19ce72f932ba.png;" parent="1" vertex="1">
+ <mxGeometry x="80" y="1963" width="94" height="70" as="geometry" />
+ </mxCell>
+ <mxCell id="141" value="" style="shape=image;verticalLabelPosition=bottom;labelBackgroundColor=#ffffff;verticalAlign=top;aspect=fixed;imageAspect=0;image=https://cloud.githubusercontent.com/assets/2662304/26528648/82de5cbc-43b8-11e7-841f-a226ba8e5c59.png;" parent="1" vertex="1">
+ <mxGeometry x="195" y="1958.5" width="60" height="78" as="geometry" />
+ </mxCell>
+ <mxCell id="142" value="" style="shape=image;verticalLabelPosition=bottom;labelBackgroundColor=#ffffff;verticalAlign=top;aspect=fixed;imageAspect=0;image=https://cloud.githubusercontent.com/assets/2662304/26528654/8395aee4-43b8-11e7-9b0b-87fe2ac5811c.png;" parent="1" vertex="1">
+ <mxGeometry x="659" y="1958" width="93" height="80" as="geometry" />
+ </mxCell>
+ <mxCell id="143" value="" style="shape=image;verticalLabelPosition=bottom;labelBackgroundColor=#ffffff;verticalAlign=top;aspect=fixed;imageAspect=0;image=https://cloud.githubusercontent.com/assets/2662304/26528655/83a954d0-43b8-11e7-8551-fbce651f9d4f.png;" parent="1" vertex="1">
+ <mxGeometry x="761.5" y="1955" width="87" height="79" as="geometry" />
+ </mxCell>
+ <mxCell id="144" value="" style="shape=image;verticalLabelPosition=bottom;labelBackgroundColor=#ffffff;verticalAlign=top;aspect=fixed;imageAspect=0;image=https://cloud.githubusercontent.com/assets/2662304/26528656/83aef5a2-43b8-11e7-8cbc-543e0b739f1e.png;" parent="1" vertex="1">
+ <mxGeometry x="1010" y="2100" width="136" height="34" as="geometry" />
+ </mxCell>
+ <UserObject label="" link="https://github.com/firehol/netdata/wiki/netdata-for-IoT" id="145">
+ <mxCell style="shape=image;imageAspect=0;aspect=fixed;verticalLabelPosition=bottom;verticalAlign=top;image=https://cloud.githubusercontent.com/assets/2662304/26528652/835acc70-43b8-11e7-9b04-0e355fe46b45.png;" parent="1" vertex="1">
+ <mxGeometry x="1003.5" y="2053" width="135" height="31" as="geometry" />
+ </mxCell>
+ </UserObject>
+ <mxCell id="147" value="" style="shape=image;verticalLabelPosition=bottom;labelBackgroundColor=#ffffff;verticalAlign=top;aspect=fixed;imageAspect=0;image=https://cloud.githubusercontent.com/assets/2662304/26528658/83cc4c60-43b8-11e7-9260-ed6d5f03b1f4.png;" parent="1" vertex="1">
+ <mxGeometry x="602.5" y="2054" width="63" height="83" as="geometry" />
+ </mxCell>
+ <mxCell id="148" value="" style="shape=image;verticalLabelPosition=bottom;labelBackgroundColor=#ffffff;verticalAlign=top;aspect=fixed;imageAspect=0;image=https://cloud.githubusercontent.com/assets/2662304/26528657/83b9bece-43b8-11e7-95cd-17fa93d37a46.png;" parent="1" vertex="1">
+ <mxGeometry x="952.5" y="1053.5" width="72" height="66" as="geometry" />
+ </mxCell>
+ <mxCell id="149" value="" style="endArrow=block;dashed=1;html=1;dashPattern=1 1;fontFamily=Helvetica;fontSize=13;fontColor=#000000;align=center;strokeColor=#59DE9C;strokeWidth=3;startArrow=none;startFill=0;endFill=1;exitX=0.5;exitY=0;" parent="1" source="148" target="35" edge="1">
+ <mxGeometry width="50" height="50" relative="1" as="geometry">
+ <mxPoint x="884" y="1057" as="sourcePoint" />
+ <mxPoint x="934" y="1007" as="targetPoint" />
+ <Array as="points">
+ <mxPoint x="884" y="1000" />
+ </Array>
+ </mxGeometry>
+ </mxCell>
+ <mxCell id="150" value="" style="endArrow=none;dashed=1;html=1;dashPattern=1 1;strokeColor=#59DE9C;strokeWidth=3;fontSize=13;entryX=0.5;entryY=0;exitX=1;exitY=0.75;startArrow=block;startFill=1;" parent="1" source="34" target="148" edge="1">
+ <mxGeometry width="50" height="50" relative="1" as="geometry">
+ <mxPoint x="794" y="1147" as="sourcePoint" />
+ <mxPoint x="844" y="1097" as="targetPoint" />
+ <Array as="points">
+ <mxPoint x="874" y="1000" />
+ </Array>
+ </mxGeometry>
+ </mxCell>
+ <mxCell id="152" value="" style="shape=image;imageAspect=0;aspect=fixed;verticalLabelPosition=bottom;verticalAlign=top;image=https://cloud.githubusercontent.com/assets/2662304/26528664/84510b62-43b8-11e7-990b-6bc09e5ea649.png;" parent="1" vertex="1">
+ <mxGeometry x="394.5" y="2061" width="99" height="71" as="geometry" />
+ </mxCell>
+ <mxCell id="153" value="" style="shape=image;imageAspect=0;aspect=fixed;verticalLabelPosition=bottom;verticalAlign=top;image=https://cloud.githubusercontent.com/assets/2662304/26529313/70be8382-43c6-11e7-943b-2c0ec46cc782.png;" parent="1" vertex="1">
+ <mxGeometry x="81" y="2080" width="95" height="52" as="geometry" />
+ </mxCell>
+ <mxCell id="154" value="" style="shape=image;verticalLabelPosition=bottom;labelBackgroundColor=#ffffff;verticalAlign=top;aspect=fixed;imageAspect=0;image=https://cloud.githubusercontent.com/assets/2662304/26528662/84277932-43b8-11e7-9322-db0e415d9d04.png;" parent="1" vertex="1">
+ <mxGeometry x="684.5" y="2053" width="63" height="83" as="geometry" />
+ </mxCell>
+ <mxCell id="155" value="" style="shape=image;verticalLabelPosition=bottom;labelBackgroundColor=#ffffff;verticalAlign=top;aspect=fixed;imageAspect=0;image=https://cloud.githubusercontent.com/assets/2662304/26528667/848aff48-43b8-11e7-85ec-1b904e34246e.png;" parent="1" vertex="1">
+ <mxGeometry x="499.5" y="2050" width="89" height="91" as="geometry" />
+ </mxCell>
+ <mxCell id="156" value="" style="shape=image;verticalLabelPosition=bottom;labelBackgroundColor=#ffffff;verticalAlign=top;aspect=fixed;imageAspect=0;image=https://cloud.githubusercontent.com/assets/2662304/26528660/83f71b8e-43b8-11e7-9240-f443470a9aa9.png;" parent="1" vertex="1">
+ <mxGeometry x="299" y="2055" width="86" height="77" as="geometry" />
+ </mxCell>
+ <mxCell id="157" value="" style="shape=image;verticalLabelPosition=bottom;labelBackgroundColor=#ffffff;verticalAlign=top;aspect=fixed;imageAspect=0;image=https://cloud.githubusercontent.com/assets/2662304/26528661/840ec356-43b8-11e7-8011-f8260539cf2d.png;" parent="1" vertex="1">
+ <mxGeometry x="786" y="1896" width="160" height="35" as="geometry" />
+ </mxCell>
+ <mxCell id="158" value="" style="shape=image;verticalLabelPosition=bottom;labelBackgroundColor=#ffffff;verticalAlign=top;aspect=fixed;imageAspect=0;image=https://cloud.githubusercontent.com/assets/2662304/26528663/842f7c18-43b8-11e7-9ff1-a76f1cefd321.png;" parent="1" vertex="1">
+ <mxGeometry x="970" y="1896" width="132" height="37" as="geometry" />
+ </mxCell>
+ <mxCell id="159" value="" style="shape=image;verticalLabelPosition=bottom;labelBackgroundColor=#ffffff;verticalAlign=top;aspect=fixed;imageAspect=0;image=https://cloud.githubusercontent.com/assets/2662304/26528666/845b707a-43b8-11e7-9260-6594db9f8b12.png;" parent="1" vertex="1">
+ <mxGeometry x="673.5" y="1871" width="101" height="76" as="geometry" />
+ </mxCell>
+ <mxCell id="160" value="" style="shape=image;verticalLabelPosition=bottom;labelBackgroundColor=#ffffff;verticalAlign=top;aspect=fixed;imageAspect=0;image=https://cloud.githubusercontent.com/assets/2662304/26528665/845b0fcc-43b8-11e7-8955-89043524e863.png;" parent="1" vertex="1">
+ <mxGeometry x="759" y="2075" width="88" height="60" as="geometry" />
+ </mxCell>
+ <mxCell id="161" value="" style="shape=image;imageAspect=0;aspect=fixed;verticalLabelPosition=bottom;verticalAlign=top;image=https://cloud.githubusercontent.com/assets/2662304/26529042/23528fa8-43c1-11e7-94bd-d01e7ded928d.png;" parent="1" vertex="1">
+ <mxGeometry x="1134" y="1991" width="116" height="40" as="geometry" />
+ </mxCell>
+ <UserObject label="&lt;font style=&quot;font-size: 20px&quot;&gt;system&lt;u&gt;d&lt;/u&gt;&lt;/font&gt;" link="https://github.com/netdata/netdata/tree/master/collectors/cgroups.plugin#monitoring-systemd-services" id="162">
+ <mxCell style="text;html=1;resizable=0;points=[];autosize=1;align=left;verticalAlign=top;spacingTop=-4;fontSize=13;fontStyle=1" parent="1" vertex="1">
+ <mxGeometry x="797" y="1145" width="90" height="20" as="geometry" />
+ </mxCell>
+ </UserObject>
+ <mxCell id="163" value="" style="endArrow=none;dashed=1;html=1;dashPattern=1 1;strokeColor=#C7D1E0;strokeWidth=3;fontSize=13;entryX=0.421;entryY=0.191;startArrow=block;startFill=1;entryPerimeter=0;" parent="1" source="46" target="162" edge="1">
+ <mxGeometry width="50" height="50" relative="1" as="geometry">
+ <mxPoint x="871" y="952" as="sourcePoint" />
+ <mxPoint x="909" y="1059" as="targetPoint" />
+ </mxGeometry>
+ </mxCell>
+ <mxCell id="164" value="" style="shape=image;verticalLabelPosition=bottom;labelBackgroundColor=#ffffff;verticalAlign=top;aspect=fixed;imageAspect=0;image=https://cloud.githubusercontent.com/assets/2662304/26529330/b4c0293c-43c6-11e7-9751-b66fad47604a.png;" parent="1" vertex="1">
+ <mxGeometry x="688" y="1083.5" width="55" height="46" as="geometry" />
+ </mxCell>
+ <mxCell id="165" value="" style="shape=image;imageAspect=0;aspect=fixed;verticalLabelPosition=bottom;verticalAlign=top;image=https://cloud.githubusercontent.com/assets/2662304/26528664/84510b62-43b8-11e7-990b-6bc09e5ea649.png;" parent="1" vertex="1">
+ <mxGeometry x="776" y="1089.5" width="47" height="34" as="geometry" />
+ </mxCell>
+ <mxCell id="166" value="" style="shape=image;imageAspect=0;aspect=fixed;verticalLabelPosition=bottom;verticalAlign=top;image=https://cloud.githubusercontent.com/assets/2662304/26529313/70be8382-43c6-11e7-943b-2c0ec46cc782.png;" parent="1" vertex="1">
+ <mxGeometry x="662.5" y="1039.5" width="72" height="39" as="geometry" />
+ </mxCell>
+ <mxCell id="167" value="" style="endArrow=none;dashed=1;html=1;dashPattern=1 1;strokeColor=#C7D1E0;strokeWidth=3;fontSize=13;entryX=0.5;entryY=0;startArrow=block;startFill=1;" parent="1" source="46" target="165" edge="1">
+ <mxGeometry width="50" height="50" relative="1" as="geometry">
+ <mxPoint x="849" y="1020" as="sourcePoint" />
+ <mxPoint x="850" y="1112" as="targetPoint" />
+ </mxGeometry>
+ </mxCell>
+ <mxCell id="168" value="" style="endArrow=none;dashed=1;html=1;dashPattern=1 1;strokeColor=#59DE9C;strokeWidth=3;fontSize=13;entryX=0.75;entryY=0;startArrow=block;startFill=1;" parent="1" source="46" target="164" edge="1">
+ <mxGeometry width="50" height="50" relative="1" as="geometry">
+ <mxPoint x="828" y="1020" as="sourcePoint" />
+ <mxPoint x="818" y="1076" as="targetPoint" />
+ </mxGeometry>
+ </mxCell>
+ <mxCell id="169" value="" style="endArrow=none;dashed=1;html=1;dashPattern=1 1;strokeColor=#59DE9C;strokeWidth=3;fontSize=13;entryX=0.75;entryY=0;startArrow=block;startFill=1;" parent="1" source="46" target="166" edge="1">
+ <mxGeometry width="50" height="50" relative="1" as="geometry">
+ <mxPoint x="764" y="1037" as="sourcePoint" />
+ <mxPoint x="777" y="1087" as="targetPoint" />
+ </mxGeometry>
+ </mxCell>
+ <mxCell id="170" value="&lt;font color=&quot;#e6d0de&quot;&gt;netdata daemon&lt;/font&gt;" style="text;html=1;resizable=0;points=[];autosize=1;align=left;verticalAlign=top;spacingTop=-4;fontSize=13;fontColor=#59DE9C;rotation=90;fontStyle=1" parent="1" vertex="1">
+ <mxGeometry x="1195" y="593" width="118" height="22" as="geometry" />
+ </mxCell>
+ <mxCell id="174" value="" style="shape=curlyBracket;whiteSpace=wrap;html=1;rounded=1;strokeColor=#6A9153;fillColor=#ffffff;gradientColor=#FFF2F9;fontSize=14;align=center;direction=south;" parent="1" vertex="1">
+ <mxGeometry x="877" y="265" width="377" height="20" as="geometry" />
+ </mxCell>
+ <UserObject label="standard dashboard" link="http://london.my-netdata.io/" linkTarget="_blank" id="176">
+ <mxCell style="shape=image;verticalLabelPosition=bottom;labelBackgroundColor=#ffffff;verticalAlign=top;aspect=fixed;imageAspect=0;image=https://cloud.githubusercontent.com/assets/2662304/26528929/cbe844ee-43be-11e7-8e1b-7899d142c1a5.png;fontSize=14;" parent="1" vertex="1">
+ <mxGeometry x="75" y="315" width="121" height="107" as="geometry" />
+ </mxCell>
+ </UserObject>
+ <UserObject label="&lt;span style=&quot;font-size: 16px&quot;&gt;&lt;b&gt;dashboard.js&lt;/b&gt;&lt;/span&gt;&lt;div&gt;&lt;font style=&quot;font-size: 14px&quot;&gt;real-time netdata charts&lt;/font&gt;&lt;/div&gt;&lt;div&gt;&lt;font style=&quot;font-size: 14px&quot;&gt;on any web page&lt;/font&gt;&lt;/div&gt;" id="177">
+ <mxCell style="whiteSpace=wrap;html=1;strokeColor=#666666;fillColor=#f5f5f5;fontSize=14;align=center;" parent="1" vertex="1">
+ <mxGeometry x="75" y="470" width="249" height="74" as="geometry" />
+ </mxCell>
+ </UserObject>
+ <UserObject label="custom dashboards" link="https://learn.netdata.cloud/docs/agent/web/gui/custom" linkTarget="_blank" id="179">
+ <mxCell style="shape=image;verticalLabelPosition=bottom;labelBackgroundColor=#ffffff;verticalAlign=top;aspect=fixed;imageAspect=0;image=https://cloud.githubusercontent.com/assets/2662304/26528933/e9bd18b4-43be-11e7-9e3c-725342bbcdca.png;fontSize=14;" parent="1" vertex="1">
+ <mxGeometry x="204" y="315" width="121" height="108" as="geometry" />
+ </mxCell>
+ </UserObject>
+ <mxCell id="180" value="" style="html=1;shadow=0;dashed=0;align=center;verticalAlign=middle;shape=mxgraph.arrows2.arrow;dy=0.51;dx=19.57;direction=north;notch=0;fillColor=#fff2cc;strokeColor=#d6b656;" parent="1" vertex="1">
+ <mxGeometry x="100" y="446" width="70" height="30" as="geometry" />
+ </mxCell>
+ <mxCell id="181" value="" style="html=1;shadow=0;dashed=0;align=center;verticalAlign=middle;shape=mxgraph.arrows2.arrow;dy=0.51;dx=19.57;direction=north;notch=0;fillColor=#fff2cc;strokeColor=#d6b656;" parent="1" vertex="1">
+ <mxGeometry x="229" y="446" width="70" height="30" as="geometry" />
+ </mxCell>
+ <mxCell id="182" value="" style="html=1;shadow=0;dashed=0;align=center;verticalAlign=middle;shape=mxgraph.arrows2.arrow;dy=0.51;dx=19.57;direction=north;notch=0;fillColor=#59DE9C;strokeColor=none;" parent="1" vertex="1">
+ <mxGeometry x="357" y="533" width="70" height="30" as="geometry" />
+ </mxCell>
+ <UserObject label="SVG badges from metrics" link="https://github.com/netdata/netdata/tree/master/web/api/badges" id="183">
+ <mxCell style="shape=image;verticalLabelPosition=bottom;labelBackgroundColor=#ffffff;verticalAlign=top;aspect=fixed;imageAspect=0;image=https://cloud.githubusercontent.com/assets/2662304/26528965/8993b082-43bf-11e7-8d5b-3eea117bbd55.png;fontSize=14;spacingTop=-3;" parent="1" vertex="1">
+ <mxGeometry x="469" y="485" width="161" height="25" as="geometry" />
+ </mxCell>
+ </UserObject>
+ <mxCell id="184" value="" style="html=1;shadow=0;dashed=0;align=center;verticalAlign=middle;shape=mxgraph.arrows2.arrow;dy=0.51;dx=19.57;direction=north;notch=0;fillColor=#59DE9C;strokeColor=none;" parent="1" vertex="1">
+ <mxGeometry x="512" y="534" width="70" height="30" as="geometry" />
+ </mxCell>
+ <UserObject label="use metrics&#xa;in your scripts" link="https://github.com/netdata/netdata/tree/master/web/api/exporters/shell" id="187">
+ <mxCell style="shape=image;verticalLabelPosition=bottom;labelBackgroundColor=#ffffff;verticalAlign=top;aspect=fixed;imageAspect=0;image=https://cloud.githubusercontent.com/assets/2662304/26528672/84d2eda8-43b8-11e7-8385-6ad78be4d7cc.png;fontSize=14;spacing=2;spacingTop=-14;" parent="1" vertex="1">
+ <mxGeometry x="352" y="438" width="77" height="66" as="geometry" />
+ </mxCell>
+ </UserObject>
+ <mxCell id="188" value="" style="shape=image;verticalLabelPosition=bottom;labelBackgroundColor=#ffffff;verticalAlign=top;aspect=fixed;imageAspect=0;image=https://cloud.githubusercontent.com/assets/2662304/26528673/84d4fc60-43b8-11e7-9512-5db073e72e9f.png;" parent="1" vertex="1">
+ <mxGeometry x="283" y="1956" width="58" height="77" as="geometry" />
+ </mxCell>
+ <UserObject label="" link="https://github.com/netdata/netdata/tree/master/health/notifications/email" id="189">
+ <mxCell style="shape=image;html=1;verticalAlign=top;verticalLabelPosition=bottom;labelBackgroundColor=#ffffff;imageAspect=0;aspect=fixed;image=https://cloud.githubusercontent.com/assets/2662304/26528675/84f226d2-43b8-11e7-9de6-6d7c391aaa8d.png;rounded=0;shadow=0;glass=0;dashed=1;dashPattern=1 1;comic=0;strokeColor=#C7D1E0;strokeWidth=3;fillColor=none;gradientColor=#CCFFFF;fontSize=14;fontColor=#E6E6E6;align=center;" parent="1" vertex="1">
+ <mxGeometry x="766" y="250" width="56" height="56" as="geometry" />
+ </mxCell>
+ </UserObject>
+ <UserObject label="" link="https://github.com/netdata/netdata/tree/master/health/notifications/pushover" id="191">
+ <mxCell style="shape=image;verticalLabelPosition=bottom;labelBackgroundColor=#ffffff;verticalAlign=top;aspect=fixed;imageAspect=0;image=https://cloud.githubusercontent.com/assets/2662304/26528605/80703edc-43b8-11e7-839d-267f613e15b0.png;" parent="1" vertex="1">
+ <mxGeometry x="654" y="205" width="134" height="28" as="geometry" />
+ </mxCell>
+ </UserObject>
+ <mxCell id="192" value="" style="shape=image;html=1;verticalAlign=top;verticalLabelPosition=bottom;labelBackgroundColor=#ffffff;imageAspect=0;aspect=fixed;image=https://cloud.githubusercontent.com/assets/2662304/26528645/8275abfe-43b8-11e7-9d21-306ab844f7bb.png;strokeColor=#6A9153;fillColor=#CCFFFF;gradientColor=#CCFFFF;fontSize=20;align=center;" parent="1" vertex="1">
+ <mxGeometry x="127" y="815" width="61" height="39" as="geometry" />
+ </mxCell>
+ <UserObject label="" link="https://github.com/netdata/netdata/tree/master/health/notifications/flock" id="198">
+ <mxCell style="shape=image;verticalLabelPosition=bottom;labelBackgroundColor=#ffffff;verticalAlign=top;aspect=fixed;imageAspect=0;image=https://user-images.githubusercontent.com/2662304/32355748-512dda64-c039-11e7-8117-bc31a0cf50eb.png;" parent="1" vertex="1">
+ <mxGeometry x="370" y="311" width="142" height="50" as="geometry" />
+ </mxCell>
+ </UserObject>
+ <UserObject label="" link="https://github.com/netdata/netdata/tree/master/health/notifications/alerta" id="200">
+ <mxCell style="shape=image;verticalLabelPosition=bottom;labelBackgroundColor=#ffffff;verticalAlign=top;aspect=fixed;imageAspect=0;image=https://user-images.githubusercontent.com/2662304/34533144-9ecf2918-f0c1-11e7-928e-c2654b092303.png;" parent="1" vertex="1">
+ <mxGeometry x="420" y="275" width="106" height="27" as="geometry" />
+ </mxCell>
+ </UserObject>
+ <mxCell id="201" value="" style="shape=image;verticalLabelPosition=bottom;labelBackgroundColor=#ffffff;verticalAlign=top;aspect=fixed;imageAspect=0;image=https://user-images.githubusercontent.com/2662304/34533412-c59e2c28-f0c2-11e7-9ae3-8eb392608029.png;" parent="1" vertex="1">
+ <mxGeometry x="845" y="1086.5" width="79" height="51" as="geometry" />
+ </mxCell>
+ <mxCell id="202" value="" style="endArrow=none;dashed=1;html=1;dashPattern=1 1;strokeColor=#59DE9C;strokeWidth=3;fontSize=13;exitX=1;exitY=0.75;startArrow=block;startFill=1;entryX=0.662;entryY=0.006;entryPerimeter=0;" parent="1" source="34" target="201" edge="1">
+ <mxGeometry width="50" height="50" relative="1" as="geometry">
+ <mxPoint x="871.2857142857142" y="952.1428571428571" as="sourcePoint" />
+ <mxPoint x="935.8571428571429" y="1069.2857142857142" as="targetPoint" />
+ <Array as="points">
+ <mxPoint x="870" y="1020" />
+ </Array>
+ </mxGeometry>
+ </mxCell>
+ <UserObject label="" link="https://github.com/netdata/netdata/tree/master/health/notifications/irc" id="204">
+ <mxCell style="shape=image;imageAspect=0;aspect=fixed;verticalLabelPosition=bottom;verticalAlign=top;image=https://user-images.githubusercontent.com/31221999/36795772-8b096efe-1cac-11e8-8d6d-791ee5c88179.png;" parent="1" vertex="1">
+ <mxGeometry x="680" y="153" width="69" height="35" as="geometry" />
+ </mxCell>
+ </UserObject>
+ <mxCell id="215" value="" style="shape=image;verticalLabelPosition=bottom;labelBackgroundColor=#ffffff;verticalAlign=top;aspect=fixed;imageAspect=0;image=https://user-images.githubusercontent.com/2662304/37907799-cf84a726-310e-11e8-9964-057e0297752d.png;" parent="1" vertex="1">
+ <mxGeometry x="707" y="1133" width="83" height="43" as="geometry" />
+ </mxCell>
+ <mxCell id="216" value="" style="endArrow=none;dashed=1;html=1;dashPattern=1 1;strokeColor=#59DE9C;strokeWidth=3;fontSize=13;entryX=0.5;entryY=0;startArrow=block;startFill=1;exitX=0.291;exitY=1.021;exitPerimeter=0;" parent="1" source="46" target="215" edge="1">
+ <mxGeometry width="50" height="50" relative="1" as="geometry">
+ <mxPoint x="819.4939412610393" y="1020" as="sourcePoint" />
+ <mxPoint x="784.2352941176473" y="1136.7058823529414" as="targetPoint" />
+ </mxGeometry>
+ </mxCell>
+ <mxCell id="217" value="&lt;font style=&quot;font-size: 20px&quot;&gt;&lt;b&gt;bcache&lt;/b&gt;&lt;/font&gt;" style="text;html=1;strokeColor=none;fillColor=none;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" parent="1" vertex="1">
+ <mxGeometry x="953" y="1150.5" width="40" height="20" as="geometry" />
+ </mxCell>
+ <mxCell id="220" value="" style="endArrow=none;dashed=1;html=1;dashPattern=1 1;strokeColor=#59DE9C;strokeWidth=3;fontSize=13;exitX=1;exitY=0.75;startArrow=block;startFill=1;entryX=0.25;entryY=0;" parent="1" source="34" target="217" edge="1">
+ <mxGeometry width="50" height="50" relative="1" as="geometry">
+ <mxPoint x="871" y="952" as="sourcePoint" />
+ <mxPoint x="907" y="1130" as="targetPoint" />
+ <Array as="points">
+ <mxPoint x="880" y="1030" />
+ </Array>
+ </mxGeometry>
+ </mxCell>
+ <mxCell id="226" value="" style="shape=image;verticalLabelPosition=bottom;labelBackgroundColor=#ffffff;verticalAlign=top;aspect=fixed;imageAspect=0;image=https://user-images.githubusercontent.com/2662304/47609442-6a8da100-da47-11e8-9918-468e59e8e70f.png;" parent="1" vertex="1">
+ <mxGeometry x="858" y="2072" width="142" height="63" as="geometry" />
+ </mxCell>
+ <mxCell id="228" value="" style="shape=image;verticalLabelPosition=bottom;labelBackgroundColor=#ffffff;verticalAlign=top;aspect=fixed;imageAspect=0;image=https://user-images.githubusercontent.com/2662304/47609535-4206a680-da49-11e8-80bb-793dc9a783dd.png;" parent="1" vertex="1">
+ <mxGeometry x="1013.5" y="184" width="98" height="89" as="geometry" />
+ </mxCell>
+ <UserObject label="" link="https://learn.netdata.cloud/docs/agent/exporting" linkTarget="_blank" id="230">
+ <mxCell style="shape=image;verticalLabelPosition=bottom;labelBackgroundColor=#ffffff;verticalAlign=top;aspect=fixed;imageAspect=0;image=https://user-images.githubusercontent.com/2662304/47609564-f7d1f500-da49-11e8-936a-6d0ff9329ded.png;" parent="1" vertex="1">
+ <mxGeometry x="940" y="361" width="162" height="40" as="geometry" />
+ </mxCell>
+ </UserObject>
+ <UserObject label="" link="https://github.com/netdata/netdata/tree/master/backends/prometheus" id="231">
+ <mxCell style="shape=image;verticalLabelPosition=bottom;labelBackgroundColor=#ffffff;verticalAlign=top;aspect=fixed;imageAspect=0;image=https://user-images.githubusercontent.com/2662304/47609572-2354df80-da4a-11e8-9121-49d3f1a74fce.png;" parent="1" vertex="1">
+ <mxGeometry x="959" y="476" width="274" height="52" as="geometry" />
+ </mxCell>
+ </UserObject>
+ <UserObject label="" link="https://github.com/netdata/netdata/tree/master/health/notifications/awssns" id="232">
+ <mxCell style="shape=image;verticalLabelPosition=bottom;labelBackgroundColor=#ffffff;verticalAlign=top;aspect=fixed;imageAspect=0;image=https://user-images.githubusercontent.com/2662304/47609620-26040480-da4b-11e8-8ac3-c1b6a209619f.png;" parent="1" vertex="1">
+ <mxGeometry x="510" y="120" width="140" height="56" as="geometry" />
+ </mxCell>
+ </UserObject>
+ <UserObject label="" link="https://github.com/netdata/netdata/tree/master/health/notifications/rocketchat" id="233">
+ <mxCell style="shape=image;verticalLabelPosition=bottom;labelBackgroundColor=#ffffff;verticalAlign=top;aspect=fixed;imageAspect=0;image=https://user-images.githubusercontent.com/2662304/47609674-ec7fc900-da4b-11e8-84cd-ff47fe586f84.png;" parent="1" vertex="1">
+ <mxGeometry x="422" y="143" width="77" height="77" as="geometry" />
+ </mxCell>
+ </UserObject>
+ <UserObject label="" link="http://netdata.cloud/" linkTarget="_blank" id="6wNVMjbWM3VSl3cxvCD3-265">
+ <mxCell style="shape=image;verticalLabelPosition=bottom;labelBackgroundColor=default;verticalAlign=top;aspect=fixed;imageAspect=0;image=data:image/png,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;" parent="1" vertex="1">
+ <mxGeometry x="4" y="9" width="359" height="72.84" as="geometry" />
+ </mxCell>
+ </UserObject>
+ <UserObject label="app.netdata.cloud" link="https://app.netdata.cloud/spaces/netdata-demo/rooms/all-nodes/overview" linkTarget="_blank" id="6wNVMjbWM3VSl3cxvCD3-270">
+ <mxCell style="shape=image;verticalLabelPosition=bottom;labelBackgroundColor=default;verticalAlign=top;aspect=fixed;imageAspect=0;image=data:image/png,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yUn6usG+fvT39LDlBxERERERERERERERRR8JfKRmZ9kDH0LmZR2DH0REREREREREREREFHWkxYc3DH4QEREREREREREREVHUsbb4cOX3mB8pqanIyMhCXHycWjM2hoeG0dnZjt6eHrXGN475QUREREREREREREQ08RSWz1Rz7vxu+RGOwIeQ95D3IiIiIiIiIiIiIiIiCobfwY9wBD5M4XwvIiIiIiIiIiIiIiKKLRzzg4iIiIiIiIiIiIiIYgqDH0REREREREREREREFFMY/CAiIiIiIiIiIiIiopjC4AcREREREREREREREcUUBj+IiIiIiIiIiIiIiCimMPhBREREREREREREREQxhcEPIiIiIiIiIiIiIiKKKXGFxWXDat6nvMlT1JyLKVfiE584DVmox+aHnsRe67ra1/Doc1v0zRzbmTqx+w8/xRsn1KKLppNeHnBhs+WrOSIiIiIiIiIiIiIimigKy2eqOXchbPlRhPOvXqbmXViDIQ/dr02voR4ZmPuJ61GpNiEiIiIiIiIiIiIiIgqFELb8MNRvvB9/O+Hc8qPy6jtxfqnvlh6uwtnyIyEhEcmpqUhITEKcWkdE0UsStcGBfvT19GBwcMBYOUpMJ4goXJiGEVE0YxpGRERERL6EOr8YlpYf7bX1aNf+Fi26EnnGKqUUeTb524omPwMf4SQZ6bTMLCQyI00UM+Ralmtarm25xkeL6QQRhRPTMCKKZkzDiIiIiMiXUOcXfQldt1fNr2H9e51A9mm45OwctTLySQ0iZqKJYpNc23KNjxbTCSIaD0zDiCiaMQ0jIiIiIl9ClV/0JYRjfgBNm95FvfY3q7TIMrB5LZpa5G8O8rz0nDWepOk0EcWuUFzjTCeIaLwwDSOiaMY0jIiIiIh8Geu8XkiDH8AW/G2jhD+c7T0g6zIw92xzQPRluOL2O3HzZ1y7yAo/1iIiim2huMaZThDReGEaRkTRjGkYEREREfky1nm9EAc/NO8/ic21at6krXtUgiKl5+FmCXrcfh6KUI/NL+5SGxAREREREREREREREYVGXGFxmQywPqK8yeHts6rppH+jo9ts+WouOFk5uWqOiGJVe2uzmgsO0wkiGk9Mw4gomjENIyIiIiJfRptfLCyfqebchb7lBxERERERERERERER0Thi8IOIiIiIiIiIiIiIiGIKgx9ERERERERERERERBRTGPwgIiIiIiIiIiIiIqKYwuAHERERERERERERERHFFAY/iIiIiIiIiIiIiIgopjD4QUREREREREREREREMcXv4Mfw0LCaG3vhfC8iIiIiIiIiIiIiIootfgc/OjvbwxKUkPeQ9yIiIiIiIiIiIiIiIgpGXGFxWVQ3s7DZ8tVccLJyctUcEcWq9tZmNRccphNENJ6YhhFRNGMaRkRERES+jDa/WFg+U82545gfREREREREREREREQUUxj8ICIiIiIiIiIiIiKimMLgBxERkRcFU4swb+EifZpSOFWtJSIiIiIiIiKiSJeQmW37tpqPSqmp6WouOCmpaWqOiGJVX2+PmgsO04mJZ07VPFx02WWYNec0xMUBmVlZegCk8vTTMTg4iKaTJ9SWRGOPaRgRRTOmYURERETky2jzi5m5k9ScO7b8CIHV9zyMRx65G6vVsums2+7Dg7ctUUtBWHM3HnngVpylFokoekVXOnEtvvnIfbh9mVqcYM6+4CLMW7gQWza9iicf+wXWv/BXfZJ5WTd/4SIsu+BCtTXRxDBR0zDje3t+Lbd9suxWPOhhHxHR+OP9WmwwjqM23XOtWjMeRviN0X8LHsY316jlEJFzdWy+9xjn+3mNEBHROGPwI2RKcO5oMs5ENAEwnbAKuMBBbp7G+GZXWnwUT5+Ov/35Tzhy+LC+TpZlErJu/d/+ipLpZfq2RBPLRE3D0jFvpUvas+xWnFui5okoSjAfNjIpCI/UIO61qCypw7pbvoxb7n1GrYtAW36CO7TP+N21ajkklmBpObBrnfm9I/k4ERERRRYGP0Kka2c1GuevmbA1pYloZEwnIp+0+Hhz0z/Q2d6h1gDzFy7GnNMdgY7mpiZ9G9mWaCKZqGmYfO/aktlOhUyrV1ahcf0G1KplIop8zIdR1Fq2BBU4hLe2qOUJRG/xMgYtaYiIaOKIKywuG1bzUclmy1dzwcnKyVVzwZPmt+c2P4EnsQY3575pr4kiP9TXYy3u+PE2fVlqbNz+wE2Yl6kW6zY411qRJrKfrYIxikkd1q0HVi5rw6Nf+wm26uukhscKlOrzkoF/wvLazo+5vTbRBNbe2qzmgjPx0gnZ7my0rj+Eikss73XL9/C0eg2sd67RJt9vJdxfT77fzfPNsZm6sOvxu/CQduOmb2+vNe1Y72175/VArf7+sq+0O6G1xjY6aR1i2R/eXs8TGdx8xRVX6t1bWSUnp+h/+/p69b+m6z/3eWz42wtoOFav1hCNjYmVhvl6fvjTMOv3tn9P/Ttk4/Vb9qHykdnYq7+uxr5eLRORjmlYoGmYcN7Wnp+TfM4lUOmZon+mGTjgMc9kpn3C+Gw5B6uRP1/7Dh3Vls+tcfpuGu3zPdp8Nm4uP+RhO+P93lqsvZe2P9dhhT1f55z31Oif2cz0+c6Lef3sHj6b875z+W6ySv9+27DUcjyM/KMxP+LxcHlPx/dSvzE7mzBvvvpeTvtSPW49HrmHsCu3ysvnGOGc0zidp173RSDnbhDfx+vjwnlfOp0DLnlz4f0cNTjfJ6jHPZ376n1df9OJiCj6jDa/WFg+U825Y8uPENr64zdRW7LCS60EIzNScfAJ3CJNdbVJMpOOLly0H24tM9Ko/XAbj2s31PZMonD8sBuPP4ED5TfZ32v1PSuQr2UyzNeWDChrRxBFnuhJJ9IxbxnwpNr20Z15WKn31/sMvru+DqWV1i5gVDcELjdqcpN1/fwm7SZFfZ7HD6FijdHn79P3ymt26TdHt9yiboK1mxr9Btvcfr12k/VZo0n/1h/fpS3XGTdy2mN+3eD4eH9PphQW4sTxY2rJIN1dXf6xa3HBypVqjYNsK88hmkjGNg1zf76kU2b3eGFPwyzkezfOX6GnR9LqAzs3OBXUEFF0iI40zDU/92W8nnuT8Tprv4d1dSWotDzvrMUztDRpraOg3SkvBZX2OZSWq7TRqfBao3fXJC3apLBZe1xLE7f+eC12YQaWWlrLWN9Pp+3Pyr3q/bTnN8535D0dwRr1uOTFVN7Oldtnf7wN55pdO3n4bJ7Yv5uWB96FKtz8iPZB1qrXk7T/EvO95Vg555l35TqOlXGsJcDjeNyap9Z/Y3L3OZ6rvdf1vrpUK9H2mdfP4f2cMU3PTUfjCRVM8LgvRnqd0X4fX4/7OF898Hmc1eN6ZQT1uJzvK+Ua9HDuY81slGr3Bgx8EBGRLwx+hJS6oV7moXBt2RJUZNbhdUstGCPzfbbR9Fp+uDuq8Xf7D7fxWnZuP+zbtAyn83ul5zoy31KwyEwAUSSKlnSiC7vWOm6K9c+RqW5+1+7TPpOjC5izbjtbe999XgoC81Bo3jDLzZrrjbaVdlPjdCMu72N9flACeH8P9FYfcVHdQJIoxMYwDZPnoxpPWp7/9LpqQAUdRPjTMNMz+PvOPJx7z904t8T5OxJRNImCNMztfZxf5+m91s9vjAVxYLu8pxoXwpL2Ye0GvaD6I5YC41otP+R/Xmgb3joIVCx2FHR/ZL75fopT3lPSyi57gHl1ZQlq11tq6mvv/Xqdp7FX5LOnO382r9t653i+fO4u7bO96QjSOOUrtTzy16z739jefnz0PLXluWr7v9ean0X7jbGPv+HyXE+8fQ4/zhnZ5xKg3+vpXDGN9Dqj/j4+Hh/hfHU28nGWQE9Xs+O6kvMduYX6+a6f+/bKC0u061I7v/Z6DoQRERGZGPwINQ8ZTF1pNtI72mAMn+vurCl5QPNxrxlR/fGSFXp/l/bJUtPo6XuN2ir2x1xq+BBRBInadCIdOXp79mew136Tom5iPN14aDczd+itNxyfx3MNR5PUWnNs+8gjlq4IghHg+584fhxTCqeqJcPBD/ehs8Mx/oeVbCvPIZpwxigN05+fKTV1LemApYuOsKdhLqQQtLFES1PZ6oMoukV4Gjbi+0jhuRnMXbMC8+xjQZQgJzPdKd/zyCOWbpCCtHX7IedC9A4/xp7QC6uXoDBX2y2XWD+PtTsjK/nsXWgN4UBKW080qTkvpFWK5XNZu1fVj4EHW7dYgj6hMMI5o9MDFyME6Ed4nbH8PiOer05GPs6Hm50DLxJA6zq4zXh9vfKCCkaqYKXPoBAREZGGwY+Q24aH1lYj/5K7sVSt0dW2oSszG9PVois9c6ZqNHiiP666e3Ga7LWYjdob5vp1zSM0vSWicRSt6YTjZkVqXqWXL8FZI914SGsO83M8bnxn91pghrNuW4N5zdbPH4LBhAN4fxm7o7+vD9LVlWlO1Tw9yCHTBZc4ur4qnzVb35bjfdDENDZpmP78jmpHVxj2yaw1PA5pmBOp0VzNVh9EUS+y07AR30cP4KbrrTGcCoZRh9aOLuyyd23kmLy3kvPDlm040GF0N+T8fj7oheHbcLxZ2y2W7pDMyWlMEJ18djNAHQ7X4puX5DntK+mO1aQfg3AY8ZxRrWdGCtCP8Dpj+X1GPl+tRj7O0tWtdNllBnFW5lpbtDjOfel+LX3EVptEREQMfowN1XRznqX2iJlptDbbNbpZUM1P9Ro8zjWQJKNjZ63loMhAYEZfmtLPpjlPRFEh4tMJ48bCpH8Oa00//bPMwEdWSr/PnmtBS5+9j1j68A2Y1HRTs57JTXU65q109N1v3R/BvP+ud97BsgsuRG6eUUOu4Xi9Pqi5TFs2bdLXyWMLly3TtyWasMYiDZPnu9TG1q9jvb/98Kdhnmz98U9Y0EIUCyI5DfP0Pi5jDRkB3DU4t6TL0gWVdEcEp3yRPsj1aPJiOqObo9LKu/Xul9wCwJZuBOV7fkTbp2Zhvd5NkVMXY7If7nPKqxrUe1i31ebD182g0QrQTo6B09gwRuvkkN9v+zxnhB9dXomRXmcsv48f56vDyMdZPve5zY6xS1zHpjHO/RX4iHTxZu+Ki4iIyLu4wuKyqO7M3GbLV3PBycrJVXPBk8JF+YF2rsEiGTtjUDvHemMgMnvTY6mhbR2sTfvhf9DSPLW2Tsss5rbhUfsPvvGa9sJA7fmPrqtTzVXdH/M2EBzRRNPe2qzmgjPx0gnZ7mztcaC0xHwnGVTR0mezRm5Obp6v3Xg8rgYs98DYxnwNqY1o2VYfBFMKDczXdv/uUvNLanzp38/+3a2v4/ydXPeHz/f34uwLLsK06aXYsulVHDns3PmFtAqR4MjRw7V4c9M/1FqisTWx0jCX50tN1rXaa2tp2NZxSMM8f2+TvM9s7DVf1+W7GfxLd4hiGdOwQNMw4bxtl9NnFOp9oPJIaq1wz/usxVvQ3n+L8ZycLb5agjg+v/N7qs/j8pn198ptQm1JiffPas/vGWrXP4G/18rnUSssnD+7a7rtkuY68fDd5H0r91k+rzz/bLSaabLT59L20/pDqLjEGNBefw2XY+34Xi6vozH2w5vqvZwfd35MuD7fxznj9h1Mno6Tj9eR5RB9H/fHhWzj5Xw131c+j8f8uctxdjlfDNZt1Pf0cO4TEVH0Gm1+sbB8pppzx+BHCDLTRBTZIuGmOybJzcwa4MkYvPGQ7q7mLVyIpORke9dWBVOL9K6upMXHnupd+jqicGAaNkZiOA0jiiRMw2KDBJAq9zoHTtwLwSmUZP9ej7XOwaSYJoEN7Yd5rSPQIlzPs4m3X4iIYl9EBD+mFJeh7LQz9IKgsSQFSzUfvIcTR2rUGt8Y/CCikfCme2z4rhEdGyTgMaWwUJ+Xwc05xgeNB6ZhY2MipGFEkYBpWAzQa+9n43WXVhcMfowl9xYXsc/zd3b+vZ6I+4WIKPZFRPDjrJVXj3ngwyQBkK3rnlNLvjH4QUQj4U13qJnN6t27kSGi0GMaFmpMw4jCiWlYlFPdELl1Z6Vh8INCzlO3V2Z3a2YXWj67jCMiomgUEcGPc68c3TBpgXr9Bf9uRRn8IKKR8KabiKIZ0zAiimZMw4iIiIjIl7EMfsSrv0RERERERERERERERDGBwQ8iIiIiIiIiIiIiIoopDH4QEREREREREREREVFMYfCDiIiIiIiIiIiIiIhiCoMfo7YAKz73eayYoxZF/nKsWrMcIRmaT17rsgVqYawUYfGaz+P6z92AxaMbP95ZKPeDi4rLRvisY/becrxXoUItBW9szpuKy1xeM0Tkda/XPq8+2c9H7TuEbB+Har9GKuN42/eh034cLf+Ow1idG36T81u+9wifNXfZDeP2OeW9Vy0rUkvBGTFt8lPA+2HOqhCeU2NJfm9C/FsTcrGeHgWDaZi/aZi+3Thdi6NPw3h9hpIcj+vdPqtxLZnncih+d8gfY5mG+ffbP+5pmL/3e+OYhnniNT80ZvmeEKWDIdyPAecJQ8q/3+jRcU4XdbL/RvW+Y/FbERm/keOflhARRR8GP0Kk4NxoLigpQE5WA9567HfY3qhWRbvGjXh+7UY0q8UxF2QGN/DzRjJdozvXAi2clQz3UmzHk4/9Qp/ewmLeqAdFrjFjHz752AvYlz133DPPYZWfhcyj2nnkdl2O/pyODaPcD3uex5MvvasWAuRv+jVnVQiu/XpsXzvWvzWyLz8fpnRqIp2/TMNiPw3j9RlqHe1ZqLJ819xlc5HR3qWWKLwmeBrm9X4vStOw0eR7fMmvRFHbbmMfRVggyG8SGIrCShzhvy8O9Pnh+I0c2YGXfoENe9SCP6L0fCAiCqW4wuKyYTXv07lXrlZzoZEx/UwsKExCy6FteP+EWmnx+gtPqznfbLbR5VqzckZbj0FqFZSg5nWgyrYdz2+pNzJKFwOb5QZZfmzK6uyZMyl4zt0uP5pSi8KG1raZmD1Ne0BuqGtKcP25BdpCF/Y9p35Y5bUWaz+12dp2WWo79VpSKH15Vbo+71hved3s/XjR5SZdagoslffTdFS/oH1eaD/6VxqvbX1fnctnbLe8nnyuq2ciU+b1m4nnccBlfcfRBiC73dgP2rL7e2v7St9/i7XsuMb6+hbW5zW8bvzYO/aj7/3wjryeyz6U996M5eo5ls9u/SxO+8KyXvtODdpnqbE/R2PdF+Z38LZ/7OQ1fZw32hb+HivHdtpnO6p9yhrZR5KZM7e17jfXfentO5vkdRajea3z58/NL0JzY4FjH8s6j8dBnr8ceEW9rn4sWvC8eYx87dcQam91PasCE7J0wv79nPeLfj63tGN2VZZxDLTz09P549e5rj2v0+14W88NL6/jlFbJ552LVnU+yPbnYyOe31/pNT1y8HBOWb+P03VuPU+NbQ/MvAELtZuLjCpje/PcFe7XhKQhDvI5A32uY190aftI+21q0b6neT2OcAysr29yHEvZA+r75zv/Drhe6yHZD5Zry7ofzHPB5Pb5G92PDSzbOJ0f+u+TWrfd5vQd9Pds+Z3+Xn7tI0+/gx5/AyzbqN+0SV6Opedj7OF8VOe08z6ybOeUHlmPjWta7n7cjOvZ93kaDKZh0ZOG6dsGlG9zsJ6Xfl33GsdrhjANm9DXp+fPJM8rU+ef+b3d8phtzsdUvsf52I360hKVjzKeV1NbhDJLeqmfm26fI7bEdhpmvW7UeafOBW9pmMfrcyzTMPNzuVwPY5uGyetpeYqL1ev78TrW9eb16fyalnTLS77Hv7TTso9c0zbtOKzA8x7zR17v76zrrdt63I9GOhDI/bX+/VSa4fn4arOenut0TmlkG2v+02nZOBdydqp9Z38uAv68/l5PDvKdQnFfbNk3Xn8rtE+gnx+ergkv+9bC7VrXn+/hO8nnt/42SDmP5Vj481vr2I/yiRyfx3o+eNvG27Gxsl5v1uvQ62sSEY2x0eYXC8tnqjl3bPkRKnu2azc3ywOrQZQ1EzlaJtiofbTY+EGU2kivaz82iy21TKYVaZlzo6aSvda99oN2vm23qr30C7zYMtfR/NF8XTNjYtIyMY4a/C+ozys1GLZrP4nyo+jhh836GTETC/X30H7sLy5C/XPGez+pZVSW6rVinNdvbslSP7ga7b2rWl5Q7/0LVNuMfSU14KBlQPT1O7NwvmtNQKfPvB1wqxGyQFveaH/dt+ClFpd9H25HZ9WV+g2EbP9itaNGXsVlcpOhPstju5FzsdHUtuIyLedifsYaqTflQlqZvN5gZBrMzKPH/eOBt/PG4/7ycKyc9k+d9j2Np2NOpfq+Mmn7bb7xXQ68pB3HdsnEGBnb3GXaTbh6jyefq0eR9bzTSS0xNWvR3Ohyky7nY2k9XlSvJefpSM1xR9yvMacAS+3dLcjNhfV6S8dsm1z/sk7OnyxU249LO6r082ekc13LPF9tPM+8afF4bng7Vnvq0JBtM5qX59u0Xx4gR73+JFs7qs2CGcu11DDN/XpzOq5yTsl15HaNmDynPwWl2s2OPF97ToE6d72lIa68Pdc97dPWO+2LjWjNVjcA+jXs+RgstG//Alrne6pFZR5LeZ72/a/WttGuc2sN04rFM9G5Mzz7wbhRNnn4/G7Hxst5tud5Lb3s0m847TfRHvmzjyw8/sa4sP6mefv+2rGsksJXfb2k84v19/WVxln3kdf0aM5iFNWq97OfB6aR0mTLuRYTmIb5m4Z5fA/53N7ybRbjn4ZZTMDr0+PxV495ZR5vj2ljA7bvhJ7X1PO8Wtp/Sj1C4TbWaZicP0bhsX4ueEvDvN0njGUa5i2tGtM0TLufvVoCTLK93Ouan9XLfnRKz5yvT4/pogtvaaenNMnX/WdFWRZaZT94vL/zdF44H88Xa4scr+ftWAV0f+3MWxql5y3Vevv97Uj5z8a92hmQhUn6gnbPhy7AZnz2XO0U3LddpWleP6+n/SH8uZ48GOV9sfffCk/3xe7PH/m+2MLn74/i87fBwutvreOe4sVqOJcP2XnexuP5YOV0vVnyUjp/3peIKLow+BEy2g/oTpegxUja9+MdvWCqHs1t2u1RjfphbGxHhzFnOKqa3moO1DQg01aA3JlFyJy2WGXgP69H7TNUZsXxus4qygoc76G954FaLduivY5Pls94oFY105fmwLJsZpAlsz6tBBUu65v319u/h7x3ZtWV9s+7dFq6nqFvbmlHwbmyTgoIn3er+eb8md/FBrdaC++i2WZ9XbXalX0fNqC1vQv12mcT8v6GBSjTPtPsq43XuV5qfWRJZlDWN6DG3J/yXdWsV972j1p05vm88ba/XLnun5qjanZPC3Ks30WtdtW8X7shV+9xvb12SODkfETtXntmWs7TgjJf10IQ+zXqSebayGDKVFNm7a+1y3GDIedPluUGXY6LfjPs61zPwsI1kuF3XB/ezg3vx0rbpq1Iu45lG+3Gb2c9MvT12rHKbncU1tivJcv5ZudyXPWbKuM1A9Fg3phZzgt/rwlvz/WU9jnvC5c0zuMx0NIP7abkcm3dqmXQbprca1E5HUv5/u1ZyJUbNO06N9Jb2Z+e02hXIdkPTvz5/H6mqV758x4Wnn5jXFl+0/z9/iZfaZxjH/lIj7TfY+jvd4N2W7hRtVrzztu5FhuYhvnNw3v4zLdZjH8aZjHhrs8gj78ln+6RXqB3JS4vrfcr7aexMpZpGJCz2Ogm1qx04C0N019fzj+3+4QYS8Pkfrbd/J7Gva7B8350Tc+s3Qt5ShddeUs7PaVJ3u8/ZV9bjo2Vt/PC5Xg2b/md4/W8HatA7q/VoiHQ4ztS/lPePwtlch3MKQF27gZKK7VzXXvNUu27mJ/F2+f1eJ0If64nT0ZzX+yyb6znyljcF/vz+zPSb4PJ62+tYz9ay1Wc+bONO+95KRHcaxIRRbLRBT9W3IJf/ex/8cdv34kH5a9MD9yCL331O8a8Nv3qqxeojbNRceYSnLdUpjMxI02tjiVa5slTLaCxotfAtWTiXQMHkUaamFo/r35zIP21yrLUbtEyGgH3Ae1Um/AXeMvtJiAQzjdFT3poHjomvJw3HveXn/Q+pe3nh9Rq8cxaK+TJ5/Z7yNxIsEjNUkidaunyeNOo0zLB5jmtT1LjzNe5nqXdJL/iqWVUYE61SC1DdcOzR7uhypagps0pczyeRnNNBMzTMZCbsrWyLDUS5YYngP2t3YR1yg2ldnOZMcr9Gfx+8OPzjzpNHcU+8pPH79+4EZtri/RCXbmxNQvRR07jRiA1P/Xn1qNIbpy91VacgJiGBS6s+bZQp2F+4vXpiRTo7ce+V1ThLEWEkKZhSNe7nPOn5bMvEyINC+m928g8pkne7j9H2tce09XIcOAl+Q2VNPbzeqB1s3lsRsh/SiBIrgOjxYsZgCtATlvdyPfB/u6PQPZbRN8XW0T474/X84GIaIIKTcuPQmDvF7+OX+/t1vJ+M7EMG3DdF/+KXV1ARskcrNI2yZhejqkpQM/xvXjtrYPoTk0ynhtjDmzXfgDnWzLSUuvFXruhCLnZ+kxgLLUjpOZDR0uDHoU3amao9ZdZay955hzRNzLWZguIgLjWMtEyUwVHtQyS63oZHFTNur734jU36Jka6VNSz9xIJlSaFrtwf56vG/Ug969OauQUGDVfhJYxX7VGmsS6rrchQ8165W3/qEVPXM8bb/vLlet2Hr+/P59ZWI6Xg1EjyNrsWs411wye2/lor+0mNb0stXPs7xHEfo0xk2zp6GzxcP25nT+rPGSoXY61XrPuXWywNJH3dm54P1bqMVuluuGRYw+ULdbOS0+f0yPX4+pSi20U/L0mPHF9rpn2ue4LOSY6b8dA0gV9/0oB4gvY5zEwaD3fpbZbu6o5Z9xQnj8/K7h0VxnNfvDv81v5SFMbW9Cpt44z2PddwO8RGO/ffwEWWrrj8HhT7DGNE97TI+kPWf9tlZtcPwpnvZ1rsYhpWGDcPrcf+TZTeNOw4EX/9en9+DsVlAeTZ9mz0b8awBQ2IU3DYLQqNwocjXsVb2mY++s77hNiNQ3zzkfabt4jjoK3NMnb/adeG97bvvZ2XnhYH3BlPuHjvHDwfnxzl5U4um92Ci5oz/GV/5TgiHbOSTdQ8joHatqRs7gEGS3eQgSKX9eJxt/tLIK7L/b+W+HE3/Tb62+SIdDfn5CUCQXA+/lgcLveLHkpIqJYFJrgx/Ea/FTNiuamTWpOk56td3GalSbBjn40N0qb1zYcb+6XR2OP9gO4Wcuo2n8steXqNqOLges/t1z7qQuGlmHRn/95vTm1XstGfx+zFp2xfsQaEHpNCrO58pUoqg32Rky7aX5Fy5SYzUfPBd7Sm3q6rNcyWfaMgMf3LsKp7bsdzVC117H3Z2tyeV7OTrM1hnRRpd1cuOxf1LZjtj/9M3tgrSFx/dVaVlLV0HNaf7GXIygZGmkKrgdMvO0fH1zPG6/HSlpiSNNhlelz2s5xfjVv2a2PbWJ8Zu3IaPvI6H/WCEZI916SYTuwfT8yzO9W1q6PPeN6IyPNtx3vYZxr9r5Ls7R9r2f8vZ+PTu9hOSf82q8xxdLk22UfOfNy/vhzrut9+2rHSo6Jl3PDZ9rR2AJUzQTMgkRtsWBau6MJuR+8XUfeuZzT3ni9Jvzg7bku+6Iq22xq7u0a3ovqlrmW19nuclMqupAxXz1Pvv9zjhZkckOZqb2250KIMOwHb5/fmn75OM+kdqDe7YB+0/ou3qnOsp/Tjn3nzz4aBW/fX/aZnPvq88jkbxonvKZH+3ej1Xo8zW4c7HylyYEen0jHNMwzP69dX597JGFNw0YhBq5Pb8ffOV8V2jyLtTuXkQoFaTTCkIbZfxu1exRvaZjX61MTq2mYlbf96JqetYTg99PjNe/t/rMIFaUu+9qv+zuX9fPbg6xl7+O80BScaxwf78c3y9J9szbpn9kwYv4zeyaKWlSrEO07Z0zzp6KO78/r4O92Fvq5EPh9sbffCu/3xS7P9/M3STfi748Ll/M+tL8inng/H3Shup6JiKJEXGFx2bCa9+ncK1erOQvp9mr1TGQc34rrvv1HrPrqd3BjZRqOvPF13PHrC/CtB67CvPQT2PDF+/HsnCWYmdOPY+/vwIFOaQlyJhYUJqHl0Da8f0K9nsXrLzyt5nyz2Xzl0kaWleOcZSWi2NPe6vu2byRMJygUpJbY+dIvMJueh5zs24Utv3PcuEkN98UtI44BEC2YhlE0i/Xrk0bGNIwiXlSnSwuwYo0N71hq+Evrltzt5kDezH9OLL7PByKiSDXa/GJh+Uw15y5sA563d0tLjyTk5htt/IyWIERERLFPbjwvr2p3b91GISHN9+219WTypxYeEYUFr08iinTS5VVn1Hb7I11bma0KjGkpjMG2mf+ciLyfD0REE1XYWn78VB/wvFIf90P09PYjNYUtP4ho7LHGIRFFM6ZhRBTNmIYRERERkS9j2fJjdMGPMcTgBxGFCm+6iSiaMQ0jomjGNIyIiIiIfBnL4EfYur0iIiIiIiIiIiIiIiIKBwY/iIiIiIiIiIiIiIgopjD4MVr5y7FqzXJEe2NsGQzt+ssWqCW1bP1eo/ieFZc5BtvSpxjYX0SBWYAV2rm/Yo5aFCFIO+TacnrNEHG6Zu3pgvYdQnbtyv5YhQq1RESRz0wXxiLNISIiIiIiIhoLDH6MVuNGPL92I0bXM1k4FWHxGvdCx+b99ejIttkLNifZ2tHQVoQKc0iV/Cygdm/Q37Ph9V/gyceM6cXaIpy/rEg9QjRxFJwbaIG/5+s1EBWX3YDFAQyNJIHPpdhuv17fwmKs4vVKNMEVITe7C/ue+wU27FGrAiHBXksFCyIiIiIiIqJwSMjMtn1bzftUOrtKzYVH3Ye71Zxvqanpai44Kalpai5YUhv6NLRU16BHbu4vmIGshStx0bJFmFcwjF37j6vtTFLj+aM4e6H2+MJidL0rAQWjgLOoYRfqu7RN9NfJxV7tuVJwWZE/AxdduhgpdbvQPf8GnFucj3mXrkRx9zs41Gh9vdP0beQ1pADT3G6R9tgUfVt5nysxOysTxZZtdV19yKqagbhq+TwLsHBhPGoaslAYv0t7HlCx+AJkHH/V6/sZn1l99xLn7z1p5iLYWuT9jeW04vmYjhrsPdJuPO9Txmd07A+Nl/XO30vevwlFV33Sx/4mAvp6e9RccEafTkxF+cIEVL8OVM1sN8799BmonAHUStqhbSG1qi+/UM73RZiRclzbJsvj9erYrhhJ7ZmAfm3Jta2uA/v1bmy7dFoS8uaY67xcv3ZFOP2cfOx/7lV7oLN5/zs40ZOFnq5MlFel4pj6vHItXnvpMv39HNee97TMKe0rSEBntvbRzeudiHwa7zSs4rJP4ox8P9KSOatw/dUXGOnCrBwcMfNGV89EZnaRWteH072kE9bfeF95HOv7GOmllqYSUcQa/3wYEREREUWy0eYXM3MnqTl3bPkRatOKgFekxvR2NEyb61bjuuKyxYDZCuJ1YOmINSHTMdtWp23/O2xXwYOCUmCz9nypfVlx2Vy0Pqde77HdyLnY0S2Nud2TrzegYL6sr8f2tdrn0v69ZXk9Qz2a2wpQJt1Z5NuQ0daCA/vrkVEmn09qfDagRnu/3GU21OjvpU3P1aNoseXzm9/9pXfVipEUYfHFRag3P799f3hbb3B8r3bMvnq52t8vYF+2+/4miih7tqO+dLn7eTpnFapaXjDOd22qtsk2Hq5XbTtHq4w67Zozno45leo6kGk7oF/vwIGXtOui3VFb2+f1qytATpaatWhurFdzSv5ynF9ajxfVa0nrkJG6wnFK+2rknYgoWrimJZ7zHtpvt55fMdZLK8+Fki5IC1ktH4KjWtrlR0vZkfM42vvMh5YuGuuN9FI9mYiIiIiIiMiCwY9QO7pbBRXeRc1RfY3FApRNM4IIuj3P+xEo6MK+7c7bNOw0Cw/k9dIx+2qjH+7rP7cYBVlZMGNd9u321KFBX+PbgZoGZNiKIF1cddZo79nYgk69K6wC5KAdp7RtmvcDVeZYAFKT03iqwf7d3RWca37Gz+Py0nps3lKvvU8lilCPA+Zz5HNOK0GFt/Vq0f69GtvR0W5uJ8Eb+UsUyeqxfWc7ZrsEHSrKCpBZdaX9GlmqXdc5HgrzZLsGuTZ1ljRmTwtyrOmAWu3K5/UbgNyZWjph6QZP0o4CPVDqjWva51+aRESRyFveQ/s9xly1TvutrwquZe7IeRyjssZSWXfZAhx4ybUyBxEREREREZHB7+BHf1+fmht74Xyv6Ce1ws1akTI9jwPqkYA1tgO2AlSUZaFVL0h4FzVtWZg0pwQZqqCzYvFMdJq1t5/bjw7ZzA/mmB9vHbUWbBBNQHuex1twb6VkHRdHpkD61c9dNhcZ1WbLEWkt4tnI128DWtl7DBGNyFPeYwEWVrXb179Y7dSnXpA853EOvGQsS6uz6z8X2LhGRERERERENHH4Hfyo+eC9sAQl5D3kvWKT1NRWXUsJ6eda785JajFaanrnZ/lZI9vD661xdHsVML2lRwnKpPamqkV5qiULVfOz0Nni0u2N8PtzOhx4aTtgDvrcuFd7J8ug6nNKUHC0TntvL+vVIlG0O7BdO8PnF6kl15YTMm6G58I81+1ys9WslXRbp2Z98nj9atd+bZZTN3MybojUrrZq3q+lB6WV9rTG0SLFW1rmmlb5+RmJKAL5l/eYZPPW8sPfPI+391mAFer9JAgilSqIiIiIiIiIPPF7wPPOthYcPbhXH4h8LCd5D3kvf43/gOdTHYMAywDGRT1qcF8Z6Hs+0uqdBxRu3j+M8qvV4J1z+vHu08bAws0tOZi3Ug3wnRuPjt6T+uu4voYMFj61xxiEXDi/XipqXnoeB7VtnbezfEbEI2/WApyxzNNgx8cRX3IBprW8iffU4KE9PfmonA98uM5o+eH0OROOY1/26Tht8B0c6nL+7lbOA54fR0vKYlx0Tj6OVO/CwYZ8LL5avV5pJ97S90c76j2ud/leLgNGe9rfRCIyBjzPcQzw3VWDE1nzUZXTjv1y/jbuRdfMj6qBzGcjZf/fsXGfXIMu12utdbtipPQlob/hHezekYjiSy8y0oFpp1DTNhOn58sgwPVIK1mGM5YYgxS/vd/L9avSE9FzZJflPRahuH276p5P+w7LTseZMrj5zje1z38uLlcDnss2z28xrn1vaZlTWjUtFX0pfTjJAc+J/DL+aVgWiqq0H9xDxm+s57yH+n1X6UJS3X5kLJqPAf06L8KMJadjkRoEvd5LOuFXHic9Hi3tM3C5GvC8uP09vLzTPe9BRJGDA54TERERkS9jOeB5XGFx2bCaj0o2m4fq0QHIygm6nQQRRYn21tEVsTOdIKLxxDSMiKIZ0zAiIiIi8mW0+cXC8plqzh0HPCciIiIiIiIiIiIiopjC4AcREREREREREREREcUUBj+IiIiIiIiIiIiIiCimMPhBREREREREREREREQxhcEPIiIiIiIiIiIiIiKKKQx+EBERERERERERERFRTGHwg4iIiIiIiIiIiIiIYgqDH0REREREREREREREFFMY/CAiIiIiIiIiIiIiopjC4AcREREREREREREREcUUBj+IiIiIiIiIiIiIiCimMPhBREREREREREREREQxhcEPIiIiIiIiIiIiIiKKKQx+EBERERERERERERFRTIkrLC4bVvM+paSmIiMjC3HxcWrN2BgeGkZnZzt6e3rUGt9stnw1F5ysnFw1R0Sxqr21Wc0Fh+kEEY0npmFEFM2YhhERERGRL6PNLxaWz1Rz7vxu+RGOwIeQ95D3IiIiIiIiIiIiIiIiCobfwY9wBD5M4XwvIiIiIiIiIiIiIiKKLRzzg4iIiIiIiIiIiIiIYgqDH0REREREREREREREFFMY/CAiIiIiIiIiIiIiopjC4AcREREREREREREREcUUBj+IiIiIiIiIiIiIiCimMPhBREREREREREREREQxJQzBj2W44vY7cfNnrkSeWkNERERERERERERERDRW2PKDiIiIiIiIiIiIiIhiCoMfREREREREREREREQUU8Ym+HH69bhZurqS6epytZKIiIiIiIiIiIiIiGjsjUHwYxmuWF6k/a3H5ofux6MHAFkiIiIiIiIiIiIiIiIKh9AHP6bkIUv+1h7EXvn7/kHUy18iIiIiIiIiIiIiIqIw4JgfREREREREREREREQUU0If/DjRhHb5W1qOSvlrtgQhIiIiIiIiIiIiIiIKgzFo+bEFf9soHV0V4XwZ8PzyUmM1ERERERERERERERFRGMQVFpcNq3mf8iZPUXPh0XTyhJrzzWbLV3PBycrJVXNEFKvaW5vVXHCYThDReGIaRkTRjGkYEREREfky2vxiYflMNeeOY34QEREREREREREREVFMYfCDiIiIiIiIiIiIiIhiCoMfREREREREREREREQUUxj8ICIiIiIiIiIiIiKimMLgBxERERERERERERERxRQGP4iIiIiIiIiIiIiIKKYw+EFERERERERERERERDHF7+DH8NCwmht74XwvIiIiIiIiIiIiIiKKLX4HPzo728MSlJD3kPciIiIiIiIiIiIiIiIKRlxhcVlUN7Ow2fLVXHCycnLVHBHFqvbWZjUXHKYTRDSemIYRUTRjGkZEREREvow2v1hYPlPNueOYH0REREREREREREREFFMY/CAiIiIiIiIiIiIiopjC4AcREREREREREREREcUUBj+IiIiIiIiIiIiIiCimMPhBREREREREREREREQxhcEPIiIiIiIiIiIiIiKKKQx+EBERERERERERERFRTGHwg4iIiIiIiIiIiIiIYgqDH0REREREREREREREFFMY/CAiIiIiIiIiIiIiopgSV1hcNqzmo5LNlq/mgpOVk6vmiChWtbc2q7ngMJ2giSQhKRlJyclITE4xphRtOUX7m5Si5RriMDw8BAwNQ/83rE3avDaj/R3EkLZusK8PvV1d6OvqRF9Pt3pVGg2mYUTBG8jPQu+MfPRVFGBgShYG05IxnJaEIfmbmKC2chbfM4CE5g4kNncioaULCac69fnEhjYkHWtRW5G/mIZRrBhMTUevLQ+9OVqaYstHb26e8VdblvVDWh4qobcbCX092t8exMt8b68+n9An88a67Jo9yKzdjzjJUxFFkbT0THR3daglIqLQGW1+sbB8pppzx+AHM9NEMY833UTeJadnIDPHhpTMTD3AERcfukahw0ND6O3sQG93F/p7utHX3Y2B/j71KPmLaRiR//qn5aJ3eh76S/PRWzEFA5My1COhIcGQ1A/qkbr7KNJ216u15AvTMIpWw1qeqH16JVpnzUPL7DPQk1eoHhm9OC2PlFWzB9mH9iDjyH5kHj2EeOaRKMIx+EFEY4XBDx8Y/CCikYz3Tfe2O5yT2SUPxqk5ovGRmJqKTO28Tsu26a06wklag/R0tKGzpUUPiNDIxjsNO/KDT6k5Q/G/PqXmiCJDz8wCdC0qQ++sAgzmhjbYMZLUvceR+n4d0t4/ioQ2pmmejHcaVr7672rOcPDpj6g5IncS4Ggvq0SbNrXOPhODyeHJJ8X39yKrZh8yjx5Ehjbl7H9fPUIUORj8IKKxwuCHDwx+ENFIGPwgAhKTkpCWlYO0nBykZmSpteOrs7UFXdr12d3WqtaQJwx+EHkmAY+uRdPRU1mk1oyva/dvQuG7e/H8lky1hgSDHxTphhMScXLB+Wg642x0FFeoteMr50A18ne8hknvb1VriMYfgx9ENFbGMviRkJlt+7aaj0qpqelqLjgpqWlqjohiVV9vj5oLzmjTifplakaZtoXBDwofGbfDVjAVk6aVIi07R1+OFMmpqcjIyUVaZpbe3ZaMF6KPKUJOxjsNa7t0npozZP+dtVFp/AzlpKHjnFlovW6J/lfG9IgUD8b/FauXtmDV2Z3aUhz21ydhYJC/+eOdhuVWfVrNGZp3/0bN0UQ3lJyKE4uXo+aqG9G44Dz0ZU9Sj4y/3klT0Dx3MdoqTteSkzikH6/TUpWorrdKMSApKZld2BLRmBhtfjEz1/tvuN8tP1KkgCJDCifGNgMvA6d2drajt8e/L82WH0Q0kqht+bHsVjz42Rk48PhdeGiLWhekgqlFqKw6HcXTp6Ozox3P/eH3uOKaj8M2yfkH4snHfqHmxs9Zt92Hm+ebge06rLvle3haLdnp+6YK5la167+M765VC6SLj09AZl4+svMmIz4xUa2NbIMD/Xp3WF2tp/TxQcgwEVt+rL7nYVTu9Xxd+3rMu2vxzUdmY6+n9GRcyOdZgVKZrduAW+59Rl/rtN7k9PjEJWN5dC6Zobf2GEqPnCCuqbKpFhsLnlRLhraueKzdlIWnXs1GuzY/UbHlx3gKfdrnnE8DunY+gTt+vE0tRYeB9Ey9pUfjwvPRM6lArY1s6SeOIP/d15G/YzMS2G0ojRO2/CCisTKWLT/8zoWHI/Ah5D3kvYiIJo4luP2B+3C7SwsRbPkJ7rhl9IEPUVlVhYKpU7Hrnbex/m9/1dftfGe7vtzfF0G1d9bc7XRD7ZEl8CFBj1tuYeDDVUZuHgrKZ+ktPqIl8CESEpOQnT8ZBTNmaX+jozCChKRhD+ORR6yThzSN7Fbf4xLg0JmBjy7setxI29bVaatLVuDB25YYm0xE2u1H+yVVOHHbJeg4vzIiAx/in3rdC3+z04dw82Wt+Mu3j+K689ql8jZFpHCnYer97rlWLZskDfC03hNt2wduxVlqKWxUPk0CHpJGPbqzC+nzb4qaNGowNR31F34U1bf8J45ccl3UBD5E15Ri1F76CVR/4Vuov+AqDKakqkeIYss55yzTJyKiUPE7+BGOwIcpnO9FRDQRFE8vw573d2HXu++gs92orXPk8GFkZmUjKTk5MgIgEtS4pESbkdYeG1BrrHWh3exbAh8MejhL1Y7nlLIK5E0rQVJqaG6Kh4eG0Nfdhc6WZrSeOI62xhPoaDqJjlNN6GxtRldrC7rb29DT2R6y80i6wLIVTkXBjJkRMz4JjcRRYG9MoQncxiQtrTtXS+q6OrrUCtMz+K7Lvnt6r0Q/gPTyJeEv5IwA3XOn4cRtK9F6+XwMJ4cukJvU0Ib092qR9fIuZL2yG5mv7kXmG/uRvvUg0t85jLT36pC6ux4p+44j6ViLepZ38RjGtXmH1JK79JQhfH31KTx2x3GUFfartRRZXNKw9U2Yt2Ysgwtd6MpdgW+uUYuas247G/lu6UIkWYLbl0k+rQsHthvBvq0/flPPr6XPX4HV+prI1Vy5AHs+83Ucvehj6MvJU2tHJ1HL+6Qfr4Vt7w5M2bYRRZtf0P6+grydW2D7cCcyaz9E2omjSG5rRnzf6LrzMPXaJuPo8muw99N3oq28Sq0lig0S9Ljppk/rEwMgRBQqfnd7lTd5ipoLj6aTJ9Scb+z2iohGEvndXkkNQO3ud61rYaHZVcEGFD5wE3K2WAr719yNRyr36V2hOHU/0FGNR7/2E7gOjXj95z6vt/KQ4Ifp7AsuwoxZs3Doww/R0d6GeQsXjWu3V2etuRUfWTYDrfp+MGtAO3d7Zf+u7AbGiQQ6svOnIMM2ur6qe7u60N/bjYHePvRrN+n9PT0Y6OtVj/pHWpqkpKUjJT0DyenpSE7LQHz86Lp7kYBL28kGDA0OqjUTS+R3e+UtDRNGOta6Mw/z9HRKChjN7eR5N2GeOTa05brWu7Zqrkb+fNW9netj9m6vnF/DqfsVSSf1gKqsr0bj/OyI6PZKPv/KXC2t3pKNm+Xz+UjP7Gmel7Q9Vg3mpOutPTrO8d583R9JDa1IbGhD0ok2JB6Xv61IOt6qvUFgYwsN2tL1QEzvaUXaX/fB1a84sR2PFq1XS771D8ThiXXZeGJ9jj4/EUR+t1ce0jBLPkvo162RnDhdj9a0Cir9GTlfpt5vyyFULAOelMf1Vq3ZeF07jVZa3tfjazl1/anSVHjrDtTMS4Yg7bO/rzVvZubXrGl7ZJHWHkcvWoWGpSvVmuCkNh6D7cP3YNu7E8ntzUhuPYW4wQH1qH+ktYYMqN5ZXI6OacbfAS2fNBrTNj6Lok1Gq26isTaW3V6ZgQ+rJ574Dd54IwITFiIKuYjo9oqIiMbLNjy0VruxrjS7QdBumpflYdc67cZYuh/IfdNRU1EK0/zoLsEa+Hhz0z/QfKoJLadOqUfHx9a1P8F3v+brxnkJlpbLbb12g908O0xdU0S+1MwsTC4tDzrw0d/bqwcXGg59iIaD+3DqaJ223IDuttaAAx9iaGBAbwnS0nAMJw4dwJEPdumv3Xz8KHo7g7tZksDOlBkzkZ5jU2so8qRj3mfNa1Kmuy21gEtQgbXutanXLAHWqrTrliewK/dsp2tZO63xpErb1sG5hrRp9T1GYNhMA1/PvcnYTm9JBqxT65/EDA/dTI0DLc2WAtTaLf4EMsw0z9/tY0PnsgqclC6uggx8JB1pRvaLO1Hwvy9o09+Q96vX9OX0d2uQdFS7qQow8CESWrqQ+caHyHvsVT34N/nh9cjcvBcJzTKwOXB9n6NiwUiSEofx+ctb8fu763F6WeBpLI0VlzRsWRseNYOSWnpSuNeRzqxrrsJHLOmRmVbpgddA8mW1P8Hr2mtdf9sSrF5Zhcb1LgEKb68l3aI+Xo0uCYaolmKrFzvSy1u0x/K1zxze1mLpyImIRNaZ2doj2MBH9sHdKFm3FlU/+zbmPfxNlPz9aWQd3ouUUycCDnyIhN4e5ByoRtGrf8HsJx/Egv/9F8x95F5Mf+G3yH/3NT2oEihpBfLhp25H95Rpag15JkFH4/p266ZNgp2WvIsEHd3zMsLxGo+MR7dzUcfYX/b9re9n131qMAMfb775Fvbt+1CfZJ4tQIgoFBj8ICKKBlu24YBZMLhsCSpwCG9pN7tnTcnT+4M3MujaJLWIcwt9ZsYzsjKdAh9CusD627N/0ucjVwly9Nrd6ZiXu8+4wV8vXcJIgYXnjHSsS7dN0ru5SkxOVmv8IwOLS7dVJw8fxLEPP0DL8Xr0dhqFeCE3PKy/dnvjSTQc2q8HQmR+sD+wrl+SU9OQX1KGSdMisHSFNC5dxjjVMq7D62ZrjNo2bUtl7XHk2AsbLS1AFGuBv3T/VGoPAJuWoDAXKL3EfA2jZnb+FO0muzQb6XX77J/B7JplfEngWvuAHdX4ux9d9p112xp9n0hrlonQxd9wYgKaPnMumlefhYFJgdWEju/sRcaWA8j/xaso+OFLyF5frXdtNVZSDp6E7c/vYOp3n8e0f/8jdrwDtHQGdltVMnkAj3/1OO7+xCm9Wywaby5pmHbNXW8W0ml5MCxzTmesrGlVoPmyp+/dgMb5N+mtwVzThUBe6+kT2bjZ3M7SAmSiktYetZd9Evs/eRu6Cl0O2AiSW5owbeOfMe+hf0flb36AwjdeRnqD0QXhWMg4VoMp2zdixvO/xNxH/h+KX3kGKc0n1aP+aZl9pt4N1skF56k15G4bHtriuSvJ1ZXGOdK1c4NLC6kSnGsNlKxZ4ZZXIV+Me7fGE0YeUE/TOtpwWF9yVlJSjA0bNuKXv/y1WgN9XtZVVs5Wa4iIgsPgBxFRVNiGtw4CFYu1zPriGcDBbfqN9vRcx6CT9mmErlFkzI8Nf3vBHvgQEhC5eo1z1zaRq8to9SLW7lMFmiWo9FArPJZlTZ6C/OLAAgHSyuPUsSM49uEenKqv01tohJsEQqQVyLH9e9B0tBbd2k1QIDJzJyG/dAbHB4sBev/29vTrCewKuGGQ3FS7Bl2M2tf6DXakUYUmfrXikBrf86F/N3s3XjFsKD0ZjbcsR/f8wNI0GZPD9sdteguP3Ke3InVPvXokfOJ6+/H7V7Nw5X8W43/+MAl1J5PUI/659px2/OrO45hsm5jd+kWsLcfRiDwUSqUTuXabN9jTmHU+ysEDz5c9g7/vrMOute7b+P9aqkWwmRZKqxD1SPh0oXX8I8y6roKSoFp7pDYeR8m6p1H1yHdQtOkvSD3VoB4JnyQtTzR18wvaZ/h/KPn7WqSdPKYeGVl/ZjZqVv0zaj76GQyksYTeI/O+IXMGltobE1wLI/bhGMvGyjGejarAQP5bVoh8S9ogaZp2E+AxPfzDH/6oT65knTUgQkQUDAY/iIiixNbth4DyFfhIeZO9FrXUhnaqvSTNif3o9qrhmHMBUfnM2XoAJLLVodVr4Wjk3HSHQ17xdOQWuPc7783w4KA+WPnxg/vQ0dQYEWNnyGfobD6FkzUH9c8l3Wz5Kz07Rw+AxCckqDUUjfSbYDuzZZeD3oJDZxQ41O5VQU+7Z7C3Ll0PCpuk/33p9krSy66S2Y4WYfoN+PgyAzL2lipqPBKjZrej9Zre3Yb2WNfOtRHZf36oyfgeJ29bid6KyWrNyFKrj2DKj17G5J9vROab+5HQEZqBhEdDxu949o0sXPdfRfjGLydjT53/LfKmT+nHo185hqmTGACJGHqa0YTjZitbO6PFmTfB5Mu2/vh7Hq91/1/LJf2Ulm9qNqSkFbKeD1NBISfGvhpvndNmYP+aWwNq7ZF+vA6lLz6lBz0K33gJid1j1BI2AAk9XSh882W9Jcj0F5/UP6O/Ti68AHs/cyd68grVGnKQfIP8teQdzPxBh9Gq3qqrQ8KIJVipXXdma0xo68IfXIw2RndXRis0R5eCeqs5lzwPEVE4MPhBRBQRXPqa9nRzKzedWga8tNnRlQvWfg+PHpzh6OrA2ke1xZHDNag8/XR9UHNPkzwm20Q2af0itxuWG5Y1s41+/D3csMSiuIR4TJs9Fxk2/wd/lWCHBBck+CFBkEjU19WFk7WH9O63hoeG1Vrf0jKzMVkCIImJag2NL5c0zI+xeMzuXoztZVB0OMYDEeVr1GM36bWuza6fDjd3ofQS4/WfvvcJHCg3X+NhnNusuoiSPvFl4GC1/pE1M4wnj6OtP77LuQa33m2fRgY8V92EWQc4f3ICtPgYmJKFhn+9DP0F2WqNb3H9A8h5/l3kP74ZybXjO06VN8NaErZhRzpuvH8q7n0qDx3d/t1uSeBDAiDT8gIfR4BCwSUN+6xjDI6tP16LXblm91NrgIN1WhrkpfDOz3yZX3y9lrRMyazSHpO08Bl8V8ZTMj9/ZRt2wRhLxKg4UoKVIRmfwOw2yJEPkxZ8kg9z7y4o/Nqnz8b+NV9G76Qpao1viV0dKH3JCHoUbF2P+P4+9UjkiB/ow5StG/TPWPry77XP6N84QdL65cDHv4BeWwS2ghxnElQU6blGgExa1EuwsEu1qndy8E2jVWrJCuO3GV3YteWQ/hD5oqUVX/syHt2p3bfpYxNJvmeD3uqmdr3Mu4xvREQ0xuIKi8v8KmXIm+xfJiJUmk6eUHO+2Wyjq8eXleN/ARIRRaf21sAHD7QabTqx7Q7nZHbJg+HvrqdgahEqq6pQPL1MrXEmgY+91dVuLULCTWptu/albZCubdRg6FLz0awxLaTgMNiChSiSnJ6BwvJZamlkXa0taD91cuzG8hgjKWnpyCmYqg/k7o/erk401tUEPIZINBnvNOzID5y7xJMBnykEzLTMnoZJTUn3sU8MdVgXY4UF/cWT0PDVS9XSyKRLq+yXd0Vs0MObyTmD+NY/NeKs2f61TjnVnoBbfzwFB48HNpZTJBvvNKx89d/VnOHg0x9RcxQMe5BWka65xrt7vrbyuThw3Rf87u5pUvU2vWurtBNH1ZroIC1bjlx8rf59/ZF55ABmrv0Jktpb1BqSbq6++cgKlOq/qxtQqP/uWu4zNOY5rp/bJ1bY7zv05e1L8KC0aJBC/RG6Go5FaemZ6O7yr59S/b4OKo+z7FZtv83AAct+9ubOO+/Q/95//4P6XyKaGEabXywsn6nm3I1B8GMZrrj9PLh2xtH+3lr8YZP/fZIw+EFEocLgB0W7xOQUFM0+TS2NTMb1kBYf0Sx7cgFypkxFnB+XS193F5rqatDfF3m1NkOBwQ+KNX0zJuPEbZeoJd/iBof0oEfWht1qTXS6amkHvnpNM7LSRh7cvL0rHl/6cQH2HY2NAAiDHzSWWmafobdyGNLySiNJbmvWgx6T335VrYlO9RddjaMXrlJLvmUd3qsHQKSlCxmMylYS8HgTOZ9dgVKXQIZT8OPHUBUTVCUEvRCfwY+RGRU6Kg6q4Khe4QN+VeRg8INoYhrL4MfYdXvV9gGefeh+PKpNz77Xiawz1uCK09VjRERE5Je4uDhMnTVHLfk20N+HhkMfRn3gQ7SdbMCJQ/vR19Ot1niXnJaOvNIZSEpJVWuIKFINZaT4HfhI/bABk3/6StQHPsRf38rEZ+6fimOnRh6rKCt9CD+5rQGFuewCi8iX5rmLsP8Tt/kV+Mh/7w3M+dX3oz7wIYr+8Rwqf/MDZBwduQum9umVOHjtLRj0Yx9NFNJ9pt5928rZ+ngfHru8stuGh9ZWY5fqBo9Gpo9dJl2WZmp72ezeVG89U6J3RypjsxERhVNYx/zImqT3zE5ERER+Kpw5Rw+AjKSnvQ3H9u+Num6ufOnt6kBjbY3esmMkyalpyJ3q/yDwRDQ+jt95uZrzLevlXcj/2StIPnRSrYl+RxoTcdMPpuKD2pFbdGSnD+GBW04gOdGvRvpEE0735CIcvvwGDMePXKQhwYIZf34MKadiJz3JPrgbc351Hwq2rFNrvGutqMLBj38BwxwnTbd1+yF90PL0khKka3MHto/QbduWn+AhNe4YjUwf4+zxam3PSusaY5yzdTLUij7G2ZftY7h5U1d3RJ+IiEIlrMGP9lP+d3tFRBQrpJsr60Tkryll5UhKGbmmXltjA04cPhixA5qPxkBfLxoOfogeP5rYp2Zmw1YwVS1RqEg3V9aJKFgtH1+Moew0teSdtPTI+fv7aim2NHck4PM/KsQbH4y8H2YW9eOe65vUEgVLurmyThQbjlxyHfozc9SSdxL4mPbq82optsgA6DIQ+tTNL6g13undg117i1qa4LZswwEzW9lxCG+NMAYFBc4YSL4Jx/V9ey0qS4CuZmOw+ZH84Q9/1CciolAJ25gfqH0Njz7n/68Kx/wgolAZ776miYIxqagEmZPy1JJ3TUcOo7NldOd4tJgyowKpGSMPhC4DoMuA77GCaRjFgs4l5Wj+5FK15F3Wq3uQ8/y7ail2xccP498/eQqrlo4c2P3hs5Pw1D9GTvsiFdMwCrWjF30M9Rd+VC15F8uBD1d1H1mD42dfqpa8m6btk6IJsk8o9AIZ8JyIKBBRPebHZtXYo/4Aw+lERET+yM6f4lfgQwr5J0rgQ5w4dMCvoIa0/pBB4okoMvSWT/Yr8JHx+r4JEfgQQ0Nx+O6TeXjkRZta491XPtaMBTN71BLRxNYyZwEDHx6U/H0tpmx7RS15J/uuo3SWWiIiIop9Y97t1d43P0C79rdo0ZUYuRiHiIhoYkvLyoGtcOSxKzpONcZU6wZ/ScCnrdF361AJfLD7K6LIMJSThpZrFqsl7zK2HEDuM2+rpYnj0Zdy8MQ63133xMcN4/7PnUR+dux1bUgUiL7sXNStuE4teTfRAh+m6X/7HSa/s0kteTYcF4+jF65SS0RERLFv7Mf8OPECdkjrj+zTcMkFHPCciIjIl6z8kbtz7Olox6n6iTsQYMvxenS1+Q78pOfYkD25QC0R0XhpvmYR+ot8t25If/sQcp/eqpYmnp/9LQdb96WqJc+y0odw+6qJ09KPyJMjK69DT36hWvIsf8frEzLwYSr7y6+Qt/NNteRZW/lcHDvvCrVEREQU2xIys23fVvM+pWdkqLmRHMGHW9/AO+99iG61pmmvtqytqz7cqtaMrLurU835lpqaruaCk5I68mCDRBTd+npH11UE0wkKl8zcScjK8z3GVn9Ptz4A+EQnrV4yc/MQn5Cg1rhLzcxCb1eXPmh6NGMaRtGqe0Ep2laerpY8S99Ri0m/811QF+uGh+Ow6f10rDizC9npQ2qtu1nT+vHmB2k40Zqo1kQHpmEUCicXXoBj51+lljzLOHII5c/+AvED/WrNxJS75x10FZaiJ997K9iOkpnIObgbye0TrxUxBS8pKRkD/X1qiYgodEabX5SyFG/GvuUHERERjSguPg6ZkyarJc8GtZv5piNqMC1Cw6H9as47dn9FNH7az6tUc57Fd/Ui++Wdamli6+iOx9cemYKu3ji1xrO7P3EKcb43IYpJJxdfpOY8S+jrRenff4/Eni61ZmIreu0FxA0OqCV3Q0nJqL+A3V8REVHsY/CDiIgoAmRNmozkNN+1W08drUNfj9mukqRFR2PdYbXkmexTaSFCROHVcd5s9JX57sYv65UPkHhCRgckUdOQhG/9xvc+mz2tD6uWdagloolBWn10Tp2uljwrefn3yKwbuVLERJFx9BCKNv9VLXnWMns+jp/9EbVEREQUmxj8ICIiGmcJiUnIyvPd6qO96SS629vUEpm6WpvRpu0bXzInjTyOChGFzlBGCtrP993qI7mmEVkbP1BLZHp1Vzo27PDdre+/rGpGeor37rGIYs1IrT4KtqwbcaDviWjq5hdGDAhJ6w/pIouIiChWMfhBREQ0zmSQ84SkJLXkbmhgAO2Nvgv4J7LW4/Xo6fJeE5qtP4jCS1p9DOZnqiXPshn48OoHf5qEvgHvfVvJ4Oe3XsV++mliGKnVR/rxWpS+/Hu1RFZxQ0OY+trf1JJng6lpaDzjHLVEREQUexj8ICIiGkfJ2k1n1iTfg5xLqw8OLujd8PAwOkYIDrH1B1F4DEzJRvsFs9WSZ+lbDyL1/SNqiVw1tiXgiXU5asmza89thy2DrT8o9o3U6mPy22zx4Ytt33uYsvUVteRZ0/xl6M+0qSUiIqLYwuAHERHROMrInaQPdu5Nf08PW334oautFb2dbP1BNN46zp+N4dRkteROH+R84261RN78en02jjcnqiV3iQnARzn2B8W4kVp9pJ2sx+R3N6sl8kbG/kg5dUItuRtIz0LTvKVqiYiIKLYw+EFERDSOUjOz1ZxnMp7F0DBr9/qjo/mUmvOMrT+IxpaM9dG1wPegxBzk3D/S7dUDz+SqJc+uO68Ncd5j50RRb8RWH+9uQtzggFoib5I6WlH45t/VkmdN85apOSIiotgSV1hcNqzmfZqUN9lnzdRQGh4axqkRBi812WyjK8jIyvF9U0FE0a+9tVnNBYfpBI2VtMwsTC6rUEvuejrbceLQAbVE/iicWal3JebNqaN16GhuUkvRgWkYRYvOJTPQ/EnvBWiJJ9pQ+P0X1BL54/ffOIbyQu/dHn754QJs25eqliIT0zAKhrT6qPnoZ9SSO2nJUPXI/0NCb7daQ74MJqeg+kvfQa9tslrjruJPP8Ok97eppeiTlu57rCmiSNPtY8xCoolmtPnFwvKZas5dQma27dtq3iepdZqUlIy4Ma5eJIGPzs52DA74V4MjNTVdzQUnxUcBCRHFhr7eHjUXHKYTNFay8iYjJT1DLblrPlaPgb5etUR+iYtHWpb31jTxSUnojLLgB9MwihYdl1Shv9D7WBUZbx1A6ocNaon8MYw4nFflvXA3JWkYG3Z4/x2JBEzDKBhHl38MvXkFasnd1DdeRvYhdqHnr/jBQQykZ6J9eqVa4244MQl577+llqLPQNwwBoa078mJU5RMGGTrfiLTaPOLmbmT1Jw7v7u96u3p0VtjNJ08MaaTvIe8FxERUazL8FGbtV/78e9ub1VL5C8JbPgKGKWkpftsGUJEwRlOTkT3adPUkmdpuzjIeaBe2JqOnj7vt2wXze/iwOcUc/q0/FHbzNPVkrvk1mZMfocDnQcq7703ET/Qr5bctcw+Ax0ls9QSERFRbOCYH0RERONAuryKT/Q+mC0DH8EZHhoaceyP1IwsNUdEodI1vwTDyQlqyV3q/hNIro2uVleRQAIff9vuvWUHBz6nWNQ68wwMx3kvqsh/dxMSuzh2UKBSTzUgb+ebasmz5tMWqjkiIqLYwOAHERHROEiz+e7DvLutTc1RoLpGCBylZLFPaKJQ655XrOY8S9tVp+YoUGtf9Z1mXTSvS80RxYbWmVVqzrPsmr1qjgKVt3OLmvOsc9oMNUdERBQbGPwgIiIaB5kjdHnV29WplihQAz096Ov23kd+akYm4uKZBSIKleH4OPTO9d7lVVz/ANK3HFBLFKiDx5Pxfk2KWnJ3Wmmv3gKEKBYMJySitcJ7l1cpzSeRdZjBj2DJvsvZ/75actdZVIah5FS1REREFP14509ERBRm6dk2IC5OLbnram1RcxSsvi7v3cDEyaDomd4HRSeiwHRLl1fx3tO0tJ1HEDcwqJYoGBt3eh+rSAIfc0u9j3VEFE2aK8/EUFKyWnKXXbNHzVGwMuv2qzl3Q4lJ6JhWrpaIiIiiX1xhcdmwmo9KNlu+mgtOlo+atxRBci/AqmsrEWxHJQ2v/wIbfOaTU5BZfCaqTi/FtPwspKZYqs8NDqKnpRlHD+1CdfV+dAyo9RQ12lub1VxwmE5QqOUWFSNrkvffr2Mf7tFbf1DwJMCUX1qmltx1NDfh1NHo6IaHaRhFupaPLUTH+ZVqyd3kh9cj5eBJtUTBmDejF4/dcVwtufu/53Lx21ciM6jLNIwCcfCam9E0/2y15K7ijz/DpOptaomC0TbjNOy98U615G7aK8+gaPMLaimKJCepGaIo0devZohotPnFwvKZas4dW35QdEgYq7b8CcicvhyX/9ONWHXpfFRMszkHPoT23ql5+ahYvByrbrwBF51WoB4gIgpOcor37gQk6MHAx+h1d7QBw97rd7DlB1HoDEz2fj0lnmxn4CMEPqhNQW+/99Y1Z5Sz5QfFhpbZZ6g5d3GDA8jd865aomBlH/rA54DxncXeC5CIiIiiDYMfFB1SEuC98XOwEpB/1mqsumQmcr13o+wsLh1F56zCqmUlagURUeASU7wnOr7GqiD/DQ8NocfHuCkJSUlISvHejQwR+a8/z3vb3OQjTWqORkN6Ddt92PtvxxnlDJpT9GufXonB1HS15C770B49AEKjl1nrvesrGfeDiIgoVjD4QdEhKz3kwY/UOVfionlZaikwmVUrcdFM7xlzIiJv4hMSkJDovVl+fw8LsEKlR1p/+JCYHPqwOtFENDjZe34qscl7EJICs+Og9+CHLWMIxfksFKboNpDhu5NjtvoInZyDu9Wcu/7MbPROmqKWiIiIohvH/GAfstFhzipcf66lu6n2/Xhx7UYE3SNc3Fycf+O5KElUy6bhLtS/9Qa27D2EnoEEJNsqMf/CZZid76HbrZa9+OufNsF30RpFgvHua/qff/5pNWf45Rd+o+ZoIkpJz0BB+Sy15O5k7SF0t7WqJRqNkfa1jPkhY39EuvFOw7b+6LCaM5z1lelqjsgIfBy7+yq15C73D28hY+tBtUSjcc5p3XjwiyfUkruv/nwKXt8deS3axjsN2/atx9ScYcl3PqfmKNKcWHwRDl/pnG+2mvuLe5FRX6OWaDQkuLHz9v9RS+5mPfkj2D7cqZaiBMf8oGjDMT+I7DjmB014ubbgWmh4Ez9rJqZ5CHzs++vv8Y9qCXzIikH0tezG9ufXYZ+nLlFtJZjJ2BkRBSjJx3gfgi0/QmdgwPcNRXyi6w8BEQXKV5dXIrHJe7/yFJiaBt8FezkZQ2qOKDoNpPtOT5I6WDkkVFJOnUB8f59acteflaPmiIiIohuDHxSd2lqCb/WhGdr3PH7/m5fwVnU9mlX3+kO172L7iUFjwWq4DvtqutSCVTpyOfY5EQXIZ/BjeBgDfRy0NlSG+n13AZPA4AfRqPka7FwkNnWoORqtlk4PLZEtbJke8rFEUWQg3XeFt6QRurOkwCR2e++WsD/Dd9oena7GN398J1arJSIimhgY/KCokJnu74jkAeirw4EtL+DFJx/HH//yFl7f4b3f07Y21lqkyHfWbffhwduWqCX/FEwtwgWXfATXf+7zuPoTn9TXXXHNx/Vl6xRJ5Hs+8sjD9inQ7zzeElN9DHbey1YfoTQ8PIShAe8BEF9jr1AILbsVDz5yH25fppYDtPqeh/HNNWrBha/HfAn2eaHgnIbd7VYIE21p3EBehprzYHAICS2qlgmNWldvHPoH4tSSOxn3g2g0JG20pj/m5Egvr8U3XR+/51r12Oj5Cn4kdrUjbogBvlBK7PER/MiyqTmiwJ31xf/BIz9+0Hm6+2r14C140OkxBqRoHOQvx6o1y8HOXCYGBj8oKiQnO9d06+tUwYjEIsxevhrXfsZSUPuZG3HVRy9B1VR/M2yD6DuxE3WNatEDz91uac9jBW0KlTV3O91IWgvlpCBsrArpKquqUDB1Kna98zbW/+2v+rqd72zXl/v7vDeFHzfafrp5fjq6dj6BW275Mh7d2YX0+TdFVQAkKdl7y48BBj9CbqDfe9dXbPkRKktw+wOWgjCZHrgVZ6lHseUnuOOWu/DQFrU8kak0zKsoTON8tfxIamTlkVBr7vDe+sOWwYLhwBnpl9s1pgdtjeCkEZB0D1QagQL39dFrCQqlFKijGo9q6Y+kQeb03bXyuAQ+VqAUXdj1uLF+XZ22umRFyNIoX91esdVH6CV0eW+Z15/Bbq8oeNNtaVpashuP3nYHbjGn7z2nPXI1vnnjXKSbj/16t5aiFGPl925x5BuJnCzAis99HquWFallUYTFa27A4tENAe0fCZJctkAtULRi8IOiQDqSXCrn9vV3AWlaIvipK7G43IZUa/lVYgqyp8zAGVesxnVXX4CCUZdtpaMg30NBxXAzTh5V80SjITfYlwDr7DeZG4BLwlMruXh6Gfa8vwu73n0Hne3GDdCRw4eRmZWNpOTkCAuALMHty0q0v104sH2bvmbrj99ErfY3ff6KqCl8SEhKVnPu+jjeR8gN+hj3Iz6BwY/QcRSGyfTowRm42RoAIZXWSxpWp6X3G/S0y1l0pnHDSd5vJxJPea9VTMFp6fS+v22ZbPkRuG14aG01UL7EKb06a/EMYOcGPK2WuzrysNLawkG7ns/N7dKu1onkGXxXT+Mdweyn90r0Q0ujXPZfsPp9BT86Od5HqPns9iozFru9crb67gfxyPduwaeklYLZKkFndo9l/L3d3orhf3D7WYtw+/fYYsEvLQ3YqmYdnsN3Jehx9yPGY1sboNdB1c636fKXyIvMqsWoUPNEgWLwg6KADZlpatY0ZMMZly9GgfcyRF1yfiVWXLMc+d57CBhZ/lLM9jC2x1DtXuyJwIrxFKU62nBYzZo3l1LLTmoVSi3g0ksednQr4NRKxHtXMk5dpwRQCHn2BRdhxqxZOPThh3pgJGIsW4IK/Z64CcfdapDnoTDILnXCbth74VR8An+WQ813t1cMfoyVrT9ei12YgaX6dSm1hc3a0c6tRKy1hZ26W3FNs6ZILWz1mNcuVry/tlNXLdprF6q14XRWKdDYIUGi79kLVJ3EShpnkdDI8T5CzXfLDwY/grJlGw7Y0yuxBEvLYQ9C6g6+iV25Z6s8l5bWrKlC45Y3jUK7CeysKXlqLjR8tvxoZ8uPUPMd/Ijtlh8S+Di35Td6IfxTP/sjdtlm2QMZZ31xGfLf36h+q4tRgT/qLRcefR+Yd+N12q2a0ZJh3ZFinPvFRfpWZLUIhXonHLPwTXvXVhI40h909vFZ0LJH6LLvbyJPGvDW68BSry0wjNYhZm8wK+ao1S5yl93g2GamWqmTliSO519vvs+cVbj+6pnInLZYW7/KCL7IOnO7z4Wp9QmNmt+lLCmpqZiUNxl5k6eM6STvIe9F5MtQQRVO87dzvuyZuGj5XLUQoLgSLF6uJXZq0W64HR9s9z5GCFFAtvwErzdX4WZVMGdt8fH0vUaXArXrv4xb7n1GW6PdcE/ZZ69dfcv6Jsxb6aEgULpOyX3Tsd2WbNzsR5/M1sDHm5v+geZTTWg5dUo9GsnSkSM55ygg41B4ExfH4EeoDQ56D37EM/gxhrbheLOH63LNCsxr3qDSpidwoFy1aFh2Kwr3qvRKm9ZpaeJHLGlhaTnwpPkYVnhsGbf6npuQs8XxGq/n3qS2k6DICkDSUXlsLVAhDSzCbOvan+C7Xwu2+6/oSeOs4vq9X38UnJYOHy0/2O1VkLbhrYNaurBYBUwlEIlD+P/s/Qt8XOWd5/l/JdmWrbts2ZZlS7ZlYxmEzc02AgcSY5tASKCbHpxu0jPNEOLZJrBLZ5IddgdmMhP2v/lvshn+S8jOEMIwl7g7ppsMJCTpGDdJaILBDgQbEcv4KtnyTbIs62LLF+l/nnOeqjp1L0kluar0efMqfM6p0rlVnec55/k9l3fCrtU259p17rk2PKxVNh3zuoLKJbUqNw89XdKN/q4MEwScb6z3Wse3Ovey0TW8hy/RmB+0/Ei/idryo+Hxp7Veb+ix//g7u+R3eufgDBvIuEE3LpD2vRd477Desp97970D6u910gb7Yz/EmFZx/E7PPP6GWudJLaaVxyPOtKZp2T+LaCljxv5Y43zosP+7AOLYvUN7ylbECGyYwMUK6a3va9MPzOs1dS+PEZRYerfurGvXz93PfF8HK3zlfEsbpH8I/f2esqu8v9/9qja9dVw6ssNZ/qr2mW1VtNnPOa+3erRkBV1iZYOUS1mKi0uVlz+a6vOpMdsw2wJCZqs84idRNqtC+X3H9cHfv6q/fcFLeP5m0+va0Rp7EI4pC5frmmGP2VaqRZ9eryVR932XdPy3r+mD03YWSAMT5PAK7Lwur+L3Ie08oOum0AOp24VKNLcmXu3a8M9VVids/VFcWhIW+DBMF1g/+/HfudNIj6GhITsVjeBH+v3+6iP6u3Xvx3z95Mb37acwblrPqN+mTU9s2K5nvmJbQWzbLjXZ9Mp5rY9I2vwFa6ablbqGyMI4r696t5Wcbx1Vs0xhpinQa1NLoKDSBJy9nlowxoamJmmii2H7n7vWq+bi4zFfN1f9S/spDNe7Ow4Eu75yu7zavz26MH/zN93A7EPrZmjXFlMhJde8LCd5dZJM5/g3e/elgTE9YgVAVj2yQctK5I5RlHuBIOSuedLHj2mL1uiJP7GLHCawoQU3aNWqG7zgZzqieROa6d7q27Y1x2F1uw1BZ6g68DBqAh//7Crpw/9mxwIBkmnXjn/Yq+LVtgVGkCkvPK6Du+2s87l9rVLNYv8YId44vr2tLSa+79q3Y6+C7ZN3n1b5PYHWHHdpSdwiaWfduirU8mN1jC5ikJFSLmUZj8BHwHhuC9ngI721+XW9ue19fdDcpvbWDnW17tCrP3pVzYeP67wtRxw8e0B7tvxY7xyJVeutVLWLhtEeLa9CS+64TzfOje5aoLd5i7buZgBPjBWvy6stbbVqiDnmx726fXlnaHyQ12OX4M2vDA2YG3x9JXGtPDPmx9afvRYMfBgmIHLPhj+zc5msX93RHehnpKHBBMGPfIIf6Xap0E7EUBA7Xo60MMGIGNelO/i5SZMiAr1hLUJsgduwmABH+Lgj5vXYd01QpVrZ3yI9e9I4v8GpEYO2YdQulkf2BRuSf4ZayCMW7PoqRpdXPi9taVbrzs0jbMGV+bzKONFjekRVoDEtjJfLTXPddDZNJvXHf8ZiAO70uzit2E5Fy90B5g+r5e+c3/Y335DW+FoivPs7Nw24/baF6thux6PAiLnjqQTHUZnntSoLtJr5k6/qOTPoOS0+MFwdb+jN5lI1NvmCDlUVip+ShUyviDGOr1XZdJWKm1+zLTpe0564WdF1ur6xR+8EW34ct8uR6ShlQRYY0LmeA2pr3qHmbb/Qr7b8WD/f8r56Y5Yf9mjftr2KdatWNjPFPi4mLdQ1d9+rFVGBj0vq+v0v9Oq2YZfIAAl5Y3P4W3rcq4baOAVdEYV48fpaNg+rYYNPmnFCUuj26vjRdjvlqV+8xA2AZARTMBGoNRTV932sPvIzVaJur+wE0uZSgornBYzbNGbcGsFR3cY4nLTIG4vDBHpf1K5e73oOT8u8Vhx+XgsOY6U7KHhrS2Sta1NjuSjUbY3DjCHidnvlph3+gHL0+jNCzqRxIQQ/0m+wLP7D+ySCH6Ngu75avzZGl1c+276np9JY2J+N3PvWdbXqH4Mg0KT++OMEXSjN/QG4x1vi4EeudzP2ip56Q1ofHIvCdH0l1c3rdIMjGIVVG1Wtw9K8NXa8jzWqc+a3uIOc36BHV87zPhd8P8GYIECErm1vqL1uRah1RkeL2ntma0GwO6waLaqT2veGl2vsO3hcJXUNCj4CVJUGu70KD4xE9zwTFBFoMa1JkB3yquctiF8F1ceMxzGeOk+esFOJVVSMri5faXkmPv1idJbrUw/eqJrIQsQzLfrpS7+JGRgJKl2uW+66UbWR94FDPdr3+mt6p5UWH9mopzvQuHFkRptO/PP/9E/tlOc//4v/Zqd8THDC14WVGeMj0IWAecg0g56rbas77kdw3tG/s1kdyxvdvuyfkreOwN/6P6feZj0fo+XHrevWa9acOWppbrZLwjU0NurE0aP6zetb7JLLzJ4n06rF1DQMHGNgPhvMuaJBkwtj19w9e6ZbJ1sP2Dmkw0d/OqS+OKNblzuneskrmR9xutxp2Lv/v0N2yrPqf5lvpwLMmBoPuN2fBIWlOWaw8SVq2fhNHWpaqfnrHwh2axW6dv3r6NeunZ1atlza4vyNnnxWqyv7VVRi0zObFhpeGmBqH5uayuH7EZ4umH1Y6w6qadbf31ukjm2hdHb8BfanzT3G4CCfWZjGnXz4Ng0sit3sv3DfSc383ut2Dulw9H/7nC5Vxa6UMPWjdlX94Nd2LnNc7jRs+7/9gZ3yrPx3X7RTEZoe1tMPOvdUEdebuQ7v1+YY12AobcuNgXptGqpQ+m2CyCa9jkyT4t1XjtaeP/+Kuhc530EM004c0dX/77+xc0iH3X/xNfWESgzDVH70Oy1+6Xt2LktMIeCOLHP+gp1AfGYw81oddMfcsMyg46tLteeVH2pHh1lgPrNCgbvR4299X1uD3WCFVDZ9QXc22ueJnn71ql1vbn5DXWF/f1x7mku1xIwPYt6rWqO7zaDnZtB1Zx9O+dbR27xXfY2L3fFGYm0PwzPa+8Xq+rBR7MMQ/CD4kYNqteJP79CSyABGkuBH/pxb9el1DaqMrCXc1663XvuFDvUwiGS2yorgx2Uye06NG+CYN3+BXRLu8KGDbmAkskXI5RR88LYyuVAwlupFSzRlWmj//c719ujEweBtHdJg1z8b0rnpdibC9BZp0c8JfiSTPPiBVAUKEqOZLru8rmayLY1LFPyY3H5as//vn9s5jNZQfp7a/4/7NDQlultWo3jbPlW+lO7i6NHLmuDHRLfhYT3RsFB1teH3KKEKOTEC3UERgdwR2n/vRnUuu9HOhTNdYl33rcfsHNKh+S//nfpn2Vr4EWZt/wfN/9kP7VyWIPiBbEPwAwgi+JEAwY8JpnSxZhfs1fGEg42HR32DjuzQpl/EGty2QCWL79Dtt9ZoamQZ2Om92vrKGzp+0c4jKxH8QCaZ5WTKU4ti19o9f/asju1rsXNIh989MqTBSXYmwqyd0vx/IPiRDMEPJNL55zfr7HWxfxOTTvWp+v941c5htC5VFOnok4E+1KOVvd6ssp87CVuGIfiBVLXe8Wc6fuM6OxdtxTc2Km+QCmnp8sFffVvny2JfX3P/4WXVvPmancsSBD+QbQh+AEFjGfxgzA9kjal16/TZ+9Zo7W2rlbDH17kzQv34+fSejjUYUaFmr7hXd3wyOvBx/vhO/fRlAh8A0uvS+fgDTUyZNk35BWTN6TLgZBbxAh/GFHoyBEZtypH4DyqM+ZFeiQY7N/J7z9kpIDslGvDcuFDCuB/plHDMj75cHfAcADDRUMKCLGACFPfps+sXqswEKCqv0rrbrtJU780IpVp6/UJFj287oOOHIrrtyavQ/Ns+r7XXVER9/tyBt/TTn76jMym1iwKA1A3099mp2KYUxX8QxfCcnZE4EZ/m9hELYDQSBT+GCH6kVaLBzo383vjBdSAbJBrw3LhQUm6nMFoDFVUanBz91BwwuSfXBzwHAEwUdHtFt1cZrtANUKxeWGjnQ853HND7297RgeM9GlSBplQ0aPknV2hJVfRn1fWRfvryW77xPmrUeM8duqYqus/kM82v62fbDjjrRK643N0tAH5Tpk5V9eLYg0sa3SeOuS+M3tEV0uFPxL/NueaFPE3JgoqNpGHIZJfKp+nov/kjOxfNDHhuBj7H6PV9Yom6/vgGOxdt5n96Q4V7Mi//IA1DqrquukF773vYzkVb8JP/opnv/cbOYTROXneLDt79gJ2Ltvz/eVyFXVmWdtPtFbIN3V4BQWPZ7RXBD26mM9+sNbr7s4tVMtJu2Yf6tfvVH+q9sBq+ccYFGa6evfr55jc0uksUY42HbmSaeUuvVv6k2P0xDfT26vjBvXYOo7H/Dud+Ymns25x851njhmczf7wPgzQMme7oE3frUmXsVmslb7ao4n+8Z+cwGqfvXaHe1VfYuWhz//XfKe9c5rX+IA1DqnrmN2j3A/+rnYtW2fK+Fv/Nd+0cRmP/n3xJnVc32blwk3tO69rv/Es7l0UIfiDbEPwAgsYy+EG3V8h8J97QL18/oN6UwnQRhvq17/VXIwIfAHB5nUvQ9ZXp9opxP9LjTG38jKPoZHYEPoBsMOXIaTsV7eyyOjuF0Tq7fJ6dijbl8KmMDHwAw1F6qCVh11enFy3XUEF0y30M35mFV9qpaKVtVMIBAOSO9JSuzLpLn3/0q3ronvv1GfOvef3FXZph3264xy6zr8/fykMQhudc6+t69aXfaE/HgF2S3OCZdu348d/onVZGtAWQWc6f7bdT0fLy8xj3Iw36Z0oXEpzGkqN2AsCoTToWP/hxqWKaBurHtwV5Ljq/cKYulcYf8Lzw4+N2CshulX/4nZ2KNjSpQGfqG+0cRqq3drFzjxR//JSSVoIfAIDckd6qpXXSvme+rR9/0CeVXakbrzYLm6Suzfqxs/x55/Vmq1R6zV26mWcgDFdPi3a88l/1Ny+9oQ92d6irb0CD/kq9Q5d0fqBHHbs/0ps/+aHzude0p+uSfRMAMkei4IcxZWriQW2R3Ol6OxFHxQE7AWDUJh9PPHjO2atq7BRG6uzV8Vt9GIUHGFcFucF0bZVId338FgtITc/8JXYqtqJjB+0UAADZLz1jfpiWH5+/UqWt/6jnX9kmXX2/HlpTo/Y3vq2ffej87a1/qT++xl/9sk8f/ej/1W8TDOvBmB8A0oW+ppFpzHgf8xoanVw4dtdLFwYGdPTj3c7USPr7g/HR/UPqi3PrYsb7uP57ecrLktNLGoZMd3FGqY4/fpeG8mOnaZM6elX9f/7EzmEkjv3ru3VxevzmbHO+/mMV9Jyzc5mFNAzDYbq1eu9fPaPByYV2SbipHce07Nl/becwEnv+/K/UvcitqRol/+J5Xfd/PebcK6Xe40LGYMwPZBvG/ACCsnvMj1l3aZ0JfJjASKBVCAAAE9jgxYsaSND6Y3JhoYorKuwchuvsdMUNfBjlB5U1gQ8gG0zq7NHUj47YuWgXq0p0rqHazmG4zl0xO2HgY8qBkxkb+ACGK+/SJU1P0PXVuapqt9smjIwZVD5e4MMoOXwgOwMfhilI5sUrm14AxsU4BD/KVer809PV7s5WVSbogBsAgAmiv7vbTsVWRE3XETtxbeLIhgl+AEivac3xgx/GuSvn2ikM18CVibsNKzzUYaeA3FC27yM7FVt3/VV2CsPVubzJTsVWdJSbJABAbhn74MeHm+w4Hxvcwc4Xdf1BPSrWVXeGBkQHAGCi6T/TpcHB+OMSTSstU+E0xv4YrktThtSRpDvwyn2xu+YBMHJTm48ov/+8nYt29to6d/BzDN/ZJIGjKa2ddgrIDWX7P1Le4KCdi9Zx3a26WGSqWGI4zk2frc7lN9m52Cr/kHjMFQAAsk1BSVnF1+10QkXFCVps9H2s5nd/q/daDnvzJ3bpPWf+YztsR2eL85553yw7ZD/7wcc6670d09n+1LrHmjrKQWELp/IQBuS68wOj6wqCdAJjYch5qJ80pVBTEgQ4Bp3PnOvtsXNIxYlrTI1QOxNDWVueZu2yM1mCNAzZIO/CJV2sLteFmtit1oYKJynf+Uzh3uN2CVJx7up56v3EFXYu2uSjp1X+ynsZ3ZUfaRiGq+D8gPrmLtS5GbG7y7vk/CYmnetXaevHdglScbxpvc4sjF9DpGLPB5rz1s/tHAAA42e094slldPtVLSxb/kBAABi6judeFAvM+5HfkGBnUMqTlxrJ+KYtdNOAEi7RON+GL03X6GLM0rsHFJxZk3ipmxF2/crb5BBjJB7Klp+b6diO3ndJ3SpkMBYqi5NKUza5dWMXW/bKQAAcgfBDwAALpOBvl73FU/BpMkqKmfg81Qdv25IA2V2JobJ/VLlXjsDIO2mfXhEBafit94eLJqivpvit2JAuJ41V+r8gio7F62gq0/F2w/YOSC3zHzvNyo+esjORRuYPksd13/CziGZzuU3a6Bipp2LVnS0VdM/3G7nAADIHQQ/AAC4jJK1/iibOVuTJk+xc4jnYqHUnrhCo2Z94PyPCtLA2Lk0mELrj0W6OCtBlBKui7NK1XNb4lYfJvCRaJwVINvN3PErOxXbyetu1VDBJDuHeC6UlOvYTbfbudhm7NpmpwAAyC0EPwAAuIz6u0/r0oULdi6aCXyUz5pt5xCPCXyYAEg8eYMm+MFA58BYK/ntXuWdjV8gP1Q4Wb03L7ZziOfMmqs0WBQ/UTNBj+J399s5IDcla/1xdmaNTl53i51DPO23fNZtKRPPpP4ezdj1jp0DACC3EPwAAOAyGhy8pP4zScb+qJyh4vLYgwhDGqgwY30kbtIx88M8TRrdGGoAUjD5eLdKf7XbzsXWe9MVujCHNC0eM8h5/6p6OxebafVhur0Ccl3S1h833GqnEMvpJdfoxKrb7FxsM3Zu0+Te03YOAIDcQvADAIDLrK+rS4OXBu1cbKb1R/4kunaINOTcyey7Y0hDCRp1FJzP09y37AyAMVf6692afCRBUHdSvvpuXmRnECnZIOdmgHMz0DkwESRr/dFfXafWT/+pnYPfUH6+jn7iM3YutoJzZ1X1wW/tHAAAuYfgBwAAl9l558HzzMljdi62SYVTVT6r2s4h4MjNQ+pLclpqtjnnb8DOABhzeRcuuQGQRHpvvsJt4YBwyQY5N4rf+liTj1JLGxNHstYfx5vW6+T1tACJdPSWu9Rbm7ibwZrfvKqiY612DgCA3JNy8GNocPxGCB3PbQEAkAnOdJzQud4eOxdb6fQqTSsrt3M4UzukozfYmTimOKd09u/tDIBxU/S7g5r2YeLBz09tWKXzC2faOZxdNk/dd11r52Iz3YqVbfnQzgETQ7LWH0bbp/80aUH/RNI3d6E71kciFXt2qvrtX9o5AAByU8rBj76+nnEJSphtmG0BADDRdJ84pqEkWW3FrDnuIOgT3cVp0j7Tk0OSMczrfpXnDnYOYPyV/OoPdiq2weJCnfr8jRqcNtkumbjOXlWjU39+c9I0rXTLR8rvoykbJp5krT8uTSlU6+1/qotTi+ySia39E3dpqCB+d6n5F867rT4AAMh1BSVlFV+30wldunhRZ8/262x/39i+nG2YbaVq6ihvbgqnTrNTAHLV+YHRjXJMOoHxcunCBeXlOXlbcYldEq1g0iRNnjZV/d3dztwEbSnpnKOP75bOzrDzcZQelmrfSlKSmAVIw5CtJp3u1+C0KTo/P343TiYAMrBolorfmbhjWJyvnaFTD96qoSmJx3Uqfne/yl5vtnPZgzQM6WBafgxUzdbZWfG7y7tQVqmLJeWqbHnfLpmY9v/xl9TVuNLOxTb3V/9D05u32zkAAC6v0d4vllROt1PRGPMDAIAMcubkcQ3099u52KYWl2r6vFo7N8HkSQduH3K7vEok/4JU//fZH/gAsl3pr3aroKPXzsVmgiOdD33Szk0sl0qnquNLn9Tg1MStXwpOn1VpFgY+gHSat+VvNbUj8RhpHdeu1pFP3m3nJp6Ddz+gzuVNdi62sv0fac4//szOAQCQ2wh+AACQQYaGhpIOfm4Ul1eqcs5cOzdB2MBHx5V2PoFFP8tzx/sAcHkVdPer4pXf2bn4zl5Zo9N/lGQQnxx04q8+7bZ+Sabs9Q81qTNxEAnIdVPOdKl269/aufjaP3XPhAyAHLrrz3XyulvsXGx5Q4Oa+2u6uwIATBwpd3uVqej2CkAydLeAbHPx/IDy8wtUWFRsl8Rm3jdjZQ3099klOSxP2neH1LnUzidQ/Z40+4PcafVBGoZsN/lkj/IuXNLAkmq7JLbz82eooKtPU9pP2yW57di//pwuVSZO541pO9tU/tPf27nsQxqGdHJbfuTlq2dBg10SW88C74ah7FCL+2+ua7v98zp+4zo7F58JfMzY+badAwAgM9DtFQAAE0zXsXad7Tlj5+KrqJ6j4or4GX0uGJws7bl7SKcako9xUnJUmvcm3V0Bmab0jT+o6N3k43p0/WmT+q+ts3O568Rjt+vi9PjjOwVMPn5GZb/YZecAGGa8ioo9H9i5+CZKC5DDt92rYzfdbufiq/zD71RDqw8AwASTVz1vQVaPllpREX8AxVSUllfaKQC5qqe7y06NzGjTibqrr7VTntYPs7f2JsZXXkGBZs1fqMKi5AVk3SeOua9cc6FYavnjIZ1NIbufdE66+r/maXLiIVOyDmkYcsmJ/3l9wgHQA4rf+liVL++wc7ljsGiKjn/1Tl0qT9563QQ+pv+Xf3T+7bZLstPlTsO+/oWf2CnP13/4OTuFbHZ2Zo1a/tlXdaGk3C6Jr+ZXr+RkV0+Dkwt1+LY/1vGm9XZJfBUf79Tizc8q7+JFbwEAABlktPeL1fWL7VQ0Wn4AAJChhi5dUmdbq86fPWuXxFc+q1qzFixO2lVWNumfJTV/IbXAR8GA1PCyci7wAeSaWf/PFuWdvWDn4utbfYUbJEildUS2MN1+tX/jTyZU4AMYK9NOtmv+z3+ovMFLdkl8pgXIgT/6ogamz7RLst+Z+qu0+y++llLgwwxwXv+3/5HABwBgQiL4AQBABrt44bw6Dx9yxwFJZmpJiWYvXKzSmbPskux1epH0h88P6UIKQ3uZwMfSv5OKTtDdFZANqr/9mp1K7MKcCndcjLM50A1Wz9qrdPJfrLFziRH4AFJT+dHvtPhvvqv888n7Ce+45mbt/ot/pZM3fNIuyV6mK6+Wf/ov1Td3oV0SX+mhPVr0d/9JBSncRwIAkIvo9opur4CclxtdxqzUo995QMtK+rXrha/pmW3h88fWP6uGli/rqc3SfU8+q/Xaqo3feNn+7ejMnlOjhsarNW/+fPX19uiVH/2NPvPHf6KK6eHjTGz6wfft1Mi5+15rZ1yB47WzAU0P6+kHGxUoF2993Tv2XDalqFgz5y9UQcEkuyQxM16I6Qbr/Nnsagphgh1tt5qBzVO7PZkIgQ+6vRofqx75lu7XZj323e12SUii99LNbOuh5aGoX//OF8dlu+Pt/MIqnXgkeY3lgNJf71b5ax9IlwbtkuxwsapU3fdcr7NX1dglieVi4INur5LY8LieWydt2fhNvWQXZbJMTKNMK4h9f7JRF4tK7ZLEpjdvV81vfqJpJ47YJdnBBDvM+B7meFNRfGS/rvjRs5rcc9ouAQAgM9HtFQBMAKbg/7nnAq9v6dEm+4bRtFKL1KznN9pAQOS8z0vf+HLaAh9GQ2OjZs+Zo13v/U6v/+yn7rKd7+1w5y+cP+/Op4N5mDaBD/MQvXGjcwwvNKtfRVr2YOS5CAU+TNDDfDbXAx/G+f4+dbYd0tBQakGBaaVlmrVgkcpmzlbBpMl2aeYaypOOXyftemAo5cBH/gVafGQGE4x10q0n77XzHpOmPbHBziRirumIv/UzaUPctDHXbHjcLVQMpIPP73RSweUP6OlHVtoP5I4pBzpU8+//h/IuJO+yxuj55FKd+MvbdO7qeXZJ5jt73Xx3YPOJHPjIHjYdC6Q133lYq+w7I2XSrpTSwM3fdK737Ah8ZGoaZbp1MuNZTDmTWsHJqcaV2v0X/6uO37hOysuCewhnH033Vqabq1QDH0XHWt0WHwQ+AAATHcEPAMgAwdYaptDfLfg/oEWRhf6Xybz5C7T7w13a9f576uvpdZcdPnRIJaVlmjxlStoCIO9+92vusQdrD27brn3u5opUHuzx5F494Qt8TISgh9+53h43AHLpYvL+8o38ggJVzJ6j6sVLMjoI0lszpOZ/OqTWTw7p0hS7MIkpZ6Sr/obAR0apvSn9aZYpaKs/oOfD0sbHdZ99O7esdM6fafrWr307vHTw3e++rVbn36Lla3PymPO7z6rm3/5YU9o67ZLEzi+cqY5/fos6/qfb1H/DArs085xbPFunPn+jOv/8Zg1OSy1Rm3Kog8DHZXTfkw9o0X5b+cJ5Pb9/oR5KEJSdmDI7jSo99LEbACk8dcJbkMTFohK13vFnat74b3V81ToNTk7xBmQcDU6aohOr1rr72PrpP3UHOE9F0fE2Lfrb/6jCrg67BACAiYvgBwBcbk0Pa3Vtm7b4W2ts+5427ZSWrXcevAMtHUoa9ZCp9fznEfMRhY2mpmGgBp43/bCeCNRkDHuQvze03HmlVDvRuunWT2nhFVfowMcfu4GRsVGrcnec2za12CDHqkdukhsHads64QIfAf1nTuvEgX0625N6AZkJemRcECTPG9ej5U+G9Afnt3c2vBe1hMrapKt/mKdpnQQ+Mke/dr1+QIs2xKstHV6rOlhL2HT3YtKz2rXO8sighlfQ1uqkh+/aJSZtfGzjHufvzExETW1f+ua2OnHSvqfjvBf8m6ja3StD6WKCmt9hrVHSUEPc1bRSi9w0r1PHIrv60wxV52iLl7yBC5r19C9VvH2/XZLcuStm69T9N+nE/3K7em++QkOTCuw7l5cJyHRs/KQ6/vI29a+qt0uTK3p3v6q+/ysCH5eLvQ97y9d1k1sho8VJL9y5OOmXIzKtCbxnlpsWEnXrnOWB9Mekd/ZzYa3YzH1eIB2x048G0yl/uhhvP8z93ON61E2XxrDiTBakUcVHDrgBkKJjzo1CivqrnXzmTi8IcuzmO3RxWrF95/K5NLVIx276tD7a+G906M773X1M1cz3fqOG//JtTe08bpcAADCxEfwAgMutrkxFvWd0yM4GvHuiU6qs1ipT2Ge6gOq13Vz994j5qAfQcEXLy9Ti1mR8Ubsq19ogh3mAvkndL9ja1M573U2pPTD7Ax9v/+ZX6jrVqdOnTtl308A8+LsP9mtV5XarEOgKYqVurDdtPvq1q2tJ8OE/57vBieHCwDmdPHTAHdNjOCKDIIOl4x8EMS07jl8r7fznQ/r4c0M6k/rzvGvODqnh7/LcsT6QYVq/p7e6GnW/r2AwILJW9VuVD3hpkenu5fU2N6AZ3e2LCYD2q9tUKw7zsl4ywc8NznY2h9KwXZXhLU/q6qVNdntbZNM+J32pbgn8jbPc2d/b3TTRE0ovvZrfsY7FbY1S+XZwHRu3lY1DDXF/C7jcVPk376jkzT12LjXn62bo9J+s0PGv3KGeNVdqsDi1WtHpNFg+zd32ia/e6QZkzjWk1sVVQPlPf6/pP3pH+WdTa9GHMRDnPkybnbTG+Sdu+mWF0pqt6li+wU2HTBekW5ykze2e063c4tx3zdoTSjde7/QquMRS0qhym049v3OG1tv0xexH+Tb7984rfD9qtUibneXJ7wvHRuakUUXHD2vpf/m/NPudLXZJas5VVatt/X1uEKT91s/p3Ixq+874uVBSpqO33OXsw79Rm5M5nZ05x76T3GTnN7zg1f+sBT/5L5p01mupDQAACH4AQM7r37nVFihu17FgV8imUNGMpxEIIJjB05M/uBaXloQFPgzTBdbPfvx37nRauDW7zYO9KUR4wBfcCLQEcfa70hYgmEJTM5+z3eAkZoIfJw/t1/lzZ+2S1LgtP2ZO1e++eF7Nfz6kw5+QztQOaWiM7grOVXgBjz1/PKT3/8WQWj81pIEy+2aK8i9Ki3+ap3n/SGuPTPbSN0KFfyErVV0Z6ibFeKmlTXUNowwYbD6m8rA0zC63/C1Ggtvb5uxDU+BvnnXHGfILpZfSuzsOSPUro1p1rJo1w0mOTEsVu551zkpMoNq+j5Gr+B+/U9nrzXYudRdnl6n7s9e6QZCuP2tS761L3C6yTAuzsTA0ZZI7YPvpP7pex756p7vt83OchG4YCjp6NeMHv1bpG3+wS5CZkqdfobSmTd1xy5y36x3dFJ5uxNPbrF/a1q1uRRiX2Q95LUnsOkz6VTUrEKANb7ky0RWc61fdL/5Gi//mu8NqBWKcr5ihI2v+SLse+T/0h3/+uI586o/UN3fsutnrrV2s4zeu1YE/+qLb0sMMaD5Q6aRfw1Cx5/dq+G/f1sz3/9EuAQAAAQQ/AOByaz2j/pIyzbezAW4BW9exUHcv6dRUrSrnQXmLrT0YeCXrSsqM+bH1Z68FAx+GCYjcs+HP7Fw6vawW93m1SItW+Gtf92vXFttF2OY9bl/TJjDS4KuFOZGc7TmjEwf3qbdreK1v2uZ4A2D2V0lHVwyp5U+k9x4e0p4/MgOPD+l8qfv2sJ0vM4EU6eQy56f9Sa+FhxnE3AQ8up0f+dAIeqcpPZKnxk15qtxrFyCDvaynomo0BwKXwxW7IHFVk5cemG7wvNZhJv16UbtSqei6Ya2WdYXGVzI1s4drfmVosN/g6yu+rrnGRKwWMLmp7Oc7VfXcGyr8ePhdtlyqKFLfioU6fc8NOvHIOrU/9U908uHb1H33dTq7rFaXSqfZT6ZuKD9P5+dXqfcTS9S1YZXb1Vb7v7vXWf969d7SoMGi4bc2mfpRu2Z+/w1Nc/5FBjD3YXYyZKWT1ph/R5p+RbpXty/vDN13uZU3hsNrCbcr2GLXewXHSbvsMjONqmx5f0StQAJ6665Q+yc/p48eelI7H/0/1XrH/eqZ36CB6bOc+5lJ9lOpuzR1mrqvWK7Da/9ELf/sq3rvXz2jPzz4v7nr7bjmZufeq9J+MnVz3/ixrvjrZzTtxBG7BAAA+BH8AIDLbdv39FZbbbBbA1fTw7p/uUKF/OnmDibuDxiYbrBS6z7q+NHwwpr6xUvcAMjYS1SjcuIUDMYyePGiTh1pVVf7YV28kLzrlIHzZ7W3Mbpv+UHnOb57gRl4XPrgi0P63aND+v2XhvThnw9p9z8Z0t7PDung2iEdXi0daRpyX4duk/bcM6RdfyFtf2xIHzxoAine50wQZaDcrnwEJvc7v69fSEtfkqamsWc1jLHN33S7mQq1qjCBzPAg5n0NtWptSZa+bdc7+6Vl/nFETNr4oNfViwlChEQXUIZqRDvpmxk7xNmeG1QO8mpS+xVVhmpj37e+Udq/PSqoYWp9F/lbhJh+/NPR7ZWbLpuJWH3nx+pjP3dNbTmmmf/xH1Txynsq6Bleyza/wamTNbBotno+uVSdD3xCR7/+Rzr2v39OJ/+nNep46JPOslvU+U9vVtefNqnrn6zU6T+6Qd2fvU5n7lyurvtWui1J2v+/n9eJ/3m9Tv/xDeq7cZHb1dbQlJGPMVLyxh9U9YNfa1JHKtE6jAtz7Sm8y75Vj2zQQ26r0pGmXxHcSich4WlRKmLshxlvZDwrfmRpGjWaViB+JuBhWmjsfuB/dQMhO574T3r/q0+r+V/8W338p4/q0Gf+XEduu9f59wva/8df0sf3/y9uy5EP//Lf64O/+rZ+9789q/f+1Xe1x1l+9BOf0ZmFV7pje4yUGd+k4b99RzW/+aldAgAAYkk5+FE4daqmz5ipGTNnjenLbMNsCwAmErdvaPm6UXlwofa9kHq/zYe6nAe7dcMZ+2K7nvnKVinYfYLpRzr29g4fOqiGq6/WsutviPky75nPjJodCNQ/gGeDWw4Z6G7CFISaupm+h/8NS7wB0HsP6J0JVDAYT8+pDh3ft0enjx/VxQvn7dJoJ4pOazB/yM7FN1ggXSiWzlY5654ndS32WnQcXTmkdue3Zl4nlg+pe6F0rjL5+lKVNyjNfl9a/p/zNGM33Vxlo5e2NIfVpH7pGy9qX73pxs5Lc1Z3vRhqaWZqXccc8NwbdNiMvfGQ/TsvbfRaqXldbAXWuUTdOyMCJfUb7HsPuK09zN+8+93N7thH3vIN0v42J+0Mbbe/MtQtzfrKZm2ytapN1zNFzrbcgsbN3wzfp6Yzet7t03+0nHR5mymYC6VxpnWLSeP83XFNJCW/adHMZ7aqeFv6mn1dnFGigSuqde7KGp1dNk9nr52vvpUL1XfTYvXeskQ9a5bqzLpG9TUt1oW5lW7Lj3Qo3rZPs/7D36vip7+3S5A5zD1ReBr1UP0BPW/HIUqYfiXg3Zs5f2OCo9u+p01m/A67jvt1QK1Oujec4MVI9yN9sjuNCrQCmfur/6Ep3YHuxEbnYnGp+qvrdLrhWp1YuUbtt9zl/HubOpc36fQVy92WI2dnzdX5skoNTklPGUfh6ZNuaw/TzVXZ/uF3EwgAwESTVz1vQUqlFSYokZemm/9khgaHdKrzpJ1LrKLCX4dm+ErLh9+0FEB26ekODnQxIqNNJ+quvtZOeVo/zK6Cj9lzatTQ2Kh582P3d2wCHy3NzVEtQoZnpR41QY/6xoh++03XXBGDIJsgib+vbDNQcloKHnNLfkGBSipnqNh5TS4M75bl18t2q2P2yGtTj6WS9jwteF2aRkuPINKw4TM1ohtaknfll4lWPfItPbQ8VBvYdLGVOV3bXD7nrpqrM7dd6Y3lkUVM0KP47b2acnjiJmqXOw37+hd+Yqc8X//h5+wURiIX0qiLRSU6ed0t6rj+Fp2bPtsuzWxFJw6r6v23VPX7N1UwzLHeAADIdKO9X6yuX2ynoqUc/DCtMsZT58kTdioxgh8AkqHgEBNZfn6+GwApqZyuyVOnqffCGf39nfvsu5nBtPSodHZp1gd5Kj1sFyKINGz4sjn4gcTOLq91XvPUv7xOKsjcHnwJeoQQ/ECmMq0xTBDk5PW3uC00MlFJ2z5V/f4fVfX+PypvyLlhAgAgBxH8SIDgB4BkKDgEnAw/L88NgnQuuKSdt2ZGYdzkPmnmLmnWrjx3GrGRhgHRTPdVXiCk1h2HIxMUnOzRtN3tKtpxkKCHD8EPZDozeLnbEuTam9U3t94uvbzK9zW7QY/pH0aOPAUAQO4h+JEAwQ8AyVBwCIS7OE3qnu+8Fg65/14cx6G2Cs84efc+qXJvnkpNT2npGyokZ5GGAYkNLKlWvw2EDBaHd/M31gIBj6l/OKqpLUftUvgR/EA2MS1AzGDk7qv+Sg1OHp80Jf/CeZUc3quSVud1eJ/K935o3wEAIPcR/EiA4AeAZC73QzeQ0fKkvtkmGGICIXnqmzOkoTQP8VV00gY89uW50xge0jAgNUOFk3V+boU7ULl5nZ87XRdqKuy76TPpxBlN+/Cwpn54RIWHOuxSxEMahmw1lF+g7kWN6r5imfPv1RqYnr4yEdOFVUnrPpW0fazSQ3tU2tqi/PPn7bsAAEwsBD8SIPgBIBkeuoHhGSiTLpQM6XxJnvvvhaI8DZQ6/5bIWea8VywNTvI+m39JmtwrTTGvM1JhT56m9Aw5086/vUMq7M5T/kXvsxgZ0jBg5AYLJ+nCvOk6b16103WpokialO+8Cpx0LF9Dzr9mftD+OzRpkgp6z6mgq0+TnJf3b7/7b8Gpfk063ae8sxRQDgdpGHLFpalFGqiYoYHyKp2vqNJA5QzvX2feLB+cPEUFA2dVcN5JQwbOKd9MDwy40wXnzbS3rLR1jxvwyBtkDA8AAAyCHwkQ/ACQDA/dALIZaRiAbEYaBgAAgETGMviRb/8FAAAAAAAAAADICeMQ/GjSZx79qh76i7s0wy4BAAAAAAAAAAAYK7T8AAAAAAAAAAAAOYXgBwAAAAAAAAAAyCljE/y4+n49ZLq6Mq976u1CAAAAAAAAAACAsTcGwY8mfWZNjfNvu9585tt6fp9k5gAAAAAAAAAAAMZD+oMfs2ao1Pzbul8t5t8P96vd/AsAAAAAAAAAADAOGPMDAAAAAAAAAADklPQHP050qsf8W1evBvNvoCUIAAAAAAAAAADAOBiDlh/b9LM3TEdXNbrFDHh+Z523GAAAAAAAAAAAYBzkVc9bMGSnE5oxc5adGh+dJ0/YqcQqKqrs1MiUllfaKQC5qqe7y06NDOkEgMuJNAxANiMNAwAAQCKjvV+srl9sp6Ix5gcAAAAAAAAAAMgpBD8AAAAAAAAAAEBOIfgBAAAAAAAAAAByCsEPAAAAAAAAAACQUwh+AAAAAAAAAACAnELwAwAAAAAAAAAA5BSCHwAAAAAAAAAAIKekHPwYGhyyU2NvPLcFAAAAAAAAAAByS8rBj76+nnEJSphtmG0BAAAAAAAAAACMRF71vAVZ3cyioqLKTo1MaXmlnQKQq3q6u+zUyJBOALicSMMAZDPSMAAAACQy2vvF6vrFdioaY34AAAAAAAAAAICcQvADAAAAAAAAAADkFIIfAAAAAAAAAAAgpxD8AAAAAAAAAAAAOYXgBwAAAAAAAAAAyCkEPwAAAAAAAAAAQE4h+AEAAAAAAAAAAHIKwQ8AAAAAAAAAAJBTCH4AAAAAAAAAAICcQvADAAAAAAAAAADkFIIfAAAAAAAAAAAgpxD8AAAAAAAAAAAAOYXgBwAAAAAAAAAAyCkEPwAAAAAAAAAAQE4h+AEAAAAAAAAAAHJKXvW8BUN2OqHCqVNVXFyqvPw8u2RsDA0Oqa+vRwPnztkliVVUVNmpkSktr7RTAHJVT3eXnRoZ0gkAlxNpGIBsRhoGAACAREZ7v1hdv9hORUu55cd4BD4Msw2zLQAAAAAAAAAAgJFIOfgxHoGPgPHcFgAAAAAAAAAAyC2M+QEAAAAAAAAAAHIKwQ8AAAAAAAAAAJBTCH4AAAAAAAAAAICcQvADAAAAAAAAAADkFIIfAAAAAAAAAAAgpxD8AAAAAAAAAAAAOYXgBwAAAAAAAAAAyCmjDn7MuPUv9dCjX9VD9zTZJaFln7naLnDV6ea/cD5nPvsXd2mGXQoAAAAAAAAAAJBO6Wv5UfeJiGBHhFnLVFtmp8vq1DDLTgMAAAAAAAAAAKRRWru9qllzvxrsdKQZS+tUqj599EG7M1esWmceyDQlS9apcY6dSUVekaZMstMjUqMVd6xRpZ0Lk9K6E/19heY33aVPrV+nJZUFduF4SLBPAMbUsNMwFWrKlNGkD6RhANKHNCwdSMMAAEDuotwuHSbW/WKagh99am/tc/6t0bW3xgpq1KlhYbF0plUtv9kvE/4oXbiMrq+QcSZPn63qcjvjKtCU4ipNDT5Ye/Ml0wqd6UJNrbtZ1zU67wcTO+f9aUXe5KRSb7n7r/PZ0gpNyfPeMvKn+dfrcD5XUlpqL8rIdZu/928n+u/zpwX+1lOyfI3qOl7Xr15vUdnqNZptl7umVPi25XAS7Fj7V1JsjyWw/cD2Uj2myO1Y7rrdc2i5269S7TVOgu+2CovYHoCUDC8NM9PX6robF4e/P9I0LCwdcT4/lmmYm1769idpGhY57+yPcw6i9yHimFJNw+zxVS29VdfMrwjOh60LQFJjkoaler1PoDTM275/uXd8pGEAACDTUW5n5x1R94f++7dUj2kClNsVlJRVfN1OJ1RUXGynwhXNX6krq6W2X7ymk3WNmjd/gcrPS6XlU9Rz8Lf6+ITzoVmf0M03zFTh8Ra91bJN+XNu1vxZRRo6uENtJmYSw9n+OG9EmDo19FAwEoVTp9kpQJo2b7nmnNulAx3efNWqz2h5+WQVXXWzllz8SF0LPqebawo0aW6DZvR0KX/p1Vo8dVCnO9vVM2D+Yo6uW3eNzu49qHNL7tAnp7fowPQ79JlrSnUhv07LbqrT6ZZD0tV3a92iKRosr9P8GZfUtq9MN6xdrIK8Gl2/craOf9ynimsD6+7RrJvXqHbSZM28YbXmdDn7UR/x9y29mn/7Gs04tlsdzvVn1F63RD3vOp/VGZ2vulq1vS06es55o/RGrV1Tp6FJtVo0s09HTk7X9etWqiy/WFfcdK3yDnysgSvv0pqFU3Wp6jpdW9Wv/Ks+ofqpBapovEHVZ1p0fJ49pikLdcOy6Wrd367JkcfUPlur/ds54V3T+c55uXXBFKl6ha6d0aEDx2u1+p5rNfncFM2bm6+Du05p7pp14dvrd/90xM4PmAMfOdIJZIvhpWFHdan+OjVMz1df1wl1911w/mKEaVjLJV21PpCOLNPFlk5NG6s0TA1a/bnlmnqpXIsWlupI+1RnnxOlYR06Wnmrbl00TUOVjbq2pk8H2hfqk/deo2kXp6ruxutUeqBFvVeONA0r0tI71qnm4kVV1k3XqQ8PqepW0jBgJMYkDetYbq/3yZpz/c1x0qCJk4Zp0nKtDrvnHFA9aRgAAMgSlNtRbhdLSeV0OxUtMrAzCq367e+8Lq1q6sIDJV6XV466T7gDnt/iNg6h6ytkvo733tGBs9KFnouSG2crcLtXOHfo99pzukPtztV99vgOtZ9xPx5XX9s72tP8ht47XqFFVUVasFDa86az7Pc7dcwkvhebtf39485Ej/ouFGmyfOseWq4F03rU67zbe+KiqhYvjP575/O7f/Zj7e4x09EuDToXuz8YO7lAk88eVPNuZ5sLlmv2xQ5d0CUdP12qxfVVapg3oA/ectb/zo+1dX+tFjgZyDsf7NAHvz6usisXu6twj+mDN7Svr9S5mmMck+HfjjX48W/0oQmK9p7VpWlOYjqlQvlnDmjPnvd1vKBCZaXO8cbYHoDhS5yG9avjyGmdPb1Xh04kvlNJmoapTb9/Z6/OOemIKX+cNtZp2KQpmuTcIra836LBZGnYB1LDgov60MzveENHKq7yatScaVdz8zt6Z0+PSspGkYapQuWX2vXRnp062DdFM2aShgHpkq40zLved+i9dztUGTMNmjhpWPQ9J2kYAADIXpTbUW6XTBqDH44PN+nNVjsdFOjy6g/68TPf1vPu6x/p+gpZoEqNa27UtM6Daj9xVk4yqt4Pfqy/37FP3VW36NarR9rqaECDl+xkQN16fWrhWbW3tul0ZLBz4KLO97Q7ifAO7fndq/rlttbov4+ht08qcaOOBZru/NvV6S520ul3tPW1d3Soe7ZuXrNCU84PqLfjI2/9v3lJW3f3m01aTsp73tm+nVPhJJmivpBLbgId85git2MX13zq06rrd87p4R45+ZOz/ha1DzboljVrNO2jN3TIHK/7SUfU9gCkbhzTsNIbdeuqUp1qPaiTfcEExJPuNEwteuvl19VyTGq4fY1mJ0vD8vzzRSZZCTdk/jeKNEwHdLBrtprW3KEFXe/ovZOkYUB6jFUadmFCp2HR95ykYQAAIFtRbufeL1Jul1Daur066XwBpgurzpZBVd/otfQw3V6dWnq/bp3vnAa3y6vD3h/psO36aqbK+23XWBHo9gqXg2k+t3RBnWbXLtGCsl4dK1yoBUWTVTaj1HkV6lzZzVo1f7ImVVRq0vEPnd/8PC29tl5DR/ary73y+1Uw9wZdPWe2ZtRWaeqpXc4jZYOWLZjt/M2VapzVo12/dx6shxZr5ap5qqiq08zSCzr88VlVXz3L+T1Xqay8SiV9H+noQK237tbfqav2Vq2YPVs1K67RdCfB29sX8fd7zqvh3rtUfexDHbeJcN+ZCl192yrNX9Comad3aMcRG+auXK21n1qgSZNKVFlwUi272lS5aq0WT5+tJSuu1OD+j9Q2dKVW3zBLxfOv1+JJH+hQ6UqtnDNHNY2lOr79HXWUNKjeeXg3zQynz1uos4f3Rx/Tybla7d/OoZPu5vNmXKkrZ03TVOecls8uVX+Lk4fcMFeTLg5pWsV0nW//vc7Muzl8e5EZyzDR3QImimGnYR3TNX/FVSo+vVsnTTWVkaZhH57SjGsXatrkClWWlWtGQYcOnpkxNmnYpKu0+nPXqWywSJXFPTr4/j4VJUrDCg+q+eQCNTXVqrK2QeXH3lXziXLVL5YO7D3m3C8vVE3/frVE7lPKaVivqpY3qCzvgiaXzdCUjvfVUX0TaRgwAmOShnXMUf11tSqbUqXFy2fo9O/fjU6DJlAaprzZWhB2z3lMhfWkYQAAIDtQbke5XSyJur3Kq563wK0vlMyMme5oJuOm82SMiEgMFRVVdmpkSssnytj2uGyW3q21elVbd9t5hFt4l26Z+gu9+YdL0px1Wlv7kba+a9qGpU9Pd5edGhnSCUxopGGJla7WLcta9eZv26RJK7R2zXlt3bLTvpkepGHAaFyntXdIW3/xvp1HGNIwAAAw0fHMm1gWlNtVm5pBcaS32ysA0bo7dazbTiPawZ06WbVeK25ep9WLz+sPu9KbgAIYJdKwxHp2qvVSo2500rAbb6lQ245m+waAzHBax46dttOIQhoGAAAmOp55E8vycjtaflCTCMh51DgEkM1IwwBkM9IwAAAAJELLDwAAAAAAAAAAgBQR/AAAAAAAAAAAADmF4AcAAAAAAAAAAMgpEz74kdKAJwCyVjqucdIJAJcLaRiAbEYaBgAAgETG+l5vwgc/Ll28YKcA5KJ0XOOkEwAuF9IwANmMNAwAAACJjPW9XsrBj6HB8atzM57bOn/uHLWJgBxlrm1zjY8W6QSAy4E0DEA2Iw0DAABAIum6X0wk5eBHX1/PuAQlzDbMtsbLpUsXdba3RxcvXuCmGsgR5lo217S5ts01PlqkEwDGE2kYgGxGGgYAAIBE0n2/mEhe9bwFWX0PWVFRZacAAAAAAAAAAMBEUV2/2E5Fm/BjfgAAAAAAAAAAgNxC8AMAAAAAAAAAAOQUgh8AAAAAAAAAACCnEPwAAAAAAAAAAAA5heAHAAAAAAAAAADIKQQ/AAAAAAAAAABATiH4AQAAAAAAAAAAcgrBDwAAAAAAAAAAkFMIfgAAAAAAAAAAgJxC8AMAAAAAAAAAAOQUgh8AAAAAAAAAACCnEPwAAAAAAAAAAAA5heAHAAAAAAAAAADIKQQ/AAAAAAAAAABATiH4AQAAAAAAAAAAckpe9bwFQ3Y6ocKpU1VcXKq8/Dy7ZGwMDQ6pr69HA+fO2SWJVVRU2amRKyiYpCnO8RVMmqyxPToAmcokhJcuXtB5J+25dOmit3CUSFsAABg98mgAADITeTSAkUh32lFdv9hORUu55cd4BD4Msw2zrfFiEtVpJaWaRKIKTGjm+jfpgEkPTLowWqQtAACkB3k0AACZiTwawEikO+1IJOXgx3gEPgLGc1smmkyCCiDApAcmXRgt0hYAANKLPBoAgMxEHg1gJNKVdiQy4cf8MM3oAMAvHekCaQsAAOlHHg0AQGYijwYwEmN93U/44AcRZQCR0pEukLYAAJB+5NEAAGQm8mgAIzHW1/2ED34AAAAAAAAAAIDcQvADAAAAAAAAAADkFIIfAAAAAAAAAAAgpxD8AAAAAAAAAAAAOYXgBwAAAAAAAAAAyCl51fMWDNnphGbMnGWnkph1lz7/+StVamdD+vTRj/5f/faEnU2i82RqH6yoqLJTI1NaXmmnACCkp7vLTo0MaQsAAGODPBoAgMxEHg1gJEabdlTXL7ZT0dIf/Aiq081/sUFXlbXrzWc2qcUuTRXBDwCXEzdtAABkJvJoAAAyE3k0gJEYy+AH3V4BAAAAAAAAAICcQvADAAAAAAAAAADkFIIfAAAAAAAAAAAgpxD8AAAAAAAAAAAAOYXgBwAAAAAAAAAAyCkEPwAAAAAAAAAAQE4Zw+BHq377X76t55/ZpBa7BAAAAAAAAAAAYKzR8gMAAAAAAAAAAOQUgh8AAAAAAAAAACCnEPxIg/uefFbPPfe47rPzAase+ZaefmSlnRuBDY/rue88rFV2Nj3u1RPPfUuPNtnZdGp6WE/HOA/Zwnxfzz15r50z5+nZ0X1/cY3lupFTTBrg/Fae2GDnA8y1Npq0YUyu1ZV69Dsx9nXEvPWZ4w+84q47Iq300uTQ6/Jfa1zzAJBzJnQe7XD308ln0/6skiXs8af1nAIA0iuNeZX7jDlmeZ73vBirXHFsygWBiYXgR9rUajUFW8NgHsLGKAiTFi/rqY1f1mPf3W7nR8FkVsGgipHGdWNCqHNu2ibWzY5JHx7Qsq6t2uhcK95rq7QudgDhvoZatW77nt51pk0Qc31ls54P/t2L2lf/QMQ1ON645gEgV028PNqzasVCFbW1qbVkoW4c1/t5U0CUAZWtnPuOx5y8/anNdh4AkHFGnldFl1e99A3n2fIr3jNn2m1YorpeZz97a9UwJkH1DMk7gcuE4Eea9O9sVsfyDRlcmA8gK/U2a1dXo+6fQMHVVY9s0DI16/lvvGyXGC/rqReapeVrI27a7lVDbZta3MKHlbqxvigYCPFs1zNfcW5Uw9YFAEAaTMA82mPz25ZvqqWtSItWJD5+t3U1rSQAAONqeHnV5WQq8/Xv36pf7u9XXcPlrLQH5Ka86nkLhux0QjNmzrJT46Pz5Ak7lVhFRZWdGpnS8ko7NXKm+dvqrhe1SRv0UOXbwUI2c6N/vzb7avva2swldrZta3iBnGmS92CjityZNm15XVrfdEbPB6PLJlq7VnXutAm4vOhbd/h7UesOMp+7Sd2vH9Cidb5tbfymXrLr0OvhtZjM8a1XrPX5t9mvXe46y/SWuy4j3j5FLHceHL1jjDg/weXRzLl9qPKAdlU2Op93tv3C1/TMNrt8uXdUoeMyvHWXbzO1x337bP/O8NYZ+P4Cnw+di/B1+/82/n6H/43U6p7b6HW7rUPW1dqZWPsVOFZvmbcebxpjo6e7y06NTDrSFvd3YdIA57u+/0HftWXSig3SJt/1Ef+3byS7ViN+wxHph5sGBH6eUesOSHSNee8t2u9Ps+w+1x+Ius4DaWp0S4k4107DnuD+xk+vIsVK7yL2M+51afdjf7OqljvpaOCaD0vDnb8IptFx9jvONR/43k2B2jL7kfD0HgAmtomVRyf++/HOo11ufmf3MXAeYuTlcfcrQR7objfefW9EPhv1vOOuV+Hbcv9mofbZbcQ/l7HzdoV93r+v5ntznqki9z3O95TwuAAgh2REHm2MOK+KXV7l5gdueZFG8RwZiy8/kW+f7bvB+w3fvg8rL3PvU2LknRF5Ks+buNxGm3ZU1y+2U9Fo+ZFG7373bbXWro1TqymUGAa6cXm+66bwMSachKfDSUC99/eoIZhYGl4CbBJYf1cugW3d9+RaVTmJVWDdWxRvP4wiLWtyHsrsZ5/fOUPr3T4EX9ZTr7dFRJq9WtVbogoSzfH4t+mkqE2+BNW+79/fXZVrbZc1pgsYZ77XZALOezYRv+/J8POzpatRDyXqqqZ2obTZfNb3MGMe0uzfbzTBo4i+EevWLVFL8P1OLXswxaZ/ToYTtu4XnAdT+7eJ9vvd737N2U6bl8E478V8wAk+qNl1b3xb5Q9GdAkWPFbnZb6jdTRZnFC2fU9vtdVqfZzrIeq3/8IZrQ42a03lWo34DTvpR7B7Kef3GdaNlLmuElyXsa+x7Xpmc7NUv9J3PZqaONKuzZGFKitVneR+t2pWqNbOqlkz1N/lXGPWS9/Y6qbDgfE+4qeDMdK7ppVapGZtCt6w+q5L3zUfUOfsv5uOummYcx43+NNwk0bHaQ2YyjVf0qjyFvu+2+KFloUAkJHGNI9Ocn877nm0x+tGZI9X0LJ5T1R3Imaf3YoIdltb/Ocnhfw17n2v29WUk8+7BT3Oe5HPJ5u/6W7L32WI2Vft3Jz6s4I/b296WPcv7wzf1w2xuzlL/D1b3M8DwLgZbl4VyhNil1eFjP45Mozb5dUBvWOCI9u2a1+Srq+GnZfFzDuH8ewK5ICUgx9Dgyk1EEmL8dxWetlE0LlRjropNolhSZve8kVSvWDJTV4C4yZ4zfplsHDcW1eQeb9tq6/w3HlQ2Ra+raLKULDE9EcYvyaRk4j7Hmbc/QhkBCZTqF0STJhXPXKTs12bYfhFHY/34NRv5wJdzfj39539/WH7GMnssz/S/FKLc/yV1TEfMFxtb/ui5zEe0jZv1S416nZfxtH6ui+Cbt5PsU9F/5gCLpuBHHLO2bD3O4LbxHHnVt85flm/3CktW+/LTP3Har4jzVA1GdOE4hXq2/QijG3OG/H7NAUx7jhEya5V837gRs16aUtE91IlZZpvJ03BQqJWFXGvMXMjJ98N54a1WiZ7kzdi3nW/b0do3920M3Aj+EKzqtaZIEic8YXc9C50Tt0Ckv3b3fPoXvP+Y/GfUyu8ey1PKDBj0sDYNXxSuub9+YF7E1yk8mD1IwBAJhmzPDqV+9txz6PtMbUEtvNyVHci8yuLwismtITujVPJX0dz32u2FXo+8t8npHAuHdF5u2/bznuPRRWCGUm+5wDu5wFgnCTLq2yesMWXZ5o8dLP/uTKBUT5H+rnPhvZvA+Vm8bu+GmleFlsqz65ALkg5+NHX1zMuQQmzDbOtrBUj4XHVlamo94wO2dlIpvayuo7FTaDc9321md2Xr2XIS9/wWlYE34uI/CYXKFgzmUIgYY7MMHySHI/LRLx9+xtqlheHaXbn+7z/+JKrVXlJkZY96Pv753zd+CTgr0UeW7ya6E4GZTKHUe23t+6OEylmspjAYhSQu8xvv1/drXY2UrJr1bxf0qiH/L9hf7NY5ybQbR0WfD9OICEB7xozN3Kh/Q+/yfPbrmNJWjsGrxc3cJMggGIKKTaa1m2xzpvhvwm+V7cvDxWQmOuyzg2chF6hJtGxmBvGrepY/kDob2LWvuWaB4DcM0Z5dLL723HPox1uwCY8jzT5Y5Gv5cihrvAKT6H1jSR/HSZTIBWo1BUWxBnBs4K5j3BbyIT+JnaL0iTfMwBgfCXNq0y67U6E2xYn74uSrudI09OKs1++Z0i33MxXITncCPKymFJ9dgVyQ8rBj4Fz53Sq86Q7FsdYvsw2zLayl1djq2rd47rRLnG1nlG/v2ZWhHdPdCZsLeC+b7tOCnsFax+ZxCu03HS9NLzBF0M37KbGlJsp2Npo3kDCEZIcj9tN17oZXjNBu0/P7wzVY4sW2ezOeflbviTVpu5As8TA39tX/BYwnuSFkIkKY0e73966kwdgANNKa7Mb5HxihV3gMr/9BK0Ckl2r5n3TF6jvmvFeodoqbvdtgeWmECBOlw/xBK6xd3ccUL97I2du8vojWmyEuIUmYd1vWBFpkr+GzUgF0zt/c2N7XZq+uMPPSXgrr2i+Vie2q7/oQhKueQDIRWOSR6dwfzveebTXjUjkM8nWsO5EzD6ZLjQDBSqmay6vhelI89fhCBVIhQdxRvisYGoCBz7rtiiN1XVJku8ZADCukudVJt12PzpiaXmOtD3AhD+Lmy634vVQMsK8LKZUnl2B3MCYH2PBNmtb5m/pYPvu8zd187qUss2f3VpK4S1GzA17UESzOsMMzuT1y2/GAwlMpyK8abq7H8HE2mFrTN2+3vSR6++axSfW8ZgMxk5H81qRDEfY8ScVXmPN5bbICH9ACWs+aGqDxQvuRHCb0Psfdtx1x65dN7z9tpmmv4sh5/s0NQfCmmACru1OeuH8Fs3gZXaJ99vvD+9uz5leXWu70Uh2rbpdXYSnPaYf0UDND5PODKclWcJrzO5Lw5OmGz9/t3Xh3AIkZ5/Cx/zxxkUKpUlek9/wwhkvLQyvtWKupzgt2Ayb3t3fFF7L1b3m/efUXXeCGrVumpBojJEQrnkAyEVjkEcnub8d/zw6Xqvw8O5EzH3E6q7QWGL+/tKHnb+OgJvP1m9wzrM/iJPas4Kfez+U4P2QJN8zAGAcpZJX2TzBX2HA7bkklTTfSsNzZHiQPsDmKTG7vhp+XhaT+zepPbsCuYDgxxhx+/610x7TMsMbpDxQC+qhyrd9/fK+rKeC/dN7rwb5WxCYqKwZFDf0vhmcadOOwHvhTdbMe/FrUPWro3JD8LMPmYH8wvqv9Zru19VGFiz6meMJ3+b9lZ2+PoqdfQprJu6kqtua1R8cEN5ExG1zPbeg0lmf22Im8Hnn+FtMZD71Fiymlpk7iLz9++ceXKh9L3jjcgS0aknofbdliq92u2ldY7oWi9XcL7JbAXfdpk/EFPbb1OpzuyyL82BnapSZQaoC637uJnW767bvA37O72VLROMi97e/f2Go66oHy/RWsOVGsmvVS5u6mwK/PydNMAOoORsxN2xmTBt3EH/7njt4W8z+rj2JrjF3W6ZgqLY2fjDC5e1TWFd+z62VXvfVmGmK1eWVSQuj/84MJBu/JoyX3hWVRNRyNdfltjJfd2Bm+5v1jn07iukaIywN9waRj7ldrnkAyE1pz6MT39+Oex6doOKQP7Bv7qn9XXh4L1swM9z8NYqp9Vrr5qFxK3653RAXqchfucuRyrOCn/v5qPt//zkLSfw9AwDGTap5lZNuR+Whvvw5vLwqllE+R7pB8tgtLb3WmLbys9tK1NlPW9lhuHmZJyLvHM6zK5AD8qrnLcjW0cVdFRVVdmpkSssr7RTCmEjwBmlTggeo7LJSj37nAZVvG0lzQExEPd1ddmpkSFviMTVflqhltAUCpmZOw56EA7sCAHITefRYSUcebe65nYeIzeFBfdOKIrziFwAgF5FHAxiJ0aYd1fWL7VQ0Wn4gpvvWN466L30AiOR197dnlDUhV+rRpmStRwAAwHCkJ4+OPYjs/Moi9XcNZ1w8AAAAYPQIfiCCqa31rNbTRy2ANDN9kz+UlrEtTDchtOICACBd0pdHR3Z9670Sd8kLAAAAjA26vaJJHYAYaK4LAEBmIo8GACAzkUcDGAm6vQIAAAAAAAAAAEgRwQ8AAAAAAAAAAJBTCH4AAAAAAAAAAICcQvADAAAAAAAAAADklIKSsoqv2+msNHVqkZ0amcKp0+zUyFU2fUH3frpJy66/QcuuKNfh5oM6Z9/LBYvu+JLu/OQNmnX2PR3osAsnmM6NQzp7Mk9F3XbBZdD7+SF1FOWptN0uSBOz3p4e79jSdZyZcL5G6/zA6K7idKQtgWvPTVvc1zz1v9+i0Q0D5Vl0xxe06PQutffbBTGYtO32eZ1qOdxjl4y3Gq3Y8Kf6VNOVKmxLsK9Va3T3rZVq2XvMLhhHZtufXagTo0j3zfd8/aTMTl/Hcx/N7251ya6Rb2vp3br/nlvtNRPjt+P/zsz0n63XDfE+OxbS8JuJZdTnLRuN0blE5sv1PDqV6/ny59Ghc5D4GeE6rd1wpU5fjuuUPDrt0pHXuL+b63Pgmdncb6wo0q7Lcf+ZEnMffbdqjo/DvU2KJuS9ygREHk0endRY3sObdY9x2QBp2dgYbdpRUjndTkWj5cdoORfWLXXt+vkPvq9N5rWzVHduWKNK+3b2q1FlWb/2vPJ9bd1tF11mprC+s87OjJMZz+VpRqudScGFO4a0faPzr53PFsM9ziDn+9j7eTvtGPF6EOX4WzZtcV4/by5VY1ONfWcimK3y0uN65wc/1I6IGwsTvFlRZWey3L5fjH36am7Q7h+nvGn43433cL7Izo2KKYhYLec3Y6+bV9pVc0/4/lQurlHfzjechx9nu7fVqN3J37xr7COVR3x2THS8oVc3m+2PVhrPW7Ya9rk05+xLuntCpaMYSxM7j47/jEAePTzjmUebfHJ4aWC685rrtKDsuI47610U7zdi8vIvjnCbptDrjuvszBjb/ao2/eJ9O5OJ2rVjc/Q9dCLp/y1yr4LLhzw6w/Jof/6TtuehdLnMaVWKeXMu3V+Nt7zqeQuG7HRWqqgY3TdfWj7KrN3cnC1oi3njYyKtCw4GEhsTUa3Qe+YCNzdlK5zbkbLFWlIq9Ta/pje1Rnc2mlYspqDvVe1z//4LqjzdoyWNs+3yNi344gqZOfM3r24zTQCc9dplkknc7A2Ofxtnjuj43Ek6aNfrXdhrpH8IvxkyNzvePjiO7HCPyRzDjXO9RSbzCCSc5rO3yEmw3H1w+M6D/2+C+xlxntxj22G2752X7jPOfpbt1c8jEsD2x4Z0xEz05+na56SBzw/pD3PctzT3rTzVHJf23uTsT/mQ9nfnaeWPvOBI4DOVu/O0+BfedHBdjvqXbeG8KbQP/L1z6Obz8zWk3y/1Pnfl03kqcf416xx42/ub9o1SYbfzebMNu1+TzYdXDmn7ajPh8C/3ibUPJlByUnnqXzrkHrt7XNu9z/iPZe5R53/7Q++5/Puf6PgjjlNHvc8a/mPzT5v9CpyH4HpiHaNZ973evvu/p1jridput3NI9r3gd5IherpHlxWPOm1xhKchzvfgu+7M9PXOQ01x42L1uddmnLTAXHurvaXqCV1joWvQpAd3OemEd82H0oF+HXd+rMWn7XVu0pR7FrvXQzCdMstuk9501+mtp3yn3d/gNa/QNW7SBd8++EWnG/L2y0kjw47HEZUudYSnqYH0y4iVroWJSI9T+lvfueg94iRCZT32HCT/DkJpd4j7XZ7+oXvewtP98OP2RKeZirefMb97+32759WXroflGd46Quf5uPNbcNZjf4v+8+LPF4yo7ybi89HfgX9/vOPdtzj02zbn2L+N6N9J+Lk07/uvGcNs359fLbrjbukXJj8031etL2+M5u57xUdhvwlvXbNjftfm/bDrUrG+e+87dO8HnLmYx5Tgd+kZ7Xkz++8/drO+yPuCOL/nhL+VeN9L4Hdd6qzH+c3e5tuWu77TetVZj//8hR1D3PPhO5cJzlmy/UP2yfU82v37YL7g/H6j0vuIPDrW9eo8W4xdHt0etixeeuMt967TmNsw123kvYU7bSW4rs05yrw82v8Zk97Z7yHWMV6GPDpsm/acxT2PrvTm0S5nH+6u2OE++4Y9S8b7LcRZHvO78J/rsN9yxO/fn+fF+X6ivseqGL8T93vx8q+U8syI33CA+xvzpRcHF6RWhhBaV/xrzD2OyGfuyOsw5m8xXMzvNumxpeH3E7ENszxWuQkyC3n0BMmj46V7jqjt+J6JvLTCO7b3Np/W9V+8St3BvzXrtM8oI92+m24E0ubY5ycsbTnToj1lDWFplZtmBtYd47z7f0OhtMyXt/jOQ/izqT9dtMeUYt4cdU5NGUjwNxT+u/T/vf/7z3SjTTuq6zvYHG0AANZPSURBVBfbqWh0ezXaJnUdPSq8/lP6VFN0U7rpi29QxelAE7M5qm+cqqOmWVfRQjWsrFHPL17U3781pHmf/pQWn/yl/vYnv9bhwhVaYZvHTV/cpGum7taml15zl3/q01PV/IO/1m/eN39zpfKdbanpSg385O+cZe9pV1uVVqywzbv829j5sU771qula/Spafuc5b5mYM7Ff/u1Pdr63/9O25119S/+nNt8+nfbjqnwinnqdNbzlq9g+tzhScF9cBPr2xrU+dYv1V53t+6s8PZ51/vHVHrzGq+pbVGDllWcCTYLnr54uaa1mya4znlpulpTd31fr/46vMmbKTTvb8nT0pfzNDd/SAeW56n6lTxNXe5czD/J0+w/OB8ql06tGtI0Z37xb535lUNqzc/Tsv/u/M22PE25wevSafJN0iRn2UJn2dz2PLU789ObI/7+7/N07rND6ujw/n5WhZPOLM7T9L3S+aulS4e9bpx6bhnSpZ3efk111n/G2Z7piqrzs95+mfWULx5SV0QXVRfuiL0Pg871+XGds75n81R7Sdp9nbPt3zkXp3MsH1TmaeUPvGPpWe+spC2i26sUj99cJf7jLL/a+Vxt9LEFp531HrjB7pOznqHbvG6szs53/t6ue9bMIZ109q/U+R6mO/vdPtnZ1//q7LezrXjrMdsNdN1lPvPxVO/4zLn+g3Ou55rvJENkQnNdk4Y0NIaa615x4QP97a8+dt+bNm+5rq3zrtmPnDRm0R1rlfeGSVOctOD9Sbrys6b56nldvfiS/v4n5np8T4dLV+t62zwzcA1Ou/VPVd3iXH/OtR6eDnRqxvWNKu3+2Ek3SrXis8ttmuWs/2yD7jTN/HceUmnjQuU1m3Rgsa5sdL7IgU4dcNKZysUNGvrw1+HX+BYnPWls0sJLEU1vnQw/Ot34rXZsH9Ks6wucdO9v1exrrt/lpCNh6VJEmjrrk1dphum+qMg5nvo2/a27Xuf4q9ZGN09N9Lcx0sQDJljkOxcnqlaosbxHe520fW687+C2Gu397ybtfk8Xr747qrsx813OORf4Xvzp/nI11U+K6E4hIs0031nMY7Tpst3P/tr1utLsv7O/Nc1/Z/dxSPW32WbOYXmGs72w76RI9Z+scdIfc/zXafXNF/Sme16OacZtazTN/f49Ud9NnLwl9B30qL05/Hv2/7a3n52nm5bXeF1jOPt0i37t5pXmWKPPZY0WOfnwQHP4+T1XslA3VF+y5/E6XX99vva7TayPuXnjp2y3lbGahJu8btYna4N53dU3V6n173doeszv+qAUdl2a7yDWd++7H4j523c+ozi/y+Bxjfa8/VIfXUp8X1CZ0v2F91tJ/L34f9fOfUJ/qWqcdEMH7GfM+mrOueuOewzxrlP/uUxwLd+y9JT9DYbun0Z3a43LLdfz6EC+cHrxF7TinPOM8Pe7vPQ+Zh7d46z/c6pw81l7vd62UCe2t2jymOXRu9TsXP+xnhGi8gF/vhW2DZOfLlSrTSN3tdXolshuKhJc15mZR0emd/GOMf+y5NHqaHH2+0rNMc+d5rc6rnm0Z9GKFbr44W+1/1SVGq6tCnZ5sujW9Sq2v+HQ87C5/4z9G4n5Xex8Wy3OPi0rcJa/8rbvudI5jkvOvi6wXVQ553nFjDa9sdP5HpKu336PsX4nJq+2+VfMazBR3hTxG/OnF6mWIYTlhXGu45jP3JHXYazfYvA34IiTBoz9vUr0NuKVmyCzkEdPjDw67rNC2D45edk9q5S/5VXt8uc/wbTtbR110vCrpweeSVZpWc8ObUuQ/gckfFaxabPZl6Rpy85DUWlVZdNazfn4Re87u7Rcty/uCUtrwtIy9xjXOunfb7WjZ6E+dVUgr/GeQd7xl3M6z1wrBuz+BI5pm0nP/OfmOl0970Pnt+ftc//ite4+R32n5jidR6pWm48GfpdJ7wUyGN1eZTTTnNQ2p3tLuvGLKXancOQjGw08ru6efrXv9Uq0u077M+9+7dnhRSvd5UfabKTT/I074SReUqOzzfvNKxgVtYLbcD637SP11TW4TVgXLSgNrjfAdAOi1tAFse/gcc1ekKjJ8Pt6r7lUC0yt/aoG1ZzxtrVowWwdPxhYd7v2ORdljVl3Ij179V6MSOTkTucQVg9p+2ND6t2eF2zBEeVoqMVAb71zrEu9vzEv0wJiYLazro+kY3bZ9kArhQDf3xeahM75rGG2H1N/nmba1hdl9u9SkWgf5jqnzG0l4hynDea6xzJ3v51xlJmWH7GkcPwu3+dKnPV2lXvTsVy4asj5Pdh9cgS6sSpTaN3B1hwJRK7HbPeIs48B9W97/07+KC+HuopLLxOpd9MX8zpYq/t9TfmPu133GNdpwdwiLbnHpgWmlkJpqaaba1BX2WVfCtUesMpXfEE3akewJkB4OmCuX/v0Yq5xMx+40drdpuNza7XI/YxNB5bWSjudH7mbztRoUZ3zGwt8PniN+9bpM6J0I1IwvXtfB23zKnM8JXNXhB1/cUWM9cb525hpYsS56HLS7l53Kv530HVmtps3mO9u3y9CNUFi86X7wXVH8KWZKR9jwO7TKvfvo13s8uUZ4d9J6Ly4+Y9zg36n8/d3N8nJ/xLXuBt+3uIJ/rbNb81d4u1TSeNdwWO90Tnf5cNt/On8Tot9+9O17Yf2+tohrXbWG9XVgzn2UlWa7bh5ncmH433XntB1mfy7T/jbj/G7TGY45y3ZfUGq9xepfS+h33UysY7Blcr5GME5A0ZjLPNoLbhbd9a160231miCPNpd/3EdDNxLd7Q47zr5cJX5TIbk0bG2YdLUUptGmpdJZ8oqou8HY1zXGZ1H+9O7VI8xIOfzaOd7KLPfT/B36r0TU8Lzl8p34WP21f6tOe4+c/5SXL/53Sb+ncS7Bu18CnlTKL1IIt66klzHrlQ+E0PCNGAE+e6wfz/BbSQqNwGikUenaIR5dLxnhcg8amuSFlomDVeFl+OZZxL3Gh/F9v1SS1uimfRltnk2NF0w7n7VtuAJF/oNmfTPrnf3Du0pM+UkNq+JTNs7nHTL3Z8vaIVpiRTWWi7gfXVV+PfZLk7Z8O4FJoqUgx+FU6dq+oyZmjFz1pi+zDbMtrKS6ffzFecKtIUJ42HRCq+pnpugO9uOf+PnXJBnTCLru+kcJZNIFTs3yMEbyHTbnqeVTzuvt6Q/PDakvXfY5UmYbqPcv7Mv001U701DKgosf3lsCtpn/CJPXffaoIMiuqdyjMc+GLGOP11OOpmz6QrMrPfaQAaO8eNklr0RmX6IaTbpu8FzbzKu0/WNPcHlPzfVGIKK3Ka47zjZ7toUAlnxmBsDU9hublS6OwLpzGyVu4XEl59p2ho6J9+PeeOSPrG+A7n9hZt5c67dG51ED/ojEPsY27XjH8x4F/amyQa5KpuuUnHw8zuCD52pCwT8X1N7nbkpc24I7TvjIewhxnmFN+E1hRPRhS2VFaXqPe0daWWF8yn70BzO3Ji/pj0xCmL2HexxHyLC87rY33Wksf7uUxX7vCW+L0j9/iLZ93KZdbyhN1tr3AcA80Crt3gAwBhJax5tRr1q08+d3+4to+ijPNPzaFPgEhw30bxidOeRPpcnj459jBMxj3YsrdXsUq9A5v4vmm4/ioKFdPt+YSshOO/5CxTT9xsJ/P6dV11PqCAyxfVnSn6efrF/i5dDRt9LIPuRRw9fkvRxOM8KCZmAkBswiHgmSdP2R5S2mLJd8/nhVHB3mQCSCWpF5DUBZqwTd39tuusLyAVVrVGj6UrN7u87KQaWQy7vvUCmSjn4UVxcqrz8PDs3dsw2zLayRWXTF8J/sFWlKjlz2r0oT53uD9XArapQsTc1dsy27WQsbgHObc5Nta+GT4AbbfUFbcKjtXG4idRVusV3UYfXGDIXvC1oCstszOBL7kRCZkwId2BzEwRxEp1URLYs6Nxo1+E3O6LlR5qY4EZ1IOhgx7WIK4V9iDyWgQQtNQISHr/z94EB2E0LkcpuOxODaYlhxgkJfj7wXfiksj9R63G262/NgmHypS/hnJulI7O9miOGk2HeHVWDXZpe4a+x4tWc8h42vQwxMh0Ifj6yBpt5eA20RNvdpr4K8zfejYpJZ8pX1KrYFjanIm66MUpR6dodX0o50BM3TYw8F8F0N953YPpR9b4L8+A8/JuXxBId46IVpWq2N02R/Uu7EuRLkd9JMM02x+Xmeeam6jXtSVLpbkR5SxyR+7RiwxeiCh/cz9jfs8s0N24M/J6831awJpU5lrCbQTPAfk/o/QDzG3eOYVFFIK9L7XozNb2Sffdj9dv3S3TeEt0XhElwf5HK9xIuIkiV5N5l9JyHVzNui70WKEzBmElrHu08rDtpZde2N5wH1zXuNRU3j45af4NzJdrCgwzOo6PvLe6OXQgQQ7bk0YmOcSLm0e76/YVQvkqDlU216n7FLg8UcI3iNxKLV5mhIVSwmPL6k/1OElyDIzDeZQjJfotjlgb4DP9eAhgm8ujhGW7667ufj76ekxW+O8dvAgZNFaHWM6PYvt9I0xYzXpL7ORMEeSv29xFMp508YsHcfnXbNN/8FoqXr7E9BoQz5cfus7oJgqQUMEpQdtpxWn2+3geCv7lh3gtMFCkHP8Yj8BEwntsaLdNlxs9Ph5rD3b9aesfeNLhdSgSaWN028ohwIvt27FWxrSVz/4Ie7SlLUIvbJK6lcRLDsJqRqdb6MImUnEzEd1E7iYNXI8as5y7VtL7hNSVz1t98JlDTx0kIvE8nVPh2ngZsSwozyHa17faq0AyS7SxvX+nNh9mepyt9XTN1tXrdNZU46+q3XWhtr89TfXmcvx8l00IlsO3tn7cLrWHvQ+Sx2MUJxTl+z5AO2eWmZYq/GzFzPsMSXudv5rcO6ff288e6vfXMdH4TgWM84ywzx+P+3fE8Vc5xlm8MBTpcEeuJ1SIGiXnNLe3Ll75E8teYu/8e55bqH8yDo9c9XaC56ILTJr2IvPkIfMZZHpEONJYFariE1woL34/j6i5brJrTgRuVHhXPtc1VUxUv3YjLKzw1zZMTBjNGlK5Zcf824lwsLw1eOzG/g6rjem9nqW898ZvXjkjc/TSpbOi7d1/OTVB4vuQcjZMux6yxFPad+NPsFjUH8zzzXe2IuKmL+G5S+g5MNwamWXOSm9FUfifuDapXQ8f9nPkegoPfOQ8b/vzK2bdXX+kJNZeO2yrAeWgxgwAq9Lexr7cIqXz3w/7t+6XhvCW4L0j5/mIExxC2bt81NCbMuXH23ds/75Uw3QCGYezzaCfP2dnjpKnOda54eXSiNCmD8+jI/DTB+YuSLXl03GO8XHm083x6usfrAsQUjIxnHr30bndQYK9rFcst3Fqs693fSamv2xnn5RY+juA3Yircme5AYxReunle42IpGOBJcf0p/E5Sui9I0XiUIYTE/i2GGUE+H5LGezxgmMijY0lfHh33WSFin8p3es9XYflPBDdg0Fij7uCxj2L7hpMPuK01Uj4//rSqRqd2fBTqhtLZdnOs3iPq1tj1mmdIX3eIbt5WFLt3nL0fqXu5Xa/5LQS6b4zIm/1lp2p1fmO3eXli+HcX/hsN/eaS3wtMRHnV8xYM2emETJdU46nz5Ak7lVhFRaJcNLnS8qjbotxlIoDOjfSbI24uHM3UMl5wkJqUpnXD3pukxb4WH6bVReFzeWNcozVFMfYPifV0j+4qmVBpCzKPSe9XnPb1I2pquqxQ1wTu89PUtLn+9A/Jr/zG4L4g00R971HXBrIReTSyGnl0BNOyokLv+fIiU+u2ckeyMVgwavwWMQbIozGxOXnaF2t1ME63yIhvtGlHdf1iOxWN4MdESVjNjc09Xp946Sr4MYEPU4Pn5zlcaDIcJtix39casnJ3gkHaxxvBj2Hjpg3ZzTy8mv607azDjA0ytmOeZDIe5qOMwX1BRrLHGaqI0K89gdZAyFrk0chu5NGR3OdK/6CuR3bE7g4MacZvEelHHo2JywQ+Vrjjd5GODh/BjwQIfgAYC9y0AQCQmcijAQDITOTRAEZiLIMfKY/5AQAAAAAAAAAAkA0IfgAAAAAAAAAAgJxC8AMAAAAAAAAAAOQUgh+YcMyAemuX2hkAAAAAAABgXFyntRvWyBvdxD8dUtn0hWC5VSaUYVGOhmw2+uDHrLv0+Ue/qocevV8NdlFw2T1N7uyMW//Sed98xv/yfT4XLb1bdzfV2Jn4Ft3xBa0Y3ZjtAAAAAAAAAHLMvl98X1t32xkAw1ZQUlbxdTudUFFxsZ2KULxEV189U4Uq1fw5g3qv5XBoWXerO180f6WurJ6i9je+rR/99Ld6791BVd/YqKsDn4/hbH+fnUps6tQiOzUyhVOn2amRqtGKDX+qTzXdoGXXz1P/+y3qWnq37l89W1NmLdGy2UPatfeYG7W999NNzmecz9llJnJ649zJmrH0Bs06+54OaI3u/uxCnWg+qHPOms3frC7ZpQMdMbbhbdzyv2/X1eG949/uwsJjajnc4613XpWWfXq95rmfvU5rv/g53WT27forVdi2S+395q99y68o12F3vxLti/n8KuUHl5nP3q2a4876ipxj+7P1usHdRujvzDm4flJgf03E+0qdNtupcj5/60KVXr9en6r1zleACRgtqlqoT3361vj7G7bc286dn/S2PbmnRDptt2m2E2O//OfNfz4xcZwfMFfhyI0+bQEAALGQRwMAkJkyIY+OVf7m8pf/BMq4YpQ9hcqPQuVoMcv+nKXJyo5i78sc1TdO1VG3jM0/HTJt3nLNOWfKA711mLLB04uddV09KXg8Zvnt8zqd/VsctywsIJ3laLGPKVFZIZDcaNOOksrpdipaeru9qvuEPnO1nU5FXX32t/5YukI1ra9p0w++r02v9Kjxjuuk3a/q58396m12lv/ifedD12mR3vA+47ze0VVua499v3hNe3r6teeVJFHcWNvwW+qcxX/w1r3pBzuk5bbJnJOI31LXrp+7y19Te92aYCuT2XXSm85ys91Fd6yQ3rJ//0q7am7z/r6y6arQ8p2lusW0ZEm4L+/rveZSNQZavDifXXLmI+0wwZvbatTuHKf7d29JN0YeQyxznfWY43LPoV+RllS0uev6ebO0ZIW3rnjHYVrh3Kgd3vIftDnrdT/uMPtVqmZ3ufmbwPFcp+t95617+d3O9wcAAAAAAIDMFbv8zSv/CZVL/by1xivjMvxlT2HlR75ytJhlYcnKjuLty8h0bftIx+fW2m3UaFFdj5q3tWvRHVepO1De9oOPVB4oCwuTpnI0U85Y8ZFd7qzr9FVed1jJyi2ByyhtwY+e1na5sdAb7tIMb1FyrfvVYiezVodz1I136f4vfkErnETt1aiCeuN9dVWYz3zJfd0YLHxPUbJt7D6t8nu8dd//xRWabRdXLnYS8NZAtLVdOzb/UDtsFPr4zjfs8uu0YO5xHQwEXzpanE86iaiTIHed7tHs1WadTgK++1W96iSqyfbFJMZ9dQ1uYrloQan27HDer2pw1tiufYEI+O42X4KdwBETOLHTYfq99Tq69rar152KfxyLFszW8YOB/XxfB4/YSbNfpbN1o/1e7r9nsUrKKpx9P65uLdadzrK7m+Sct1e1z/4JAAAAAAAAMlGc8reIcqmubT/0yrgMX9lTePmR8/lWqcaUrcUsC0tWdjTKssAopjyrVJUmgGKO50ybsz1TFlakJf4ywdJSRdeBT085milnLJm7InhMdzYWqbgi3vkBMkP6Wn50/aNe/6BPKrtS624qtwvD1awJjPfxCTfRefOVbfadLNbhXNSBCKlJbGJFN6vWqLFsr40Gf1/vBArfU5VkG6aFRrFpZeKuf4eT/KbJ7le9dZqWGk6i5o5hkvR4nUTxjEksnUS0zBfwyFQ9oe/FfW02QSETKDLzJspvEm9afgAAAAAAAGS00Za/xROzLCxJ2dEY7Mu+gz1uMMYEIfqCwYnjesduw3uNbQVet5cb3/a8itIplI0Cl0lau73q/M37zqUvldbVqNRbFMaM+fH8M4HXpuxv9eEwfd25TbzMhf7KXhs9TcRJpMrsZKSO0+rzRWinV3jjmQxrG1UVCozOYqK5sq0wjNiDq5sI7mwtMOs3fNHw4OdNEOQtL6SSyr64ifFtV6k40OrEFz12La3V7CMmQi2dOt3vRYkN374PX/zj2HfwuGYvCCS8vvMftV93ewm0k0HdHczITNdk3tsAAAAAAADIBonLf9wKvhEiy48W1Unte9tjl4UNq+woQVngcOxuc3tbWVTRY1tsRJaFOfu0IVa3V7EMvxwtupzxS+55GX7ZKDB+0jbguY43q/lQsz7uX6DrF9rQR8SA5z0Hf6uPT3hvJZMtA55POzdJRU2f8wb1WTpVB9/4pTs40LmShbrhhuu8wX92vq2Ltev1KTtQUOfeHi25tkaHm3cpr7ZJ16wMDIx0TKcLV+hTduCgirx+XTi+S0cPx95GwDnn/Xmf/pQ3QNHcUzp4ZrGurjqmlj27dKJ0te4MrK/tl3rDSY39AycZXXuHVH+PHeDIrP8Xr2p/v5MJnD6rhYHldX16/yc7dDbO8YbpKNK8ppnqePu39r0etR+v0op77MBSzrreeenXbmAkfN+n6rzz2VYz0FPRQjXUnFNLYGAqn+mLl2taux2Eyfe52MfhfMbJ5PoXfy44UFPh+cnOeTXnO85+FZ3XxaJV9rwtUeHeX2ubO8AVJhIGUwUAIDORRwMAkJkuex7dfzBu+dt+f/lP5TFt/ftd0WVPYeVHpjzIlqPFKgtTkrKjuPtyTnOarta17mDhUn1wOlT+Zcrtrr1huTsAedd0fxneMeU767xGu/Ub+/m4ZWE+aStHaz0YVs44r2eHXt12LG7ZKJCqsRzwPK963oIhO53QjJmz7FSEWXfp85+/Uvpgs370m1Z3UcM9X9Utdc5E6z/q+Ve2acatf6k/vqbYbfnxsw/djyTVeTK1KElFRVRThmEpLU8tHophMJHm26Q33S6kgOzU0z26Xy9pCwAAY4M8GgCAzEQeDWAkRpt2VNcvtlPRRh/8GCMEP7KUCXzcs1h9b31fWwODJgFZiJs2AAAyE3k0AACZiTwawEiMZfAjrWN+AG7/fj8g8AEAAAAAAAAAuHwIfgAAAAAAAAAAgJxC8AMAAAAAAAAAAOQUgh8AAAAAAAAAACCnEPwAAAAAAAAAAAA5heAHAAAAAAAAAADIKQQ/AAAAAAAAAABATkk5+DE0OGSnxt54bgsAAAAAAAAAAOSWlIMffX094xKUMNsw2wIAAAAAAAAAABiJvOp5C7K6mUVFRZWdGpnS8ko7BQAhPd1ddmpkSFsAABgb5NEAAGQm8mgAIzHatKO6frGdisaYHwAAAAAAAAAAIKcQ/AAAAAAAAAAAADmF4AcAAAAAAAAAAMgpBD8AAAAAAACAHDettEzFFZUqmzlLFXPmui8zbZaZ9wAg1xD8AAAAAAAAAHJcfkGBJk8rUsGkycpz5s3LTJtl5j0AyDUEPwAAAAAAAIAcVzDZC3pEcoMgznsAkGsIfgAAAAAAAAA5Ln9S/ABHovcAIFsR/AAAAAAAAAByXKxWHwGJ3gOAbEXwAwAAAAAAAAAA5BSCHwCAuG6+ucl9AQAAZBvuYwAAACY2gh8AgJhMYcEDD/xT90XBAQAAyCbcxwAAAIDgBwAgSqDAIICCAwAAkC24jwEAAICRVz1vwZCdzkoVFVV2amRKyyvtFACE9HR32amRGb+0ZaUe/c4DWlZiZx39O1/UY9/dbuc8qx75lh5aXmTnjH7teuFremabnfUJFBi8/fY7mjFjuruss/OUbrrpRr344n/Tb38b448AABgn2ZNHOzY8rufW1XrTbVu18Rsve9OZILhvbdqy8Zt6yVvqCN1bxLqnGK37nnxW693Njs354D4GAOKrnDPXTsXWdfSInRqZrMqjAWSM0aYd1fWL7VQ0Wn4AQNa6V088ZwonTKHFl7XRvF5vU9HyB/Tck/faz4QCH6YAw/3Mxq1qVZGWPfgtPRqjEmRt7Txt3fqG/vN//q92idxps6yhYYldAgAAkrmvwQY+jNolus9OjhcTaHjuuWf1xAa7wG/zVu3qNRO1avC/37RSi9xKFW16K82Bj/HAfQwAAAACCH4AQJZa9chNqnP+7d+5NVRbM1CQUXuTDWys1I31XouPjhOBAoyX9dQLL+r5jbFbfvzoR3/rviKZZf6CBAAAkMi98mIfbWptM/9GBBmimBYXCYIVabdd7+zvd6fqGnyVJlYslHvn0LbH1xokfV76hq2wMUatYLiPAQAAQEDK3V4VTp2q4uJS5eXn2SVjY2hwSH19PRo4d84uSYxurwCMhcxvrhvqkqL19S/rqc12sSPQnUSgq4rI+eH46lcfc//99refdv8FAOByy5ouNQLdSvU2a8v+hVpvup+M7OrJ3y2W5c/XI7ut9OfloZadzepY3uhWiHA+Ybu1jO4WM2Y3U00P6+kHG1Xk6/oqcN8Q2g/T0nStXb/h7yYr8J4J8NSqLvh3kX8TWp+/RWrksQTEOgexj9OdiYv7GAAIR7dXADJRRnR7NR6BD8Nsw2wLAJBIrcr9BRoxFFV6hSkvbWmWqdfpdof1nKlR+vi4d7sBAMBEE+jyqn//dr10otOdDuv6ygQebHDkedMS4gUvv65zlq9y/vUHCUxLied39rt5eWSrkKLlZWoJ/n2Rlm0wf79dz3zlRdutlRdMiNnSYtt27XM/M0PVbovRUGuVlrDAR6CLTdN1Zq3Wf8fbxxDnj1rM+yZoYQIv5m9MgMIs8/ajbl3s+49Yx1m3Lrr1S+zjBAAAAOJLOfgxHoGPgPHcFgDkvG3f02NugUWgwKBW600QJKrgAgAApMdKVdvKq263k61n3MBGKMjgqCvzupfqOqZ3zb/bjqnD/FtSpvnBbiv7tW+H1zriXRtACQRHAsK6vzTcv09VoOurIi1asVLasMRrWRHo8ipyXm3qNsGSkkbdHhacCARLYjGBGHMf4h9UPWC8jhMAAAATEWN+AEBWsoUPCfR3uR2Mh3MDIbYmaFTBBQAASIumwKDh/epudf4JtrCwQQYjEBCprPYK+Zuq5Xbo23tGh4ItPIu07EFvHJDI7rHS5d0dB9z9MC1GV82a4S5rbfFaiQTmVbvWth6N6Eorpu16ZrNtnRHYd+cVexyT5C1ZAQAAgJEi+AEAWSk0SGnVLFuI4grUNA3VoDRdVtxHkAMAgHETHDQ8GAAIBQ2K6ld6wY5t39OmnU5eXtKoh0yAwIy9YbrA+sr39G6wkkOg6yjfy30/jQItTmqX6PZKs9ehVhyBVhjueCH+fXBe/vHGovhbnbpdZcXr9ip5ZQ4AAABgpAh+AECWeve7b7uFCUXL14YKEzas9QpX2t62g4B6fXWv9/edHayNmqiLCk9b22H3BQAAUhXoysmOtRERBFDJQt1our7a8LgeWq7w4EYwsBHRHZXL5Onf0qOBbrOS2q5jKY0d+bJa3Mai3oDloS6uHJv3ePvsG6vEjNHx3JP32rlYzJgfz8boXtPX5VdQ9HEGW59sG32Qh/sYAACAiY3gBwBkrZf1lNuFlR3Dw7zW1XoDhgYGNW1q0y+DA43azwRqlsbsezvcj370t+4LAACkKFARIaqSQSDIUKRl6++1gYXwrqG8lxfgePe7XwsOcu4tX6u63k6Vr0gUeAj30hY7iLq5B0gQsHjJ2zFXoMsrj7nXsIOc2/0zg5O3qjp+EKapVse2OdsNtGgJDpj+NVsxI1zkcbrrfz1Jy5IUcR8DAAAwseVVz1swZKcTmjFzlp0aH50nT9ipxCoq3J5xR6y03I5ECAA+Pd0pVZWMi7QFAICxkSt5tGlB8dDyTm3xV0ZoelhPm0oKppupQEUGAADSpHLOXDsVW9fRI3ZqZHiOBjASo007qusX26lotPwAAAAAgHE23x1fI0JdmdylgUHQAQAAAIwYLT+IKgOIgRorAABkptzJo71xuersXFBw0HMAANKLlh8AMtFYtvwYdfBjxq1/qT++plhq/Uc9/4rXiWtgWfsb39bPPpQa7vmqbom4q+/5YLN+9Bt3+LyYCH4AuJy4aQMAIDORRwMAMDIEPwBkorEMfqSv26u6T+gzV9vpmPr00Y++reefcV5vtKv0mg36/K1R9ZwAAAAAAAAAAABGJa1jftSsuV8NdjqhD/9RH52RShcu0wy7CAAAAAAAAAAAIB3SFPzoU3trn/Nvja4dTmuOsnKNrtMqAAAAAAAAAACAcGlr+XH67de81hzX3KUbU+6ir1wzxnccdQAAAAAAAAAAkOPS2O1Vq377u3bn32LV1BV7i5I506qW1MY1BwAAAAAAAAAASElax/zQh5v0ZqudTuTqT+iqMqnnwC512kUAAAAAAAAAAADpkN7gh6PllX+Uaf8RrVhXff6reuhR57WmRj0fbNaPfpNKpAQAAAAAAAAAACB1edXzFgzZ6YRmzBzfwTk6T6bWH1ZFxeiGTC8tT3mAEgATSE93l50aGdIWAADGBnk0AAAjUzlnrp2KrevoETs1MuTRAEZitGlHdf1iOxWN4AcJK4AYLvdN27SiEjsFAEBuOdvfa6dGhjwaAICRmVpebqdiI/gB4HIg+JEAwQ8AY4GbNgAAMhN5NAAAI0PLDwCZaCyDH2kf8wMAAAAAAAAAAOByIvgBAAAAAAAAAAByCsEPAAAAAAAAAACQUwh+AAAAAAAAAACAnELwAwAAAAAAAAAA5JSUgx9Dg0N2auyN57YAAAAAAAAAAEBuyauetyClSEPh1KkqLi5VXn6eXTI2TOCjr69HA+fO2SWJVVRU2amRKS2vtFMAENLT3WWnRoa0BQCAsUEeDQDAyFTOmWunYus6esROjQx5NLJZ/qRJmjylUJMKC4P/FkyarLz8AhXk5zv/mleBWzY+ODiowUsXNWT+dV5DFy/q/MBZXRw4rwvnz+nCwIAGnWVIzWjTjur6xXYqWsrBj0xF8APAWOCmDQCAzEQeDQDAyBD8AEIKJk9W4bRiFZaUaGpJqRvwSKdLFy7oXG+PzvU5r95eXbp4wb6DSGMZ/GDMDwAAAAAAAABATptcOFXlM2eruv4KzW1oVFXdApVOr0p74MMwwZXiyumaMW++5i5t1Jwrlqp8VrW7HOOH4AcAAAAAAAAAIOfkF0xygxAz59d7AYjZczSlqNi+O37cwMusajfoMmvhIhWXVyovb2yHlwDBj1HrW1Wv9n9/r/sy08BoXbdonb72T37ovsw0MFKrHvmWnn5kpZ1DRtnwuJ577nHdZ2fHWsb9Fpoe1tPPfUuPNtl5AACs+558Vs89ea+di8PkI995WKvsbGL36olxzHMBAEBmmFxY6AY65lzRoBlz6zSttMy+c/lNLS7VjNr5biDEBETMeCMYG4z5Mcr+BE3QY7DYaxqV3zegmn/zsjsNjJQJehQXegly38AZfetvv+BOY3xlQ1+lpnCgoeXLemqzXeDwLzMF3vdrsx777nb77uiZdd5+4mth20yJKexfV2tn2rRl4zf1kp1zCzAebFSRnW19PfyY/MzxrQ+sprdZz3/le3rXzgaYfXxoeVH89URsz4j12ajzF+PvPP3a9cLX9Eyd/xgTHce9euI71fqlu+8r9eh3HtCyEvuWy65vm51Ng7i/hbDvxWrbqo3fGI+8zH8eAGSf6PSrf+eLac1z4sn6/sQDaW+S9DaQn3l8eWec/Ch2vhPxPaWSxifYv4T5cFiekv68LIw5BxukTXHykPue/JaqtwS2b4IfS9Tiv/cAgBzgT5NTzYMZ8wMTwZRp01RcMd1t7ZGfX2CXZjYzcHrf6VM603FCF8+ft0snDsb8yGCBwIfhn544rtPaL35J99vX3U01djlGKhD4MPzTE1ll0xfsb+wLWhEj3rnojtBv8P4Na8TtUoYxBRTrpC0bv6yNzuv5nTO0Plhbc6Ue3dCojte99za+0KyqdXFqZ254XOsrm/W8Xc+WrkbdH9mawfnMQ/UHtKvNzsdS51xXzsOBuz37ihdsCbPte3rMfv75nf3uA4b391/TMwo/xo0bt0rrntUTG+zf+qx65CZ3XaHCGlNAFPg7u76xKiyKxRRuBbftvMYl8GG8rF/uX6jbY5wjAJlv1SMbtKzEFMh7accWJ90tWv5AzHQPISag8ZzJL15PlFE5bH4WyPPcvNPfGiIs7d6qVicv6W617/m431NX4LMvalfl2oTfUcL9S5gP36sn1s0I5Wevd2rZhlRbZqTKBDGepVUrABgmTa51niR6++0CAAWTJquiukbVixpUOmNm1gQ+jLz8fJVMr1LNkqvcIGU27XumI/iBUTCBjxWa7Uz1Nr+mTT/4vl7d1u69hcunao3ujhMAcIMEd1znTpuAQtxgVYJ1jDtnX25pjK5rH2CO6UZTeaVnr37u/AY3bX5Do4sX55qVbkHBc+bl6x7C1IgMdTlkaoWG5k0NoiceeVhP278LFDKY5aYGat26UHcUidYTsl2bfLUt391xQMFb9KaVWqRm/TIQfNj2Pb3VVquGGAUz9zXUqtUXNHhpS7NUv9JXsGIKXqQtX/mejtkl46r3jA7ZSVOw/9TG2DVwb6xXzAKqaF4hj/vdOa+wwirnYSewPPx7DX3ev3xE/NsI6y4kzn7F+3zY8vDfx7snOlXXkKRrEwAZ6d3vfk0bo2rSxy6An2jcAEJYt00m3TTp4r26fXlneOvHeDZ/UxsjWjX0d8UJmGxYorq2t2MEzr08Z9eWQFB7u/OZtgTpbuL9S5gPR+7D5q3apYW6MeqewDIVI5x86tFgvhXKN8z5CwU4/HnOWlVF1Gy+Mervzb2IqQldpGUPRgZKvPei8tPR5pcAMO7Mc0+t2wJv07ZOuwyY2EzgYHb9YpVVzbJL0mxoSBcvnNeFgbMavHTJbakReKWbCdzMWbLUHRMEo0fwY8zVaMUGX61093W3Ftl3x5wpxLaF3elljisU+CDoMf5CrSHMK3aLiEj7fvF9bfrF+3YuOyxasVgl6ldvj13gY85BMPBB0COmouVlarE1NJ/fvzC6pUQcdfXSJvfvtqpj+Qa3wPqlb3g1e023GsNqHbBte1jhzaoVC1XUdcxbVlcW1W3Hoa5+Vc2K3M+Vqo7M97cdU0dJmebb2fueXCu9nrxAadWsGc6PZ20wuJOWQg8TtOlq1EN2nfFr1daqXAf0TlgBlVdA4xXeBPbHFNCY47E1aDe+qO4mGziIbEnjfK8PmUI2Z3l1S+DzXo3clFpV1K4NbTuw7xHb2PjCGa2258m0XDGFT952trqfdfe3yV/jV1rt/tac5bP22M+a5Z1att5X6LZ5j1orqyl0ArJYIOi6XqZ1wTi3XMtQ7353s3ZVLvEV5pt0c6uTP5nAeAqBj6BQwX9Y14VOnvNYMB+26W8wwOHn5Dlh3So6Ws+oP266m2j/EufDbt4aZruOdRWpvM7OxlLSqHKbb0W1bLEi85yO5WtDwfWYf79dz3zlRe3q9VpVhncB47y3uVlVbr5l2HO3OXbXWQCQscz9epuTzpF+ASqcVqRZCxZpes08TZoyuh55zp87q9PHj+pU+2F1tB7Uif171b7nDzr8h11qbf5A7S0f6ejHLe5820c7g6/WD3/vLP+DThzcp1NH2tTTeUIXBgbsWkfGtGIxY4LMXrDYncbIEfwYF/3a88r33ZYR5vXz5lLdOCYBiXG0dIWWlDr/9uzVmwQ+xt/Su3VnXbvX0sG8XmlXzT3jGFQbL1Vr1DhXOv7WG4r+lV2n690WIc719Q8EPuLpdwtbPKbFRXhLifhCNTvb1N3rTqSF1395p7aku2ulDY+7BW8pdV/lKKo8Y4M7cbrPGgETHAoUzpgur1If0Dyi2yu3pq8psGpTS/B4tuud/SYY6OynCRi17Ql9r6b2tTmf27ZLTaEgRrBP9mQiur1yz2HENsy699kavKa1hunaxmvF8bKecvfXK+RygzgmSLL5m7bAydlv3RTcp6jxRQBkvUDat0UmkBqr9d9EZNLsGcEgsGl9sW+HvxA+VV4rQnN+N2lD7EHAN6zVsq5YrT6S87cWHNOupExAPZAP+PPG3lDLT5O3xDK/Mn7r31T+PoovP/Nan0ZWSACALLDZyR++QYUDoLiiUjMXLNLUElNAOTLn+nrdsW5M8OLY3hadOXlcvac61H/mtM719+ri+QG3pUcyJthxrrdHvV2dzvra3fWZ1+ljRzVwduTd0xWWlKj6igYVFhfbJRgugh+XQdfpHqmswutSaOndMWvve10SrfHG0wgESsI+GyroDvts5OfvWaySuStCn4+zjuFatMC0+ZCOt0q3BNf3Ja1d6i7GmKrRiuWzdXynr8C/4w29+oM25/u1847QOBiRv5XYXV35W5Ks9Y8TZLvAWuGuL8nvKOyzEe8Nm3Octzm/3569em+3XeS3tNZteaQj7dJtge05r2wPLA5D7BYSmcvtNsvtv9xXq9TUQLWTAaago+NEZCGRKVy3kwFN1aqyXU2Zrjj8LRhMwb/pnitWYY4bLHAL7D0vtbSpqDKdhfJeYdWWON13pcQcm52MFF271nILwEKBDNNKZ6TibsMwXbGYbbxwQIsCwQ5ncaAA1G2JEixIC3Sh4r23MVkf9wCylsmTTEs2N0iLUIWDNBWwu11GRrXYMC0XatXaEq9CQYwKDCa4bVtfhoL2kS0kYkmcD0cHH0xLEdsNmmmpEsgHhtXyxezji9pXbwLuJn9PrYVnYqbrr073d2paomp/eOtUAACQHcpmztaMefOVXzD8sTHO9vYEAx4nDuxVT+fJUbfUiMWs80zHcR3ft0dHWprdbQ709ZketIaloGCSZi+8QqUzx6hLrxxH8OMyqKwolc6cVpcp3K1oC7YI2fRWj5asCBXcljSW6qBZbropMoXKy3t8Nf171OgbkyH42R+8pj1lK7wgxO5XnXUel47scJa/qn1uoXlpqBXKW1LjiAYor1GlHYd7dp30prs/e2WerWavTq37JYzGbJWXOg+THXY26H3tCwQJSher/KD3PafU0mhpeEuSgxWmqymf4Pqc31GS3+Kwtx2PbV0UFuTxca8jY67zG/4Hsz3nt2+6xpq7YsIMvO/WwPe35Gh6WKtrw/tb9xfq37e+0feQ7y+gitEtRspSWc9Kt4/t1V0vRvVf7tXA9HXP5B5DqMWDWyvV1nQ1QYq6YFcV4cfjL8Axr0D3XF5hjtdtiNcVlbcv/qCICZzE7Uc9RW7/7mEtPe5VQ8R34WlTt0JddcVlzkuvP3gSqjnsFoDVhrpTcfsrd85ReMAiRvckwxC1DV/hnTlW91y6hVlb1ep2eWLOq3f8bnDpdRtQigjiRAVVzPuBLtAAZBd3PJ9QuheooR8dvJ6gbAuD29cvVEewNWVibl5iA8r+/M8I6zIywE2bfeNmufx5ntdqMNTdYLJgSWKJ8mG3G8Pam0Itf0xAPg1BHzNge/m2UP6eagvPhJx97ajfoPuXd+qtpEEfAACQaUzQo2L2HDuXur7Tp3R8/x6dPLhvzAIe8Vy6cMHd5vEDH+vono/cFiLDVTm7RlV1C+wcUkXwY1wUack9oZrpdzb26B133IV256HoqlCN9dVea4qA3uYdzvtWValKShfrzsBnTYuO0lJNt2+HPtuurjPuRAzmPbsvprB696ujHqsjWDDd0aJ2d0yGItUsnhgFzxnN11rCbWmUhAkk9La2BIMM+3Z4wawgf+uLJL/F4W47Nq91S9h24znykXa4gSDnemr12hCU1DVkxmDtY23zN90umwLjTDz3YKM6Xg9v/txfGepyaH1lszbZh/yXvmH6zQ7UpLxJVSl2bWVq9voHPE9pPaYApMRJHYKfM69A1yimb+5AN1HOyz2GQK3OiAJ853gDrQrMZ/3Hk1BYAbzXF3ioFqnXT328Gq/h+xy/GyuvwF9aH/ysqZ0aqym61xVK8hYhEefluQdUvs2uzwQd/NtqOqPnv/Gy7WM+0PrF2cD+Nue7sgEJ21VV/LFIIkRu48EyvRUIXO14W93B7rVCtXDf2XZGqwOfN+OFuF1xfU+bTD/sdvn9OqDW2rXB/TCFeR0jLIQDcDmt1KOzpFbVBq9v0+LOBJ3TUjidE7zAQ11tp68LQxvUcNNJ54QFWizagIe/iycT1Pe6EvM+77acDOsy8l49YfLMyMBKRNDZ5E+h9TzgthBM9B0l2r/E+fDLeirQItD9+/SMpWHyL/e+w27TvJLnZaGuGL3KDqYFjPNbDRyHs68tzvth3TsCQJbxKl+Z9Nar7BZ4bkn5fh/IUrMWLna7uxqO/jPd7ngcnYdbNdA/8i6o0sUMnG7GBjGtQUwXW8NRVFah2fVXKD9/+C1eJqq86nkLhtnYJrNUVIyumUHpKEfOP/x//5md8sz7l39tpwJqtGLDGukffmgLaP2u09ov1uqg2yrDYboSWtDmtvQwXRDdojeCwQkzf2fFRzEHq478rOnuaMHB72urKTT2rdPPXV9j0YgHKzfbMANNH3/Lbsc9zru8mvrBZRiJr3/hJ3bK8/Uffs5OBcT+TVVW1airw/kuTcuM26Q3AwOAx/ld+afN99l42vdb8K8jYn2JfouJtj085trwBtSPxf3dnl7hBQxNyya7/sDv2r9spHq6A6GgkRlt2oJcdq+e+E61fhnZCmbC4TwAGBny6InCtGJZohZ/V1lm/JAN0qZR5R2mtaKpVECwDsDEUzlnrp2KzXTLMxrk0RhLNUuWatKUqXYuuYG+XvV0euN3ZLLJhVPd1ixTpk2zS5Izg7ObgM7gxYt2SXYbbdpRXe/vvz8cLT8up6oK+YerCXbjE0PX3nb1zq2NGltheNmCKTT3xmDo2vZDt0uskop4xcuJ7Tt43P139gLbpVFVg2rc3T+ugwQ+xphp4SAtuc33/Tu/h1vuuWvEY66Y7zOstYRp3WEnI6Xnt5jM+9oa6FbLfdkurczg5q983wvS7G5zfm2O4L7UaFGdV2Py+MHRBT6AsfWyntpWpodSHhA9B5nCq+duUncaagUDAHLVy2ppC7Uucl8PLtS+UeUdJqDygJZFdRcGAAAyWe1Vy1MOfFw4d06njrTq+IG9GR/4MC4MnNOxfS3u4OipmjJ1mubUL1HBlCl2CeKh5cdlbfnhq6nu6G3eq77GxdJb39d7FeGtOVymFn2wa6zjese2GPHX4DfCWn6YgmnTLZH7+R06tbRBt6wOjOcQWseIBNdt9ezVzwM1/jFiyVt+ePy/HS8oYH9j5ntJoeVH4PcUaKkTtr6efvWqPWbLD1ec32KibY9OoGWR7zhdoRZHnsj3R44aKwAAZCbyaAAARoaWH8hG8668WvkFk+xcYqa1R+eRVl08f94uyS6Tpk5VlWkFMjW1ViCmC61j+z7W4MULdkl2GsuWHwQ/xjz4AQxPqsEPjC1u2gAAyEzk0QAAjAzBD2QbM75FYZG/35z4+rtPq6PtoJ3LXnl5eaqYNUclVbOcabswAdPS5fj+jzU4eMkuyT4ZFfxYfdfYdtLx1mvDG3aO4AdyDcGPzJDxN20F+c6LAa4AABnmkvPQdWnQzowN8mgAAGJIIQ8m+IFsUlW3wB3gOxU9nSdH/fvNNFOLSzVz/kLl5ScftWKgv08nDuzT0NDY3oePlYwKfmSayx38aP/392qwuNCdzu8bUM2/edmdBkbqa//khyouLHOn+wbO6Ft/+wV3GuMr42/apkzWzU036s/uu1eFhV4alE4DAwP665de1m+3vWOXAACQhHmqMLXTzo9ts/tszqPJXwEAYyLFPJjgB7KFGQC8uCK134Pp5qqv65Sdyy1TphVp1oJFyk+hYs3ZnjM6eWi/ncsuYxn8YMDzUSr/6e/doId5mWlgtF5//z+7QQ/zMtNAPGMV+DDMes36AQBIWQrN8ieKeHk0+SsAYEyQByOHlM2cnXLg4+jHu3M28GGcP9vvdWl18aJdEt+00jKVVc2ycwgg+DFKxe/ud1t7mJeZBkbr/X2vu609zMtMA/GMVeAjYKzXDwBArkqUh5K/AgAAxGaCHhWz59i5xNqaP9CFgXN2LneZYzx+ILUxPcy5S3WMlImC4AcAAAAAAAAA4LIpnFakyjnz7Fxi7Xv+oKGhrB7JYVguDAzo5MEUxvTIy9PMugXKnzTJLgDBDwDIUqbf8LE01usHACBXJcpDyV8BABgb707pDnshu5TPnpPS2BZmYO+L5yfe/dRAf79OHjqoZDGf/EmTNT3FINJEQPADALKUGTB1rApQzHrN+gEASNnEqXyXVLw8mvwVADAmyIOR5UqmV2lqSamdi+/UkTad6+uxcxPPud4z6j5x1M7FV1ReocJiur8y8qrnLcjqJLKiospOjUxpeWoD6MRz3aJ1unPFv9CUSVPtEgCX0/mL5/TzHf9p1OOl9HR32amRGW3aklRBvvNKXiMCAIBxdemS80rSHH+UyKMBAIghhTy4cs5cOxVb19EjdmpkLnceHdnaY9X5cjuFTFYwabJm1y/WpCmJx0XrPnHMfUGatXCRphYnDhaZsULMgPDZYLRpR7Xz+4mH4McoE9b//fMv6d3ZR/TXtb/TQH7ykfdHonBwkv6sbYU+0VlvlwCIZ8j578LFAf1/fnSfXTIyGV+wAgDABEUeDQDAyBD8GK6VevQ7D2jR/hf12He322Uh9z35rNZrqzZ+YwStOpse1tMbpE1f+Z7etYsCZs+pUUPj1Zo3f776env0yo/+Rp/54z9RxfTp9hOeTT/4vp3KbhXVNSqrmmXnYus9fUqnDrfaORQUTFL1kqXuv4l0HT2sns4OO5e5xjL4QbdXo2RafLiBj7yxCXwYZt1/XbvDzgFIJM/5j5ZYAAAAAAAgllWPfEvPPfesfX1LjzbZN6Js1zNf+XIo8GECFk/e6007XvrGl0cW+EiiobFRs+fM0a73fqfXf/ZTd9nO93a48xfOn3fnc8WUadOSBj4uXbig7mPJu3qaSC5duqhTbYfsXHzls+YoP39it0gm+JEGbouPPDszFpx1j1WrEgAAAAAAAGBC2PC4Hqo/oOc3flkbzeuFA1r04OMaXd8R6TVv/gLt/nCXdr3/nvp6et1lhw8dUklpmSZPmZJTAZDiivDWLLH0nurQpYsX7BwCzvb2qO/0aTsXmxlAvnTG6HpNynYEPwAAAAAAAADkuJV6tKlWrdt8XU05049t3CNtcKZNy47vPKxHnzQtQryAiOna6gnz3obH9dyDjSqqXRt8z7QgefqRlc6UYbrICrQmsX9jmHUGW5n4lg/TTbd+SguvuEIHPv7YDYzkgsmFhSquTBz8uHh+QD2nMr/bpsul69hhDQ5esnOxlc6cNaFbfxD8SAMzJofGcuQUZ93uNgAAAAAAAACMQK3KS/rVHTV0xMt6abOdLGlUeYtpFfJNvWQXuTZ/Uxtfb5Patka/57jvSW9sELc1ycat0jqvO637Vkibgq1MmlXV9LBW2b9JlT/w8fZvfqWuU506feqUfTd7FVVMT1oo3+sc66AZzB8xDV68qNPH2u1cbOYcmwDIREXwY5TOXzynP2u7QYVDYxecMOs2A54DSM4MeG6uSwAAAAAAgGHpbdYvA4GQlK1UdWW/9u0IDIr+sp7a+DU9s0166USZHgq0/DAtR+wnUlVcWhIW+DBMF1g/+/HfudNjJXxcFOflG+skHfILJqkkSauPCwPnaPWRAhMgMuOiJFI6fYby8sZyzIbMRfBjlH6+4z9p1fG5evb3G/T8e/ePycus+xOd9XaLABK5cHHAvS4BAAAAAABC2tTtDaERZlVToOuqkTItSuxkGNPN1gzteiHU8qPfvpMqM+bH1p+9Fgx8GCYgcs+GP7Nz2WlaWZkKJk22c7GZQv2hwUE7h0R6Ok/YqdhMsKl0+kw7N7HkVc9bMJYdNo25iorRDdpSWl5ppwAgpKe7y06NDGkLAABjgzwaAICRqZwz107F1nX0iJ0amcudR787pdtOeVadL7dTIaZFgzvg+VfsuB9mTI4HG9Xx+pf1VKszvUHaFHjPYcb8aGhx3jOtQcy4Hw17tPEbL7vvmXXdr8167Lvb3c+t7nrRnfbG/3BWtPltlT94k7pf8FqBuH/fdMbbttluxLYC7v/il7Trvd+5A57Hsuy667Xs+hu06Qfft0uyz8z59ZpWWmbnop0/e1bH9u1xprK62Hrc5OXna25DozvAeTwDfX06fuBjO5dZRpt2VNcvtlPRaPkBAAAAAAAAIOe9+92v6fn9C31dUS3UvhdscCOZ1jPq9w147vfSN17UvvoHbDdRD6h8mwl4vKynXu/UsgftthrOaJcadb8ZJH3bMXWUNOqhGN1JHT50UA1XX+0GOGK9zHvmM9lqcuHUhIEPo6/bjGlC4CNVpoVMb1fiLsIKi4tVOG24Ha9lv5RaflyYN1U998zQ+YXTpLEeHP6SNOXAWZW+0qnJh5P320/LDwBjgVqlAABkJvJoAABGhpYf2WH2nBo1NDZq3vwFdkk4E/hoaW7W8aOJB7rOVOUzZ6t89hw7F1v73t26eI7xXIfDBJXmXLHUzsXW331aHW2ZFzgby5YfSYMfJvDR+dg8L+hhPjnWY6MEtnFJmvH04aQBEIIfAMYCBSsAAGQm8mgAAEaG4AcyQXX9FZpSVGznomVy90yZbs4VV2pyYaGdi3bp0kUd+cOHdi5zjGXwI2m3V6bFR7C1x3gMCh/YhrNNd9sAAAAAAAAAgKxWMGVKwsCHcbb3jJ3CcJ3vjzGiv09BwSQVTBrrbp0yS9Lgh9vV1eXoYs3ZprttAAAAAAAAAEBWm1pcYqfiO3smvAUPUnem44Sdiq9k+kw7NTEkH/DcBIPGo8VHJLPNiRWIAgAAAAAAADACppsr/wuZZ2pR4uDH+XNndWGAsT5G6sLAgIaGErdimFZeYacmhuTBDwAAAAAAAAAARiFZyw9afYzexfOJg0eTp8QfEyQXEfwAgBy16pFv6elHVto5ZJQNj+u55x7XfXZ2rGXcb6HpYT393Lf0aJOdBwAgI9yrJ8YxfwYAYCLJnzTJHfMjkf7u03YKI3XuTOIxU/Ly8pSfP3G6W8qrnrcgYVuYY/8h/mjp46H6r/baqdgqKqrs1MiUllfaKQAI6enuslMjMx5py31PPquGli/rqc12gcO/zBR436/Neuy72+27o2fWefuJr4VtMyWmsH9drZ1p05aN39RLds4tCH+wUUV2tvX18GPyM8e3PrCa3mY9/5Xv6V07G2D28aHlRQnX4zEFHGtVl2A9YecvYj9D+rXrha/pmTr/MSY6Dme736nWL91trtSj33lAy8Iqv9j1bbOzaRD3txD2vVhtW7XxGy/bmbHkPw8AMl0w/fWnmRFpSP/OF9Oa58STDXl0QoHzliS9DeRnHl/eGSc/ip3vROQzSdP48M/H/k4Dn4nIz4creBypryfsPiDiWOKeLyPRfUgYc2+wRC2p7E9g/2PcR4TvS+J7GwAYKX+amGoeXDlnrp2KrevoETs1MlmfR2NMFRYVa3b9FXYu2uDgoA5/tNPOYaSmTC1S9eIldi62kwf3Z9TA8qNNO6rr48cvaPkBABhbpnBgnbRl45e10Xk9v3OG1n/nYa1y31ypRzc0quN1772NLzSral2cGpcbHtf6ymY9b9ezpatR90e2ZnA+81D9Ae1qs/NxmYKbtdLOZvXbJUlt+54eCx5Dv/uA4e7zxq/pGYUf48aNW6V1z+qJDfZvfVY9cpO7rlAhiQl2BP7Ori+NgY+kTOFRcNvOa1wCH8bL+uX+hbo9xjkCkGFM+huKcXhsYXIwLXy9TUXLH4iZ7iHEFIo/Z/IL53wlZPOzQJ7n5p1P3mvfdISl3VvV6uQl3a32PZ9Vj2zQsq7AZ1/Ursq1Cb+j+558QIv2B/K3reqI8Z16n2l2tjkcJqjwrK8VojP/4ELtc/O/1AIf5tytV/ixBNeX6HwlvA8ZIfP7N/v/eoz7CGd79y/vDN0TJLq3AYCRsnlzf2/KTzPAZTepMHF3S4MXL9opjMb5c8nThcIUBp7PFQQ/ACCnrXQLG54zL9+D/n1P+rscMoGA0LypQfTEIw/raft3gYIFs9zUYqxb5yy3BQqJ1hOyXZt8BRvv7jgQKihoWqlFatYvA7Uht31Pb7XVqiFGwcx9DbVq9QUNXtrSLNWv9BVe3KsnTOHGV76nY3ZJPKYwyBTuPLXDLkiH3jM6ZCdNwf5TG2PXwL2xXjELqKJ5BUXud+e8wgqfTKFL4L2w7zX0ef/yEfFvI6wLkDj7Fe/zYcvDfx/vnuhUXYOvMA9ABjJpqwlyhBd2mzTZ6Dhha5m2nnHTdq5pG+DwByrcdNOki/fqdrdQPIXC/s3f1MaI1gT9XXECJhuWqK7t7RiBcy/P2bUlENTe7nymLcF3tFKHtnzZV3P4ZbVEbtIUtmmr85lkOa3hzy/WqspXK/m+J9dKr8cO9sfNy3Zs9gXot+ud/b4H+4TnK8F9iOHLp55+pNoutGLmbc79TtMMr5VmrPx82zF12MmgXucVdX8EACPl5c2mNeambZ12GZD5ko01MXjxgp3CaCUb9HzytGl2KvcR/ACAHFa0vEwtgZqO+xdGt5SIo65e2uT+nan1ucEtsH7pG1/Wljav64ZhtQ7Ytj28K4gVC1XUdcxbVlcW1W3Hoa5+Vc2K3M+Vqo5sAW0KF0rKNN/OegUpqXVTcX/9AW1KZ9csJmjT1aiHbAFJ/Fq1tSrXAb0TVthTpGUPBgpWnJdb0GNbpgRaxGx8Ud1NNnAQWYPV+V4fMoVszvLqlsDnvZYxKbWqqF0b2nZg3yO2sfGFM1ptC6BMyxVTgOVtZ6v7WXd/3YIg+/nXpdXub81ZPmuP/axZ3qll632Fbpv3qLWyenRBGgBjyqStpnvATRGF3SatNoLpdYz0fKJ697ubtatySTAI7KWbW538yQTGU2vl4AkFD8K6LjQtEYP5sE1/gwEOPyfPiazUZ4JUcdNdJ78Oy5/uVUOtv0WJKWxz8oYU7wEi84uO5Wu9c+LkG6srm9XdEJH32Pfi5WXvOvcTISawUxQKvrnina8E9yER+d0mLVSd+ymHea/pTLA1SSgv3K5nvpKolab5nveowe7Lc87+b/qK873H/TwADJNJF9v6tWtzeNAXyHTJWn5cpOVH2gxeumSnYps0ebKdyn1jGvx4+hrnhtF5AQAuj363sMVjajqGt5SIL9TCok3dprZimnh9YHemXHCSMlsTNXl/2iv16IaF2jcGDwomOBQo4DFdXqU+oHlEt1duzVVTYNWmluDxmBqu0qIVK70CxrY9oe/1u1/zglGmUKjJFrQ4r6guauKJ6PbKPYcR2zDr3qeFurHJ2d6JTrdrG68Vx8t6yt3f7TrWZYM4pmBo8zdtoZOz37opuE9R44sAyGwmba2NXbhi0h4TEPfSg9D1Hbd1woRi0uwZwSCwaX2xb4e/kD5VXitCkzZv0oaI1iTWhrVa1hWr1Udy/hYWoe6oAkwgwbTW2Bxcd8JKBv4WEjZYPr8yTjjM5DEljSoPBDn83UKllJeZCgIPuN15hef7yc9X1H1IVJ76dqiFk93PQMWG58z4Hr5KF3GZoIk7bog9Pmcf70/5ngAAUrDZSe++Mc5d5QJpUDApcYE73V6lT7JWNBNpwPMxDX5cW7HIfQEA0i92C4nM5Xab5fbH7Ss4sd2k+JnCkvCanIYpXLeTAU3VqrJdTbndr/haMJjCEtM9V1RhTtNKLSrxtbRwCzJMwUY6CyW8wpctcbrvSok5NjsZadWsGXYqglsAFgpkmELJkYq7DcN0LWK28cIBLQoEO5zFgeCP2xLFObfeuQ908WILgJL1cQ8go3hdWwXSzLVejXiTZkZc9+74C26gvE1vpbNVXRYLVjgw+U5Ui7/hc7tqimqxYVp91Kq1JV6FghgVGEyBvm31EPr+/F1dOdzCe6/1YWi5aQXi5a1eXmt+D7VaH+jOMJA3mJftfuqlb7yoffU2OOauz5f/t/kCFybA3jtD1WY9SfMyE5Sx45IkqEgR63zFug9JlN+570WOi5VCyx23ZUlYBYL4XXoCADCR5CUpcB+8RPAjXS4lCSTlFUyczqDo9goAspRbA9/fkqPpYa0O655CKqoMVZm8b32jtD/Q9UOR14rAFaNbjJSlsh5TQ/NZre56Mao/bq9Fga97JvcYQi0e3FqptubmSy1tqnPeDxyv/3j8BTjmFeieyyu08brBcLvUMF2F+D5napv295pB1JMXZiTi9u8eFkCJ7CokoE3dSqHWqFsQ5C8oCdUcdgt0akPdqbi1bZ1zFF6AE6ObsGGI2oav8M4ca+hcblWrWwvWfMfe8bstUczAx+a3FxHEiSpkMu8Huh4BkHHC01YzqLbDpJlhabmthe+k/62pdD04UdgWc7evX6iOYGvKxNy8xAaW/PmfEdZVU4CbNvvGzXL58jzbajDU3WCyYInD5CkPlukt5zuP16oi9Hto05aN8Wsem/G1yreF/ia4PtPlYWQeU9KpYyaPSZSXuUGZm9T9QkSwxpH4fMW/D4nO70L5VvR7zvbt95OIuT+T/+/i3hMAADCxFOQnLoam26v0uZSk5Ude3sQJCeRVz1uQcASUY/9hsZ0avl998v92//3Ur/+l++9IVP/VXjsVW0VFvLqxqSktH0XpEICc1dMd2cxgeMYrbTEP+/4uIUyBf6BwwRSi3F8vFZXYbifCCqy87iy8fq371d8r7dvsFWCYdTa0BNZjCgw2SPY9r7sIZ32mJqRb4zL+eoJMQUpUd0emq6fA5/zr8B+DLVAzNUBt7c7g9o2oAriQsGMwhRUPNqrDd26CzHvO4W2KsZ6wbblMIY9XsOeeW39/4kbEcfq/Cz/zt7ef+FrM8xsu3nlx+LcVPA/2fLkBKOf87uzUsuWmH3Nnn+3no/bJLG/YE7v2bNjx+I696V7daGrm2kCXt86VWrVhpe5f12j7/A8/V4HzaAZM7lje6NYmNvsRfi4AZDabJsVMc+KneWMhW/JoL/0LH+A8Mt922XM637xXGcrbwj4bled530cgPQ2KkeeFrSeYf8cS/p0Gxfwbs33TtVPo2KJE5ItG8HcS9p7/niB+XqYE5y7h+YqxH2Hb9L/f69zLlHTqrcBxxcsLfXlbSPz3x/P6ADAxxE6Hkqc3lXPm2qnYuo4esVMjky15NC6PeUuvVv6kSXYuWkfbIfWP8jcET/nsOSqfOdvOxTKk1g8/sNOX32jTjur6+PGLtAY/zPge11TU2zmv2yvj96f3uf8aH5zer8c++J6dS47gB4DLgZs2jJ179cR3qvXLsEKsiYjzAGBkyKOzQYzgSIIKBwCA8UHwA5dT7VXXKC8/z85FO3Fgr871pXHQ0QmsdPoMVdZEVv4I1/rh7+3U5TeWwY+0t3HJS/IfAAAT28t6altZmscZyTKmAMx0XTIGA88DADLBy2ppM2OCmPE+7OvBhdpHug8AAOLITzIgOoYhSRdjEwndXhFVBhADNVYAAMhM5NEAAIwMLT9wOSXr9uqU8/vr7Txp5zAaFbPnqCxht1e0/AAAAAAAAAAAYNSGBgftVGyTEgRGMDz5BUmK/BM2hcgttPwgqgwghoyvsWIysoICOwMAQIa4dMl5JX6wHS3yaAAAYkghD6blBy6n6sVLNWXqVDsXrfdUp061t9k5jMaMefNVXJHgehoaUmszA567RhP8MAOgG8MZ4DwSwQ8Al0PG37RNmaybm27Un913rwoLC+1CAABiGxgY0F+/9LJ+u+0du2QMmKcKM8Tf+Qvu7FjJ5jx6XL4HAMDEk2IeTPADl9Ps+itUWFRs56L1n+lWR+sBO4fRmLNoiSZPK7Jz0UwrnLaPdtq5y28sgx9j2u2VCXqMJvABAIiPwAcAIFUmvzD5xpgyhS5wxcujx+V7AABMPOTByAKXLiYOztHtVfrkT55ip2IbNC3FJgjG/ACALEXgAwAwHOQb4yfRueZ7AAAAE9HFgQE7FVv+pMl2CqOVn6QL1otJAlG5hOAHct7sOTVadv0N7mtW9Ry7FAAAAAAAAMB4uHA+WfCDlh/pkpeXuDlYb2eHncp9BD+Qs5Y2LtM/+af/TGs/c5dmz5njvtbd9Vl3WUPj1fZTQPYy/YYDAJAq8o3xk+hc8z0AAICJKGnLj/x8TaaF7KhNmTbNTsWXLBCVSwh+ZKR79cRzj+s+d3qlHv3Os3r6kZXu3Kg1Paynn/uWHm2y86Nm9vVZPRfc38xw062f0rLrr9e23/xam37wfb3+2k/dl5k2y5Zff4Oabv2k/TSQncyAqRSgAABSYfILk2+MKTPYKlzx8uhx+R4AABMPeTCyQCoF7tNKK+wURqqwuMROxXfx3Dk7lfvyquctSJhEHvsP8UdLHw/Vf7XXTsVW8f9v704AqyoPvP//su97ICGQEBIENBI3EBSrUsC11ZaptKPt1LFKF+28bd864/u+tWNH3/fvjH2dvlPtotaxY7Uz2Npqa2tFqrZV2RQFUYKEJYFAICH7vv3Pc+5zk5vkZr+Bm5vvp3Mmz3POueecu52D53ef50nNtKXxSUpJs6XxMuHEzVrc73NVro3r79cztjZ2JlBYoJIJbaPPDXc/oOyNd+r7m+2MQFp3lx5ZuFfr7x3/f8QF+vhMiw8TfPzu179UU0OjO2/O3Lnu38OHDrl/0zIytPqaj2nX229rz+5d7jzAV0NdjS2Nz8TPLSOICHem4ftwBADglDODJ3Z128rk4BoNAIAfo7gGp82abUv+1Rw9YkvjE/TXaJx2sxacqajooVt3tDc36dj+D20N4zFjboHikpJtbbCOtjYd/fADWwsOEz13ZBcMnV8QfgQk/Fgnbei7eX/hHQ/o1rQ3JxAIEH5MxA2f+7ze/NOrvUGHcc0n/0rt7W1u6w8vE4iYFiLPPPlTOwfowz/aAAAITlyjAQAYH8IPnG5pOXOUlD78vVxzY97coMc4hIVpzlmLFR42dGdPjTXVOnmk3NaCw+kNP77rPNi8XsOPkxJ45qi6nYP/5tQLP9xAYHm9HvvGD7R1QMuQ5p1P6GsPbXNKnoCjbmeGFhfHu8vKXr5d920wpf7hxw13P6yFJb7LVinPFNWsXY/b/Zp9rs515/bN97fv3P7BSr/H+bRYMd1jOU+rtKZIi93Fflqz+Hus3+Pw1NxQyD5XlW/S+nvL/b42/dZr3G1fR8/jb9QBVRUXKc99/ODAxQxubsb4MN1b+Yq2qbIJQHzd+IXbtOl3L6jyaIWdA3jwjzYAAIIT12gAAMYn1MOPv3z1oC15XPL9fFtCsIhPTlVm3vDvi/kcNlSfsDWMRWxismbmF9iaf1Xlh9Q8we9qoE1m+DHimB/RB1pOffBhOPt09z0FXTgzw/mmHnNv2N9w981K2Xy71q/3TK+n3axvrfOsJ+WqUBvssk3S6pHGzTBhxirpZbu9xw+o8BbzGGf+zL29+1j/crUWr1nrrL9N3//GE9rVaEKI223o4sMEHKuljd7HPV6vFQ9+RRfaxUosUkqJZ9ljOzO05m6zTR8b7nf2Ve4JMtxgZKjjcKy7S7cWHNBjdtlGrdL37tDg4xuw3vrNybrVZ7/xxckqMfOHaGkyMztbx48dtTUP08Lj6k+s1aVr1tg5fcy65jEAAAAAAAAAJk9bS5MtDS0uKcmWMFaJaem2NLS2Js8QAdPFiOFH0nPVUpetnIoBlLz7cPbp7ntKiNfiW8yg357p1uJqbXRvzi9VdpqUt7pv2ZpcKXOmd/Dycr3eG0g8q5LyXC3sDUb8yVVKYrlK3BYgjs0/0Nfc0GGbtuii3n30tbwYQV6y4sv39rXm2LxNpZqnZd7B0Bt36yW7r63HR/NeDH0cJhBq3r/NDYSMZ+71E8Y4Bq6nDXtVlrugNxRq3rmpf+uTUXBbfYSdig8vAAAAAAAAAH+6OjrccT2GY1ovRMUMPS4I/AsPj1B8yvADxpvuxLo6O2xtehgx/Ig63KqM7x1W9L4WtxuqSefsw+zL7NPse2rwtFxwWzSU+96gN2FF3zLv5O+m/6gsz5b/Tr7W6goTuHj3YVpjjILbQiWghj6OuWm2G6sRjHa9oRw/dkwzs2fZmsf+D/eqqdF/qmnWNY8BAAAAAAAAAsl0Zf/IwJ5UJsB0937p6ivcbtyv//Rn3HlmnFtT952CWUtDvS0NzQQgGJv41JG7jQu27q5OhRHDD8OEEOkPH3HH3zADkE/q5OzD7GvqBB/9PXPvJlUVr9NX3dYTpjVHvAqXeFt6eE56fd1eZSjb28pi+Ve0ItenVYc/pmVGo2/rEDP+x126YUAoMtpQY+v2A2r2aVWh5UtVqAPaMt6Bx4c5jmdKyhVfsLS3Sy0zfsf37uh7XbwGrqd1C5Tn2zplBGbsjo72drerK69FRYvdkMNMl67u6/qq4IwF7rqM9wEAAAAAABDazL2oR3y7e3fvq/nep3OYsWz7rTMxpueTobpuH4+FRUXKmjVLu95+Sy//7rfuvJ1vb3fr5h7XVNBcX2dLQ0tMy1BY2OkYh2HqSs6cYUtDa6ol/MCEPav7zFgX7lgc5iT3hEoLbu7tCmpFzRN24HKPwnW2i6hbilT1sndA8XLVNeZqzaCTrRnDw4wNYh/zyEWqe9x5zOYf6GkzJofdhxkUvCx3lT15b9OxGk+3XIPCBtNt1svqfdwjtyTrdTu4+LgMdxwb7tdj++fpVrvs1rQ3bQuYAcc3YD134PgxXiR2vf22ll96mdIyPOFL5bEKd1BzM23+05/ceWbZ+cuXu+sCAAAAAAAgtG196E2VJSar9+ey6xYos7xcmQv7WmbcsDBXZZsncG8sIDyhjL8fDc+Zm6897+3Srh1vq6nB08vJ4UOHlJiUrKjo6CkRgHS0tY7Y+iMqNlaJ6SPfzIeH6e4q0nT7P4z2lhZ1trfZ2vQRlj0nf0oPhpCa6r8jqNFKShm5SdDkMCeyBSpxx+xAoF106eWaPTdPm//0mnsR8GVahZhw5MihMr35p1ftXKC/hgk2BTx95xYAAEIb12gAAMYnbdZsW/Kv5ugRWxqf032N/stXD9qSxyXfz7clr6X66oPrpA136vubPb2zLCwxPzL23p8bvNyM3etq3K3H7A+G3cfV7FZmcZG08wl9bftSfc95WGlNkRa765dro73fZ1qb3KgN7g+APeUDqnIel+eutsmnVYi5T7jKM9/RbLbrp9t806WVaeVhwg8vcw9s3hln6MCHH6qxoV6Lz79AT//kUbs0OCWkpStjtvfZ+tfZ3q5j+/equ7PTzsFQZp2xSFExsbbmX+2xI6qvOmFrwWWi547sgvm2NBgtPxCSTKjx3ts73AuAuTCsuuZadzJlM88sI/gAAAAAAACYLrZpy37Z7umXKjvNdD9vuqy33dL7dge//CvKLrFj2jrTxpoiXeH2sOKRVyA97czvDSgSi5Ri13/M9IoyxDgf8cXJKnG3+YR2pXl7bTEhyUXK3PmE3Z/pUn9VXzf1w/ANPsx9rpqT1ao9edIuDV4t9fUjDrwdGR2tpPSJ/eh9OogzrX5GCD56errVWBP8n4vJQPhx2jyr+2j1Man27N6lZ578qdvd1fGjR93JlM08swwAAAAAAADTx9bj1YpPy/UEHTWeMWYP1dhAJC9Z2r/N0+XV5m3OOrZLdmfqbQFiDeoaq3G3XrLd3Jt9DKV55yZ7L9B0A+8WXHPT4m1p9BKSEvsFH4bp/eR3v/qlW54Id3wUb5f0ZgrgoO1Gd1fnqG7GJ2XMUFRMtK1hoLDwcKXNmmNrQ2s8We285l22Nr0QfiDkmQHNTXNAMzG4OQAAAAAAwDS1Ya/KchfoBhN01JS7s7ZuPyCl5brjfVQdty051q3S4ppNtiXG7droWXXS9B8zeJXUOy7w0MyYH+ZHvr49m5hA5Pp1f21rwa259qS6u4e/IR8eEcHYH8NImTnLbSEzrJ4e1Z+otJXph/ADAAAAAAAAwDRQrrrGDK1YnqHS7Tbo2LxNpWkXaYXbDZZn1oUzMzwFl+kiyxYnyYV3rFPKZk/QYqb77HGMZOCPfAvmL3ADkIna+tCdvcfiTr1jkwROR1ubmkbV+iNT0bFxtgavmPh4JWeOHAw11taoaxqPm0L4AQAAAAAAAGAa8Iz7Ee8d28NluqCKd+bV65Cds/WhDe6YHJ6WGOuk/eXKW33XqMbhGA/TVVbeap9uppzJOx6Ir8OHDmrh2We7g5r7m8wys85U0VQ7mnEowpQ2O1fhEdzG9jLdXWXMmWtrw6s/fsyWpqew7Dn5PbY8JaWmTmzgm6SUSY5uAUxJDXU+nW+OA+cWAAAmB9doAADGJ23WbFvyr+boEVsan9N9jf7LV/vf9L/k+/m2FOzW6luPLFCJ79jAy7+i762Tnv5G/7FFsmblaGFRkebM9f/cTPBRsnv3lOr2PTU7R8mZM21taC0N9TpxaL+tTW+ZefMUn5xia0Mz3V3VVh61teA10XNHdsF8WxqM8IP/+AHgBzdWAAAITlyjAQAYH8KP4HXD3QMHVW/Wrsfv1Pd7W6eErojIKGUVzFdkdIydM7TGmmqdPDLJA7AEucS0DKXPHjACvx+mq6uKve+rp7vbzglehB/DIPwAMBm4sQIAQHDiGj3FDfFL1qC07i49sjpXKt80KX2dA8CpRviBYJWYnqn0nDm2Nry648fcaTqKjU/UjHmFCgsLs3OGVlV+UM11tbYW3Ag/hkH4AWAycGMFAIDgNOWv0aO8oX7hHQ/o1uJ4WyvXRm9XGCY8uKVI3iVeZS/7Gxx1qb764M1a7B33dKSb+H627d3u4F+kOhp367GxhhjjDj/6P5fmnU/oaw/ZgWoN77EPOqYBr4Hva+nD+3r7vo7mOS8s6f+6el8H/6+36bZklfKm0a91AZwm3muJ1yjPx4QfCGYz8wsVm5hka8M7fqhUrQ0NtjY9mEHfZ847Y1Rjn0y1LsImM/xgpBgAAAAAOAXMDfZHVksbXx6hu4Z1d+nWggN6bP3tWu9Mj+3M0Jq719qFDhNi2GXr129SmZpVV2aX+bjwjnVaXONd9wl34FZ/g6f2ykuWdj7hs23fG/zmhn7f/Md2Nks1x0YZYJhQ4GF9746ltj52N9x9swr3e49tk6qKb+57LuYm4C3zVPrybuco++v/Gvh5LQ37eu8a9m0xIcrDWqNN2uh3PbN8lfSy2Q/BB4BJZMJeb4huzm3mmpJYpFsHntumIRN2+E6YWuoqj6q7q8vWhjdzbuG0GgA9MipaM/MLRvWcuzo7dPKwn38YTlOEHwAAAAAQIG7A0e8GlLnxf5ducP5eUVztt9XBIBvu1/oBv+Btrhnizvy6Bcorf9PPzfalWlYg7drobemxzVmnXHkLA3FzbOC2/fEEHo+40yplDmipsexu77IH9NXldqYNGDzznan3dVyqQxtv93n8syrpfTmcxyzP8LS08PPf+VuPV9tSHxOQXOgpOpzjNIGU83r7dqDhbeGRt9oex7pVnhBliJYzJmQx4czg1iAAEGCbf6CvmdDDez4qq/cEv2nZPuc2YOppa2lWzdHDtjayOWcWKyIq2tZCV0R0tLIKzlB4ZJSdM7zqskPq6uq0NRB+AAAAAECAbH1og3alLdANtn7hHRcpc+cmPaNndd9ogo9efeHBjdrQd+Pf3PTqvQFvb/z7DSFyldLb1ZNlbpANc3PswpkZUtoqfc8bPjz4ld51n7nXpzWDGwT4C1z6eJ63b0uNVb2vifmFckqJpyXG+scPqHCd3c+6pdIGO99tqXKRDUa2aWu/fa3Vwlxva5dt+v43hmlpYYKkkgW9gYr7WjqvnzdYuuFu01pj8PvyzL23uy08TPdW7g1Gs50hgg9zPCbYKtW63v1MpJULAIzFhUvmud0VljnXh9G1xgOCV1NtjWorj9rayGYvPEuxCQP/wRM6TFdXswoWKCJqdMFH3YlKtTY32hoMwo8JSpp3peZe94w7mTJOjZjL85X26HXuZMo4Na45q1G/vbXcnUwZAAAAA23Tlv0ZWuHe/Pa0kCjd7jM2xaiZsMQTBDxtbqr7685kFCHEUEzLBn836uPT6vW03e/GmiLdOOgmvp/AxXQ7ZbflDUzmpg0clcRH42695G0hsXmbSjVPy0zIseGYUm7xHpfvOB2+TChkWpJsGNXzdlviLNxrA5UBr6Vz3KYbqwm31lierUzlqlAb7H5M2LPOp0ULAEwS021fcbw7DhItzxAq6k9UuiHIaM2cN1+JaRm2FjrM4ObmuYVHRto5wzPjfJiuw9Af4ccEpS++VRExKe5kyjg14m8qVnhytDuZMk6Nr1xco9S4LncyZQAAAAy2dfsBqWCpLly+VIU6oC3jCCd8me0NbrFhQohclZUM1RqhXHUDf6uSl6x4O06HadngDQS8rUq2PnRnv+62nikpV3zagFHOTeAy8DmZVhF2W97HP3PvEyotuNkGGf5bVwzUv7XIE9o18PhNP/futny7wBqOCZ/i+71GWx96U2W5npY5Nyx0nlvuKnuMfd1cjavVRuNuPd2vW654peTZKgBMBhM8r3auA6M+JwJTR/XhQ2ptGv2PbtNn5yo1K8fWpr6kjEzNmGfGNYmwc4bX3tqi6rL+A/7Dg/Bjgkzo4eVbnjznadUXbtONvtNV59llHmnLb+q//AvXOf/RFVpM6OHlW548OVqyzvc1daZ1K5Vml/aTuVLX2XWuWx46J17DhB5evuXTZ+D3IfQ+6wAAYAqyrRmuWDNPVaPshsRtoWBbTbitMnxaerhdmgwcXNwNVnxaULg8XWV5BgI3LVCkxWv6xs0YPixxlj/Y/8a/CQcGjjVi5o2maxUzBkbK5r6Apd8vkhOTNdcWfcOU/q1FBnTbZW7y3ZKs1wdua1jbdKxG/cc5MWOkNNbrkFP0DYDM5O3masw3EX1br7h8u+UCgMBzrxmrnXM0LT4Qwo4f2KfO9lZbG1nyjJnKzMuf0uOAhIWHKzM3X2mz5igsLMzOHV5XR4eOH9yv7p5uOwe+wrLn5PfY8pSUmpppS+OTlOL39vWoFdzwki157H/mCluaDOZG7xJlqVl7n3tK26ukwqtu07LZ5odGL+j5zRVu8HF1UXxv3b0Rf/18JTbs0+83vKJQ+a1+xn99ypY8qj/9C1uaDCb4uFYLkqTK1x/Vpj2egMm8zjqyXU+/uMOuZ/Sta/S+DyHiL1/tnyJf8v3T2eWY97Wu1JafPO/8B6f3++GtT0xD3cS+LRM9twAAAP+myjXa3Ji6dcAA595BtPtp3K3HvvEDzTXL0jxlEyz0W9eu0xc4eLp+Mi0g+t30Mi0jbilSlc/8ftspH3rAbg8TgPh0NzVgffc5FRwYcCxDsL9I9mWChfvKnGNcN09KjHf7qDfDj7uDlbstSTzPy9Ngoly7dmZosbu/bVrme1xe9vg8r/XAbrbK7Ws/4Dn1zh/MvFYLS/q/dn1132PrY248umGJfe29R+E+V25IApgU/s9Hw53fvNJmzbYl/2qOHrGl8eG/ozEZcs8qdkOB0epoa1FD1Qk11py0c6aGmMREpc/KVVRMjJ0zsu6uLh078KE6W0cfEgWjiZ47sgvm29JghB+nKPzwhhTem+cBs+g63bgiy/mUeMKN9EH7GXiDODSMLvyYxCDCGyoNvNFu34/GhmYlJvmEUCEiIOGH9zPrmsDn0rsdnwBqUPg3AfyjDQCA4MQ1eiowN+YWqMT3JpwJB9ZJT48mOAEATIrUWbM11G/Jzc3BWsIPBKk5Z56t8IjRjX3h1dbUqIbqKjXX19o5wSkyOtoNJuOSxtajUHdnp47t/1Cd7W12ztQ1meEH3V5NOnMD/jYt03Ztmdg1xK+0VHtn3yo9WOn+zcq3XWFlLlSOWeVIecgEH6NVeNW1yil7QU//5FFn2q6momu1apFdOFGZSRo8/qLzXhd7bsb/eRo3cTcBxJCvswmNVkhb3PfkUf1+d5KWDdV92AgK8z0BSmOt5zPvKzHVG64AAADg1DNjXuRqjR1Lw51umafSDQQfAHA69XQN3X31cMuA0+3wB++prbnJ1kYnJiHR7QYrM2+eYhP73z8NBhGRkW7okbPgrDEHH6arq1AJPiYb4cdkW7REC+oHdo0UKDkqzPM0rK7cabu02vO8e6O/cvYSzxgI18+Xdr8wSfsPZjk6uf1Rn1//79DBAIZPvTfed2/vDZXSlq/UgqRm7d0+3V7r/mo2P6WD+UONv1GiP/u09KjZV6HRD181Bsmp4wpUAAAAEBgDx9NYv97btRUA4HTp6up0W3gMZOaZZUAwq9z/4bhaccQnp2hmfqEy5uQpJn5gN5mnXlxKqjtA++xFZyspY4adO3om8Di2fy/BxygRfkw2E0ZMUvDgudnu6c7J25WWZ7DzJcoyXQGZX9c/t08qunYaDgRdoZoqW3Sdp/zZzarrN2+cFl3ndi1mWnj0hSvn6Xx3DJD33bFYprvSF03LjnLlmwDOd0D+Kud9sUUjbX6OEp0L18QatwEAAAAAgJH0dHe7N0zNOAEm8DCTKZt5ZhkQ7KrKDqqpdnx3kRJS05VVsMANQpIzZyoqJtYumXwx8QluK5TcsxZrRm6+EtMy7JKxaW1u1LHSvW7LD4wOY36cojE/DDPuR/7BAI354Y53kNQ78LlH3xgXg8f88J039Y1twHPPQNgJgRh/w4710TTgtfSMNaHe9yOQY08Ek3GN+WE+q/nlg0JAz2vUMO4xPwYO9m94X/fBA9GPHX2VAgAQnIL+Gh0R7kwRtgIAwCliuq3qGj7ASEhLtyX/miY4QDT/HY1TJXlGllKzZtna+LW1NKu1oV7NdbXqaAvsoOHR8fFKzpjhdrk11vFK/GmoPqGaCY7LE6wmc8wPwo8pGH4MfWPdc5PfdMhE+GENEVaMi58Btr28N+L9soPRT+xrHBzGFn7Yz56fbt/c1yt5gq8LA54DADAtBf01OjpKFy9fpr++Ya1iYmLszNFpa2vTz595Vm9s3mLnAAAwCubOnhnJvH34X4MTfiCUJKSmKW3WHIUH6EcnJvwwrUpMaygzmLiZOjs7nLpTHmJMHLNvE2zExMUr0vl3X2S0maIUHZegsDDzpQyM6iNlE/5+BrPJDD/o9mqK6f1le8M+/XnQzd0dent3s1saNOC5KnUwRIKPUTM3x69P0u6fBDD4cF7HLX5aFHi6eeqbfm/fB3MT/ukQCT7GwoQbN35hpfRH5/Xo93qZQOQ2FdUG4HXZs117G5y/s3Ntl2626zHnPdodQq1tAADA1DOe4MMwjzGPBQBgTAJ3jxWYMkxQceJgqVobzc2hiTPdYJnWJOk5c9wuqmYWzFfOgjM158zFyis6RzkLz9JsZ8o9q1i5Tj3v7HPdZWadjNy5SpmZ7QYyMfGJAQs+WpsaVbH3g5AOPiYbLT8mveVHX2sMX+P7ZXpfK47BKnu7D+oNSLxCqOWB18gtP4Z4rcbZHdLQLTuaB3Q95hHIFgjBZDQtP8xzP7/2Kf+BU2+I5Mv/azg6A79ffd+DieIXKwAABKep0PLjkYe+Zyvjs/6Or9kSAABjQMsPTFOJ6ZlKzpzhtrwIBaalSc2xI9Mm9JjMlh+EH5MefmAyjG3MDwTKuMb8mKL4RxsAAMGJ8AMAgCEQfmAai4iMUlLmDHcw86mqp6dbjdXVqj9Rqa6uTjs39E1m+EG3VwAAAAAQQGbsjvGayGMBAACmq67ODtUeq9Cx0hJ3cPDubv/jdAQj09Kj/niljuzZ7bb4mE7Bx2Qj/JigrrY6W+pfxuTqrm+3pf5lTK7alr5BpHzLAAAA6GMGLR9PiOEd8BwAgDGZ0n26AIHV3tKimqNHVFm6V3WmBUXn8C2iTqduG9gcKdmt2uNH3RAEgUW3VxNsUpc070qlL77VLZ/c9ZgaDvzBLWNyxVyer/ibit1y81M71fZq/+6YMDmuOatRX7nY0xTtB2+k6XfvJ7rlUERzXQAAglPQX6Mjwp2JH4kAAE4xc9O0q9tW/KPbK0xH4RGRiktOVnxyquKSku3c06unu1v1J0+ovvKYenpILyez2yvCD06sAPzgH20AAAQnrtEAAIwP4Qemu6iYWMUnp7ghSHR8gp17apiurNoa6tXgfM/amhrtXBiTGX7Q7RUAAAAAAAAAIKR1tLW6XWEd2/+hjux9X9VHytzQr6s98F3qd3d1q625STWmW6sP3nOnqsNlBB+nGC0/SJUB+MEvVgAACE5cowEAGB9afgBDC4+MVFR0jCJjYnr/RkRGKSw8QhHh4c5fM5muTXvU2dHhLItUWFiYO7B6d2enO6+zvU1tzY1qaWhwu7bC6Exmyw/CD06sAPzgH20AAAQnrtEAAIwP4QeAYET4MYzTHn4woCEQfEYx0NtI+EcbAADBiWs0AADjQ/gBIBgRfgzjtIcf0VFKylujzMVfVFhknJ2JUNPT2aKqXT9W+9wSJfxNscJiCbyCUU9rl5r+Y6fa/lwmtXfYuePDP9oAAAhOXKMBABgfwg8AwWgyww8GPA8Ago/QZ95f8z4TfAQ3896Y9wgAAAAAAADA9Eb4EQAEH9ODeZ8JPoIf7xEAAAAAAAAAwg8AAAAAAAAAABBSCD8CwIwHgdBn3mczpgSCG+8RAAAAAAAAAMKPADADYROAhDbvgOdmMG1urgcv74DnAAAAAAAAAKa3sOw5+T22PCWlpmba0vgkpaTZ0jhFhDsTYwwAQaWry5m6bWV8GupqbGl8JnxuAQAAfnGNBgBgfBLS0m3Jv6aak7Y0PlyjAYzHRM8d2QXzbWkwwg9OrAD84B9tAAAEJ67RAACMD+EHgGA0meEH3V4BAAAAAAAAAICQQvgBAAAAAAAAAABCCuEHAAAAACAo3HD3w3rk7rW2NoTlX9H3HvyKLrTV4a3Vtx65SzfYGgAAAKYPxvygP0EAftBXKQAAwWnKX6PX3aVHVudK5Zu0/t5n7czBLrzjAd1aHG9r5dq4/n49Y4rmxv8tRfIu8Sp7+Xbdt8FWei3VVx+8WYsTbXWEfbqGOz7vvht367Fv/EBb7WzDhBZrnIf18rNOwJjjWCc9PcT2b7j7AWVvvFPf32xqJvxYoBLv6wcAIaP/Od7/daA/xvwAEIwY8wMAAAAApjgTaDyyWtr4crmdM4R1d+nWggN6bP3tWu9Mj+3M0Brf1hAmmLDL1q/fpDI1q67MLvNx4R3rtLjGu+4T2pW2St9aZxf6MezxmVDklnkqfXm3s7eBlio7zQQ03mNypoAHHybEeFjfu2OprQPAdNYXfJjQw5x3Rwo+AGA6IvwAAAAAgABxA4R+3TZ5u11aqyuKq/tacAxnw/2DwoPmmiECk3ULlFf+pm3l4GuplhVIuzZ6W29sc9YpV97CobqUGu74luqryzO06/E79X0/IcuY2W6rvmq6uHrETH3dUpnXry/g8AQennVWKXPnE/raQ9vsMmnZoMebm4GmBUq8Ft8yMCjxLOsX/phAZ9TdZwFA8HDDbRt8EHoAwNAIPwAAAAAgQLY+tEG70hb43My/SJk7N+kZPav7xtT1Ut+N/xu1oe+m/+Yf6Gu93VHZUKI34PCVqxRvd1deZfVqTsse4mb/cMe3Td//hrcbKX88+1rYG0YMCBn8SSxSSonn18qDWrZYntfuCduaZJOqilf1jd3h9/HmOJ/QrsZm7Xr89n5Bibtsw25lLveGHfa12zBJXXMBwKQxYXW81FguLR/DeRcApiHCDwAAAAAImG3asj9DK9xWB57WF6XbfW/Cj5YJIzw395/WOv+DgK9bpcU1/lp9jMwdWNzeMAtMV1K5kg0j1j++W5mrbWsM08rD7se3hYcZE+Ql+2vlrcerPYUB5qYNHNnExygeP8jmbSrVPC1b7pSXL1WhDmjLOF47ADitlmfLHf02MUN1GzzhsGmUl+c97wIAehF+AAAAAEAAbd1+QCpYqgsDdIPdbG9wiw3TciFXZSVDDWBerrpGW/TKS1Z8zTG3pcMz99qgwpn6t5AYD09Q09v1igkZGjOUbUIG01LF7mf9GAcdf+beJ1RacLMNTlZJL4/t8YOZrr+qVbjEeW+WzJP2b6PVB4Cpq7fLw2dV4vaMaM+7AIBehB8AAAAAEEi2hcEVa+apavPoulVyxwqx40+4rTJ8WnqYG/Xe0KKXG6z0tX7w8HSV5en6xLRAkRav8W5npLBkAszYGb6tOsyxJVbr2ARDH9OnfcrmvpAmIP3ab9irqoJ1urG4Wq9POPQBgNPADZidv7neLhbXamGu86eR1mwAMBDhBwAAAAAElCd4yMutVonPDfverqZW50q5qzxlG3j4dvFkWmVslF3uTLcWHNBjveN8GGv1rVuKBgcr3q5QrK0P3emznZu1uGbTsAHCcMfnhjOm7uw3PrFIt7rbtIGHGaD9ZWmNO8+sM0+lj0+0lYanO6u81Xabdhq5T/ttOlbjO+C5aQGTqzW9A5s/qxJneXz53gkfHwCcHt7xjZxzm3tuXKU851y38RuMYQQAA4Vlz8nvseUpKTXV95/3Y5eUkmZLANCnoa7GlsaHcwsAAJODa/R0YVqxLFCJb1dZZvyQddLTE7rBt1RfffBmt0VJQFqSAMAUkpCWbkv+NdWctKXx4RoNYDwmeu7ILphvS4PR8gMAAAAAEGRMH/beXzXbybQo2TCR4MMEKjdr8aDuwgAAABCKaPlBqgzAD36xAgBAcOIaDQDA+NDyA0AwouUHAAAAAAAAAADAKNHyg1QZgB/8YgUAgOAU9NfoiHBnirAVAABOka4uZ+q2Ff9o+QEgGE1myw/CD06sAPzgH20AAASnoL9GR0fp4uXL9Nc3rFVMTIydOTW0tbXp5888qzc2b7FzAABTgrmzF+ZM7R1udSiEHwCC0WSGH3R7BQAAAAABNBWDD8Mcszl2AMAUY4IPAMAghB8AAAAAEEBTMfjwmsrHDgAAAPgi/AAAAAAAAAAAACGF8AMAAAAAAsiMnTFVTeVjBwAAAHwRfgAAAABAAJlBw6diiOAd8BwAMMWYAc8BAIOEZc/Jn9KnyNTUTFsan6SUNFsCgD4NdTW2ND6cWwAAmBxBf42OCHemCFsBAOAU6epypm5b8S8hLd2W/GuqOWlL48N/RwMYj4meO7IL5tvSYIQfnFgB+ME/2gAACE5cowEAGB/CDwDBaDLDD7q9AgAAAAAAAAAAIYXwAwAAAAAAAAAAhBTCDwAAAAAAACDE9fQMPSbIcMsAYKoi/AAAAAAAAABCXfcww/4OtwwApijCDwAAAAAAACDE9fT0qKerq18rD1P2zCP8ABB6CD8AAAAAAACAENfSUK/m+jo119aqqeakO5mymWeWAUCoIfwAAAAAAAAAAAAhhfADAAAAAAAAAACEFMIPAAAAAAAAAAAQUgg/AAAAAAAAAABASCH8AAAAAAAAAAAAIYXwAwAAAAAAAAAAhBTCDwAAAAAAAAAAEFIIPwAAAAAAAAAAQEgh/AAAAAAAAAAAACGF8AOYqFkrtGxBpq2MRoyioyNseXwKr7pOhbbc3+i2Pdzjs865SpevuUrn5CXZeafG0McEAMA4jfka7fzjODrelsaHazQAAKMwBa/RWnSdVi2y5X64RgMYpVN+DzFHS65aqTRb6ycsXtGRtjyMqX6eIfwAJiolQ1np0bbiER6XqcS4GFtzRKcqMSnJ+cJFKDrhXJ23bL5ifU9e0UmKDjOFGMW6j/P8Ndvpv56zHd/tutvzrjN4236Pw7cemaRY3xNd3kqdE7NDr258XS1nrdSifk/LOaYk3+Oxdd/H9z5Pq199lM/JOabEpFT7evhw5/ts2+4/c9GlOmduqjvHfb4JE/sHMQAghIzpGu0pn7nsYuX6XksGXaOd621cvMIHXr8G1qfUNXqUz2moa7TzH06xA+a7zy/rPJ1/wULFeutcowEAXoG4RvdeKz3XsUm7RrvXOZ9raDBeowftxxrFNXrQ/gFMnlN4D3Fgvf/5wHlM3sU6r8h7Phri3ORzXOFxU/M8EZGYnHqPLU9JsbET+4+omNg4WwLGKXOhFsZWqORwg1sNX3CVLs13TmTZS3RuRpUONC/WqpV56onMVeGMDjWb9dPD1VRzXHVNHe5j0pZepTNbS1TRXKzLVs/QgX0zdNnacxTXGaVZ51+sWTXv62jaal29NE2dsbmaOztWtbtLFHHhNSpOiVL8WRdrQed+Nc4+r3fbbbmrtGKucxxZS3Th7Hrt07JBj+8oulYXzXCO8Xiz5zjOPEtxpW87x9Gu6qiFOjupRAdPuouUc9lane18XZLOWKDoI9WacdlK5UZGacYFK+zxrdQVSzPUFT1f5y+KUGlLkVYty1RPdL7OWZSoI4eydIn7nGKVt+w8JR0oUVX2wOcknfXxYsV2pahwXpKOHDmhHrPzyGKtWDVfEWE5On9plio/bFPBVauV09mptLx0nXzvPbWd4bzuhXHqSSvSuTlNOlDheT/Gq72t1ZbGh3MLAASBMV2jm3Sia76KznSuXc21qjlZry7nMYOv0dJ5167SrPBwJSxarqKIgzoYd9kUv0aHq9g+p5Ti5cpvel9HUkZ5jVauzl+zVMnhCTrjosXqLNmnnsXX6ZLZXepMn6u0mnd0dOYqrtEAgP4CcI2W85jL0kt0oGqWzlt9jlr2terMQF+jK7K07LplSu2KV9bcWYqv3aUTWcF2jS5RXL/9HFR9t9n7yNfoA5EXDdhfpee1HSeu0cAITtE9xJqC67S6MFrdKXmam9Gl8pIundXvfFCtuHPP1vzYbtVWN2jmxQPPTQPPM9WaecVKZRzbo6p29zACaqLnjsS0dFsajGAXCLDuD/+k9447hcYWdcU5JzAjKkJRLQe1e0+Zqo7UqqV2nw7ZfygNqb5Cu3dv19tbq5Q2P0dZBWk6seN17X33TzpY71ml6u0tOtAidTR0SvHNPtuO18KCONU3Ois1Vqs9a6Hm+3l8465fadOuKk9loB4pwjfxDY9QdJR08oMtKo8rVn5cg9zNH+9UpnN8uQuTVPaa2f6Leum1EuWenaojbv1lvdedqwLzUrjPaYu27G1QYrLz70k/x6TIaEWqRiU7SuT+e83o3K1tOyqdQoOaOuIVpVSldFXo/b07dbApWhkJmVqY36n3Xt+ivdtf0ZHUs5TleSQAAL2Gv0ZXqv14ueqaa1W2r1zD/pu+rUql723X7j/uU8T8haFxjbbP6d23q9SVMoZrtMr1zpZ9alWXc42WzC2LhNQuHdu1U6X7GxSbPpdrNABgRMF6jU7LyVdKpXN9dP479t2DnpuVQXmN9t2P87Q8RrpGp/rfH4BTZnLuIc5T/jxp75+df3+/s1PH2swKA88HVaqobFaLc36r6Bl8bhp8nqnSnt/9Snt8fsM0d+Xf6FOf/RtdfUGOnROcCD+AAMu5/ErlNR9UxeEGOf+ekhq2aNMLW3SoLksXr1yi8f5boqur918wVqaKVi5TXLWzr+Mt6r+0WW3tDTrinPj27n5dm377imoHPX6wpibnBGi7KE1Llep8/j1X8coz+vMHFVLRlVqW1qn2hgpn287233peL22uUHuHz+9DwiL61aOdE3c/np+K+nlOJXr92ZdVckxaeMXKvpsjeWt0+bwWVZSVq9YNgw/oYE2Wlq+8Svk1W/R2jfN8ezcVrxjff2wCAGBxjXaMdI22qcaor9FJy3TphUk6WXZQJ5o8j6nc16AZl1ylFfNrtW3bIa7RAIARBes1usa5Zo7UEuL0X6MH7GeWnTniNbr//gftD8Ckm5xzX4e6B564/JwPejn/WB94bvJ3nhno0Cv/oV/87D/0+7ecc08Qo9srmtRhojIX6rwzcpU5u1D5s+JU1ZSu+TPjFJuRpJSsJDUfn6Pll+crMjJRaREnVFLao7lLzlJC7R6dMLGqozVqns4pzlVqZrYyYmtVsk8qOC9XydGZml+codp3tulA9UydfcmZSkvOVfbMGNW+v1PNc89TfnyUkp19JWfE6MTeLs1yt71Lpc1nasWFc5Sae4HOzqjVrtK4AY8vUdeSz2hNzknttd1PdJ2IUO5ll6gw90zNiynT1veO2X8MxmjuZZ9QUYoUnx6vxg8368ScS7UkK0s5S85RetUelVbN0vmXnaHE9LN1dk6Tdn0YoXMuX6yM9LOU1/W+thyMUcF86cC+Y85rNk85zsl938DntDdCZ338PCV3xystoUEH9x2RG1CHZSn/7JnO9z1TySmZSmw6ppiChUoO61BUcoaia/bpQMsZWr48V2m5C5VybKt2j5SKj4DmugAQAsZ6jT50TMnzLtDC5CYdOFbrbmLwNbpJOWcu0qzkNGUVz1X4vje150jaFL9GdyhnfqZq9h1Ua7xzjY53rtHlo7xGd6Ro9rnzFBeV6qybooyIKlWlnqEFyc4xRyYpNbpaH5yYzTUaANBfAK7R6kxVwRLnepaco+zUDpWXVCs9wNfo0g+rlXzuCs1Pz1RW9gzFOutU5wfZNfr9g0r23c/+Ep0wl8oRr9H1zusaPWB/tr+uceIaDYzglNxD3Kp9TfO11JzLMvM0I6lDh987qYwB54OD9RladG6BesreUk1u/3PT4HuQDcpbe62yj72nyol9zf2azG6vwrLn5NvfYE9NqaljGSF/sKQUv+PdA6fZeVp1lbTpxR22jn6SVugji8v05zfKpcglWrWyXZs27rQLA6OhrsaWxodzCwCEqhwtuWqhSl98RRO7UoSqTJ2zerGOvPyKqhSjBddcqbbfPa9DdmkgcI0GAPjHNXp4XKOB0DT17yFO9NyRbX5tPQS6vQKCUq2OeX/NgsEadqqsq0jLLl6tZR9JVfn23XYBAACTrV01x6rlGW4Qg1WpZH+XFq5YrSUrVir5wJaA3lQBAGBoXKOHxzUaCE3cQxwOLT9IlQH4wS9WAAAITlyjAQAITlyjAYwHLT8AAAAAAAAAAABGifADAAAAAAAAAACEFMIPAAAAAAAAAAAQUqZ9+DGlBzwBMCkCcV7g3AIAQOBxjQYAIDhxjQYwHpP9vZ/24UdXZ4ctAYBHIM4LnFsAAAg8rtEAAAQnrtEAxmOyv/fTPvxob20lWQbQy5wPzHlhoji3AAAQWFyjAQAITlyjAYxHoM4dw6HlR1enWhob1NnZwQkWmMbM99+cB8z5wJwXJopzCwAAgcE1GgCA4MQ1GsB4BPrcMZyw7Dn5U/p8kpqaaUsAAAAAAAAAAGC6yC6Yb0uDTfuWHwAAAAAAAAAAILQQfgAAAAAAAAAAgJBC+AEAAAAAAAAAAEIK4QcAAAAAAAAAAAgphB8AAAAAAAAAACCkEH4AAAAAAAAAAICQQvgBAAAAAAAAAABCCuEHAAAAAAAAAAAIKYQfAAAAAAAAAAAgpBB+AAAAAAAAAACAkEL4AQAAAAAAAAAAQgrhBwAAAAAAAAAACCmEHwAAAAAAAAAAIKQQfgAAAAAAAAAAgJBC+AEAAAAAAAAAAEJKWPac/B5bnpJSUzNtafwiIiIVHRuriMgohdl5AEKLOdF1dXaovbVVXV2dnpkTxLkDCG2Tcd4AAAAAAACBk10w35YGm/YtP8zNy7jEJEVy8xIIaeb7bb7n5vtuvvcTxbkDCH2BPm8AAAAAAIBTZ9qHH+ZX29y4BKYP83033/uJ4twBTB+BOm8AAAAAAIBTh5YfkVG2BGC6CMT3nnMHML3wnQcAAAAAYGqZ9uEHv9wGpp9AfO85dwDTC995AAAAAACmlmkffgAAAAAAAAAAgNBC+AEAAAAAAAAAAEIK4QcAAAAAAAAAAAgphB8AAAAAAAAAACCkEH4AAAAAAAAAAICQQvgBAAAAAAAAAABCCuEHAAAAAAAAAAAIKYQfAAAAAAAAAAAgpBB+AAAAAAAAAACAkEL4AQAAAAAAAAAAQgrhBwAAAAAAAAAACCmEHwAAAAAAAAAAIKQQfgAAAAAAAAAAgJBC+AEAAAAAAAAAAEIK4QcAAAAAAAAAAAgphB8AAAAAAAAAACCkhGXPye+x5SkpNTXTlsYnKSXNlsZrqb764M1K2Xy77ttgZ7nW6luPrJJeHjj/1Lrwjgd0a9qbWn/vs3bOKbTuLj2yvF6PfeMH2mpnAcGioa7GlsZnYucOz3ljcaKtDtC88wl97aFtthYIQ52nPG64+2GtybUVr/JNATpvmHPhRap7/E59f7Od1c9Iy0+F4V8fwGui5w0AAAAAABBY2QXzbWkwWn4AwCm3Td//xu1av95MT2hXo1T2srd++yiCD3Oz/gF9dbmtBoAJXLz7d48pbZUeuXutXQoAAAAAAABMLYQfAIABTDizSWW5FwU0YAEAAAAAAABOFbq9OmXdXnnqee4yx4AuZdzuqYrjba1cG9ffr2fcst3+/t3KLC5SfONuTzdSy7+i793i1N11hu4mx9vt1Uat6u3Wxruuu6zgQP9uqdztzlPpwC5obBdWu2qKtNjZjvmVur/n1e84/HR7NfTzBE6t09vtla/hzyGDv1sDziXec4LdTm9XWr3zjaH24WG6vVpRM/gc0n/+cNsf+N1u1q7ec4g53otUt7Nai4vtSajfY+3ylw+ocLX3nDbw3DDMecYx9L6958AD2pXmnLsS+5b5dvXVvHOTSgtW9b0+ozy/Yvqh2ysAAAAAAIIL3V4FgRvuXqXMnX3dypgw4lvrPMt6QwhvlzMvS2se/Iou9Cx25RVIT5tl3puc64pU1dtNzhMqLVg39C+0c1dpYYl33U2qKr7Z3ffWhzZol+Zpmc/jLlwyT9q5wX/f+4lFSrHb8Q0+TMDj2fbtej3tZn3vjqXu6gON5nkCMIb7bj2r+9yussyNfGeZDRFuuPtmFe73OcfUFOnWAHZbNez2l39FNxZXa6Ndtv7xAypc5/vdjtfitL32sc6xq0g39jtPOMud85B7jnOmx3Zm+JwbRjjPrLtrwHmlWotvuUs3eJZ65DrntQ1muSf4MOeiNWm7ex/ztC7yGX9ljOdXAAAAAAAABCXCj1MoPq1vROFn7vUGCEu1rEDataHvF9TasMm9OXiFDUeMss0+y63Mmd6bh6aLmgEtNXyVb/L5tfezemlns/IWmpuW27Rlv1S4xLudtbqiWCrdPsQvnBt36yXfX42vW6C8AfOe2bhbKl7V/8aja3TPE4BjTN8tD3NO8W2d8ExJuZSWHbBwceTtZyjbGxA456uv+bQKcVtjbPS2dDPnneZ+50N3uc+5YetDb6os0QazI70WG+7vDYBcG/aqzPdYjPI3fc6P5lwU3++c6gbBjbZijfr8CgAAAAAAgKBE+DFh23RsyF4wmlVX5ik9c68dQPiRhz1T76+ac5WSGK/Ft9j57uTTtYxfnv74TQuO3seM9Rfe9qbl1u0H+m4iujcZD2jLKG/yXTgzQ6o55nODczjjeZ7A9DS275Zlumrq/W4502rfcCEAhtu+CTvcFhd9y70t28YvXil5o3ktTHdcPsfl2yWYX+Zc1HduHiwA51cAAAAAAACcdoQfAdL3K2Frebb6j0Zibqh5u1HxdBnj6falXHXe7mvsMu/kr2/+PqbrG++6nmBlTDcbvTcTN29TaWOuFjqPvWFhrpr3bxv1Ddetx6vH8Mvy8T5PYPoZ23fLGNhVkzO9XG6XjddaLcxtti3BRrF90wLDu+zx3cpcPaDrqTHzBBQjvRYX3rFOi2s29e17/SYNmWu4zLnIE6wMbYLnVwAAAAAAAJx2hB8BYLp/iS/u3yf8DWvM4OTeVhSmz/qHhxgLw9P11OI1Pr8sdn9hPcyNQ/sL7FHfjMtd4LMt07VVvMpK+ndBk7fwLi3MLdfrYxnU13Qvk9i/2yrzvLVzk59BzMfxPIHpakzfLf9MmDl+pjXFKuX16y6qP9/tmzE0HpnQdznep/s9s72L+lqhjfW1MC3YbNE/e85zzj+9gcrypSr0tkIb6/kVAAAAAAAAQSkse05+jy1PSamp/dtXjFVSSpotTYw7mHdxvK05GnfrsX593nsG7e29KVe+Sevv9QYQAx9vWkhs0BZt09bN5ibkzUrZPKCFhLlBd0uReh+x84l+/fF7udtNq1ZZbm7vvgeva49twDH1s+4uPbK8fsBzMvo/r37b9h6jz2sx9PO0s4BTpKFuyP7qRiVQ5w5P0ODnOz7cd8txw90Pa43JH7zf2wHnhLKXN0mrVynTPs67ftnLg1tb9W7L18DzwQjbH/zd9o6TYZ7HRarrrXvPS2/a7XuWq1zKy/U+vlwb19/vE24M91p4Xr/eLvSc496oVZ4BzZ3zjvrtq0+/59xYrjI5Fe97MMrzK6afiZ43AAAAAABAYGUXzLelwQg/AnYDc2ozNwIXltAFFaaP4Ak/AEwVhB8AAAAAAASX4cIPur2C+yvnFbnlKiH4AAAAAAAAAACEAMKP6c50Z3XL2MYSAAAAAAAAAAAgmNHtFV3XANMS3V4BGCu6vQIAAAAAILjQ7RUAAAAAAAAAAJg2CD8AAAAAAAAAAEBIIfwAAAAAAAAAAAAhhfADAAAAAAAAAACEFMKPCTtPq75wm270na46zy6bKGfb61ZqpGGVC6+6TasW2crpkLlS15nnPcKxpi2/6fQe55iN7vUfjTE/90XXBfBzZD4jN2lJpq0gOLjfmwHvSwDf91Pynofsd39op/a7lKMl67z7G8X5yLwfQ3x+OAcAAAAAAIDphvAjICq15SeP6ml3ekF7k8+aXjeZMpOUeGS7nt7wimrsLA9z4+46FdratDLMTchR2fO8nn5xh60gdMVrwUcDE7CdFkN99yf6+Q8mp/W5VGj7hqe0vcpWpxgCFwAAAAAAcDpFJCan3mPLU1JsbLwtjU9MbJwtjdcsFZyfotodJfbmX5JyiuZJB3apotlz86cwc54uv3KJYsqdefErdd1fr9EF51+gxefPUbN9nPll9NorlzvznPlZPdq175hn20WxOrr7oFrNDTjncXNa3taBKrPd23T1ZZ5tRDUkSrWe+X63Y35NviTebtO0VFmlDHMszvGZ9a+YU62S1iW67tJ5Sjp/jS5f7nsMvsxjP66L3GM/s+/5rMlTdHKOFp+RosPmWN11TfBxrRYkJWqOXbcmvVizYjO1+ErP859pn4vR93wu0LyYYyo53OBZYJnl50d61ze/gD5Tte6+fI7JZ//+X8/B70dLcd96vsfj4Xn943Od18Qcm3f7/V5Ph3lvPjZPx73P3dSvn69En9dEc8b43M02Lk1TibMP81xWzPE81vv+e3hehwxzfM5n0OwnbojnnT6/WHEVnvfc3bafz+B0097m+aSO14TPHfHztHD2Se0oy9GZibs872vmQi1OrXfftyE/8+5no++7aj4zx+dcZ9/3vvfTfc+dfVx+5aXOfPt9Ne+/Y+jPnN1u7oDvv7/PjJnn77tv5k/08+/Ld99DfAfd73WteX6DvxOzR3kO7js3+LxeZt0BzyXOfpfMucM9d3qPd+B5wTH4PBTunBc/ozO7vM/fnCevU05l/3PRoHOW+9x8rgfu4z7jOVc76/e+nuYzlRPr9zw+1nOA7/vi3f6Qn0k/5+HZzrrLZkcpY1H/xw9+r33eM7PMHHPkhbrx+sGf22Aw0fMGAAAAAAAIrMS0dFsajJYfAZGlZb3dXl2rlJ2+v9SN14LUcj39EzMvR0s+mqTd3lYizzWoyP1F8Xkq1Cu25cij2qKBLUfO06rrPY/btMepLrpOy7Tdrl8uzfasZW5ofSSvQr/v3c4ST1cze8pVmZzq+XV5ZqrUIKXY7aenNmj35gpPZXaO9Efz2O2qnD249UrhVUuk1z3bfvq5CuWYX6xXvaLnX6+UBv362/xi2dmO2yqm7/XIypP+bB7vPCar2P7i3Xk+RbUv2OfzqHanrhz1r4XTlp/Vd0w7k/SR5c5zMK9D6vu92/t97Vk+Xe74vh/n6fze1+sF1RX7aaWSNF8pB+12ynJ0tXm/9mzv17qncMl8Ne30ee5DvCYTee7ex7rvvy/v8Zn9DPX+9zPUZxCny8nN70srxthCyue72lR0rT5izx+/352kIvMdcHk/6+Z9dr6v19t9DPeZ8263X6sj85nJUcVznvWffl1aZj4zQ333A/r5779v8x10v+PD8f1OuDNGcw72Xc+8jtKCJcM8R0eN8741FS2x75uz3eIk7d3u+7r5O68758WdzvPPt/vMXKic+vfdc9Hw14ABFi2077+Ztkve19Po/Wx4zgHXDXq9RnEO6HeNcbY/wufT33m49MUXtLehWXud927wdesFVeT5vNe951nTcnKJbsy3n9vXGzzvAwAAAAAAwDgQfgSEb7dXj+pgvu8YHM19N8TMja4kn6DE/UWxCSV2qCb12t4xQ5Z5wwxXks5fZ0KH51Vq5xTmZ6nyoPcm2w4dPOIppc3Pkcr6fsFbetB7k81Zpz5HhZlmnSTV7axQgjv/POUnN+iku7bjiLkJZwp92+zjrDu7Uge9N9+rSlQhzzbHotIbEphAxp3jeT6JRb7PP743nBlJTW2DslaYx12nwj3P6/nNFe7rkDh7Se/2ri6KV0Kq9wagz/vhHEGd5utqZ53rlkvbN/S9xr0a9ult+5xr9lWo0X2/zA3MBuWY19t9DfvWGc5EnnvvYwfyOb6h338fQ34Gcfrs0Nv9QotR6P2uOp/hhmZVOJ9Nw3wf+vh81s33tSFJac5na9jPXO92fZjPjPOZL/XON5/f2bnD3gz3Z1yf/wH7rtn8lPsdH5bPd8JjNOdgo28997vulobjed/yzbnebNcNMTxLPIY4r/uE0eY72+Sey4e7Bvixp1Yp19vn8IUlyrKzXT7voTkHJKb2Wzqqc8DAa8ymn/g5N/rwdx4eqP82nfe0TPYc6uh9zypUU+98VrzrVTWM4n0AAAAAAADwj/BjEpysbfa52T5Aw77eX+a7k/3FflFy3/wtvsFDUpLq/jjyL29HcrLWtPbIUWGeVLOnRBXJuSo0rUB8bpafTpXeXw3baVALh6GYsTHMY8yv0b9gQgzP6964u+/X5Gbyf8PUtE4xy82vkM2NxzG8xnvK1ZS3UGmLcpUwwddw3M99vPx9Bu0inB41m19xPoPeVgST75R/5oYxlT//JiQxQXJfiOFjyPO6N4w25+MGT6A83DXAD9PSIqH3HGda2I1RoM8BQ5yHAQAAAAAATifCj0mQnhqvplo/N9sHtpZYdJ1uHNTlUI7Skm3RaDC/et6hTd6uZhz9f9Hft765ESdzQ95T7fdLW3dZ6kKl1Jer1P7qNn9JjuTvOP0yrUGyPL9yNgb+GnwCBj6fJetuGtTlS79AKTNVCZ6S2x++u665+Wa6p3EMeh2u8m2J4yNzpR3I2IQgposWz+x+kpLk7TXObVFSX2tvEnpuYH6kOKn3V/fjMZrnPlrDvf+9RvUZxKlnWhNJRc7nyWuoz/zo+bSicH/t36Aa5/s65s/coM9MrrKOmPPIxI14LH4+r+6NddMioLe1woBz5nAC/fk320s+Sx/xhhhD6n+MpQdNyzHv+XigMTwfY+Bnw6cVhzkHNNYOiEZG8RoMfl88wfBYzsMDDdymCeIncu4EAAAAAAAYySkPP7KyBnTBERJ8x/y4ze3X3P+vlyu0/Y8VyvF2V7JC2mL61q96RbvrPd0v3fiFlVJZgxaY8TTso1zuOBNLPDep9jzv9uXu2d9K2VtR7nb+bMal8HccVbVS0XzJG4Y41azZI92w66/0RdMCxR779Tmq+ONIvxY2XfKY18beGBtKv+dzrXLKXhnU9Y6nf33bLcxHvc84Rye3v9/X/Yvzerrjlwz3OvRTot21Z/nsd7ufbq+SVGS3c7UZT8NnLARzAzPReU/9BkDm5qzpemvdgPdxoFE891Eb9nnHa4E75sMQn0Gcfs5nYXd9vK0M9Zkfi2YlFNv32Xxfn7NdF435MzeOz0zAPv8D9l3coD/b77jvOXP0r04gnov3u2R4guREfyHGcOd103LM53w87Lq+kpx1nGtA/8+GcxTOY/vGQsnRR9zteM4Bfa3exnAOGPC+pOz0fHbGdB529lPjfJ4XOPM9Y08F8FwHAAAAAAAwCmHZc/J7bHnSFBQW6otf+pKKi89x601Njfq/3/2u3nzjDbc+Eampw91VH1lSyrC35oAhpS2/yR1oesQxCBCUGuom1tkX5w4YpmVZ/sHT23UYTp2JnjcAAAAAAEBgZRfMt6XBJr3lhwk+/uWBB5RfOE+vvPOKXtr5kupa6vTtf7xHn1i71q4FTC0m+Li6qMH+whnAdGSCj2XJAwdYBwAAAAAAQDCY9JYf//2bd2r1mjV6seIPSpmV4s5ra2rTgtb5SolL0acmGIDQ8gPAeNDyA8BY0fIDAAAAAIDgMlzLj4CEH5/93Oe0uLjY1vozXV3tOLRDrXltdo5Hzd6TumbhNfqHO+/Uzp3v2rljR/gBYDwIPwCMFeEHAAAAAADBZVK7vfr2Pffops9+ToWF8xXm539GfGScqvaf6J2Mrshu9y8AAAAAAAAAAEAgTajlhxnP4+Ef/FBvvvmG/umee+zcPt7lvt6rek8NGY2qq6jVVbOv0lM/e1I/e/JJu3TsaPkBYDxo+QFgrGj5AQAAAABAcJm0lh+JCYnu36yZWW73VgOZ7rCM3737gn6383d68YMX3eDDSMlJVUlFiT7xyU8qwW4HAKYrM3j2qkW2MgaFV92kJX4yYDMo/3i2N1ZmP9ctz7G14eRoyTr/xzrYeVr1Bd91Tb3/6zPe18u7rRt9ptEdPwAAAAAAAKaSCXd7ZZgWHv/8wAP64he/1C/IMOFIWXWZ0oozlLY4XSmLUu0Sj0NVh9z1m5o8gQgAIFRVaPuGp7S9ylaHtUMHj8QrxRt+ZDrXjiOVSsg/z87IUVpypQ7usdUxq9SWnzyqp93pBVXkrRxlKAMAAAAAAICpIiIxOXVwf1WjlJWVrTVXXKEHv/tdJSYm6rLLL9e1H7tW5eXlOuxMCUmJWvWRVeo52a3Oo+2KqonsnZKbkrVswTLt2rlTGze+ZLc4drGx8bY0PjGxcbYEYDppb2u1pfEJxLnDtF64+rILtPj8OYpqSJRq39aBKtNC4jO6fLmZf4Fmtph5Zm3f+XPUvKNEpgOe9PnFioufp8uvvNSZf6ZiynepolmKm1OsWbGZWnzlGl3Qbzue1hprr1zubn9xVo927TvmzB1qv+dp1bozlZG7Rpefn6LDuw8qrvfxZyq5Q4puPaiSww39tuu7Py/TSqWw1hyfzzbN8z/Ds13fd6Qmco4WpzZ4tlu8QrOOlaojL1W1Zr3MJVoyr10lprzoOt14vXnu/bdjjmXFHM/zn9PvWGapwHketfb1k5KUUzRPOuB53fy/NkPPd59Tpvf1N+9LvM7/wsd1kbPevJhj7vEjdEz0vAEAAAAAAAIrMS3dlgYLSMuPyspK/f2d39Q/fcfkKGH69j/e47YESYhPcJfnZeQpoimi39Re06Zf/+pX+o6fsUIAIOQtuk7LtN22PiiXZnvnL5T+6G2VsF0qXil3dJFFS5RT9oJn/nMNKrrK2woiXgtSy935v98tLVjinS9l5Ul/9m5nhe1GKnOlPpJXod+78x/VFi3xdB811H6NpPlKOejM3/CKavo9/hXVJXsD6PN0fu/8F1RXfJ0K7RK/vNt01t2r+Tp/YBdWVQ3OxWOhewzpqVJd1Q4drM9RofsckqQyE17kaIl97u7zL8vptx3v89802hYiQ702Q813+b7+SVr2hVwddNfbrqaiJcO/BgAAAAAAAJg0AQk/vN584w19/nOfc0MNMwaIGfNj//79uv0rX3bDEe9kBjj/0Y9+qB87E11eAZiOCvOzVHlwh62Zbp5scU+tUq73jkexRFl2thsGFF3rzLtJS/SKnn/R+9hm7d3uKdfsq5DvGbVy5yu2dUNfN1Jp83NscOBRerBSWaY7qaH2azTs09s2QOj/+AqVljW7JdOVVJ3m62p3DA1p+4bnVWqX+NW7Td9t+KgqcZYkKd10cWXWqZJO1srzHFKT1FRb4azkzNdZ9phv09VF/VsC9j3/gbK0zD7mxi9cq5yyV9zuuIZ6bYZ8zVw+r3+t8x4dKbfP23k9aPQBAAAAAABw2gQ0/DBMmGFCjX+48063O6zbv/wl7S/tfwvMtAr50pe+bGsAAK+05WcpYbdt4fGT7aq081X1ip43856rUI4JKXpbfgTGkPsdNTOmh3msGUPDhDQjtPwYUYVq6pOUtmihclTrBg8m3FHqeSrMa7DjfZyn84saesfv+P1uPyGKX94xP8zzrNTuzSZIAQAAAAAAQCgJePjhtXPnuxMaywMAQln/1gNmAG9b9JWZKk/ngSacuMnT1ZIJQZ7b16+Fx1ASUnNs6Tzlz25WXZUNEGx3Ukb/FiiWz34HGvj49FTb2iJzpa5zAxkTgrygvQFo9eC29MhPUpP3+KqcGXlnOa9Wg0565vTTeyyjtkObXpeW2SBpqNdmVK8ZAAAAAAAAgsqkhR8AgGHsed4dO8LT9dJKeWOKms3vq8nt3sqZ/1Gpon6+PrLcWbrvfdUV266ars9Rhe1qaVh5K+32l0ivP+V27WTCkz+X5bjdU5llZtwRMybGkPsdaMDji5K9rS1KtLvW2wWV6Upq+/DdXo2CCR0SZsu28jDMuB/xPl1Q7dDb7jgbnmPJr92nhBVjbHGyZ7v2Ji/Rdea5DvHaDDkfAAAAAAAAQSsse05+jy2PmRnXw3Rh9eMf/0i/fvZZO3d4CQmJ+oWzrhkXxHSPNVGpqWb02/FLSvH+lhfAdNJQ5380iNHi3AFMPxM9bwAAAAAAgMDKLphvS4NNKPwwQcZPn3zSKfW4g5iXDhjbY6BEZ30zCHpBYaH+6Tv3uAOkTxThB4DxIPwAMFaEHwAAAAAABJdJCz8M0/rj2/fco4SEoXqIHyxQrT4Mwg8A40H4AWCsCD8AAAAAAAgukxp+GKYFSGHh6HpZr6w85kyVtjZxhB8AxoPwA8BYEX4AAAAAABBcJj38OJ0IPwCMB+EHgLEi/AAAAAAAILgMF36E278AAAAAAAAAAAAhgfADAAAAAAAAAACEFMIPAAAAAAAAAAAQUgg/AAAAAAAAAABASCH8AAAAAAAAAAAAIYXwAwAAAAAAAAAAhBTCDwAAAAAAAAAAEFIIPwAAAAAAAAAAQEgh/AAAAAAAAAAAACGF8AMAAAAAAAAAAIQUwg8AAAAAAAAAABBSCD8AAAAAAAAAAEBIIfwAAAAAAAAAAAAhhfADAAAAAAAAAACEFMIPAAAAAAAAAAAQUgg/AAAAAAAAAABASCH8AAAAAAAAAAAAIYXwAwAAAAAAAAAAhJSw7Dn5PbYcEFlZWfrEJz6p4nPOUUFhoTtvf2mpdr77rn7961+psrLSnRcoqamZtjQ+SSlptgRgOmmoq7Gl8eHcAUw/Ez1vAAAAAACAwMoumG9LgwU0/Pjil76sT3zyk255//79qqw85pazsrJVUFDgln/97LP68Y9/5JYDgfADwHgQfgAYK8IPAAAAAACCyykJPx7+4Y/cgOPNN9/Qj3/4w0EtPEyLkC9++cu66KKL3ZYgt3/ly3bJxBB+ABgPwg8AY0X4AQAAAABAcBku/IhITE69x5bHzbT4uOjii/XUz57U9//t39TU1GSX9DHzXnv1VYWFSZddfrkSExP11vbtdun4xcbG29L4xMTG2RKA6aS9rdWWxodzBzD9TPS8AQAAAAAAAisxLd2WBpvwgOfuGB+f/KTb4uNnTz5p50pr1lyhXzz7rDuZspdZx6xrHmMeCwAAAAAAAAAAEEgTDj8+8cm17l/T1ZWXGej8G9/8ppoam9zJlL2Dnxvedb2PBQAAAAAAAAAACJQJhx/F55xjBzfvP8bHg9/9rr7y5S/rRz/yBB2+rTzMuuYxxcXFdg4AAAAAAAAAAEBgTDj8MIOcV1YeszUPM6D5xo0v6eKLL9a3//Eed7yPne/utEs9zGN8W4MAAAAAAAAAAAAEwoTDj+F8Yu1aHa+s1O1f/pKamhrtXAAAAAAAAAAAgMkz4fDDdF+VNdP/wOVvvvG6O8D5wC6xjKysbPexAAAAAAAAAAAAgTTh8GPnu++63Vf5junhVVx8jlZfscbW+ph1TXdZ5rEAAAAAAAAAAACBNOHw49e/etb9+8Uvfdn968uEIoWF822tzxe/7FnX+1gAAAAAAAAAAIBAiUhMTr3HlsfFDGaemJioNVdc4dZ37ewb2Py1V1/V7174rbuO12c/9zldc+3H9Otf/UqvvfaqnTt+sbHxtjQ+MbFxtgRgOmlva7Wl8eHcAUw/Ez1vAAAAAACAwEpMS7elwSYcfhhvbd+ui1es0GWXX67CwkLt2fOBG3h4J8N0dfXfv3mnG3yYsT7+6TsT3q2L8APAeBB+ABgrwg8AAAAAAILLcOFHWPac/B5bnjDT9dUnPvlJt7y/tLR3oHN3jI/CQrdsWnz8+Ec/dMuBkJqaaUvjk5SSZksAppOGuhpbGh/OHcD0M9HzBgAAAAAACKzsgsHDbngFNPwwTNDxiU+uVfE557iDmhumpYcZ3NyM8eENRAKF8APAeBB+ABgrwg8AAAAAAILLKQ0/TjXCDwDjQfgBYKwIPwAAAAAACC7DhR/h9i8AAAAAAAAAAEBIIPwAAAAAAAAAAAAhhfADAAAAAAAAAACEFMIPAAAAAAAAAAAw5XR1dtrSYIQfAAAAAAAAAABgyunq6LClwQg/AAAAAAAAAADAlNPd3aX2lpZ+LUBM2cwj/AAAAAAAAAAAAFOSCUA62lrV2tToTqZs5hF+AAAAAAAAAACAkEL4AQAAAAAAAAAAQgrhBwAAAAAAAAAACCmEHwAAAAAAAAAAIKQQfgAAAAAAAAAAgJBC+AEAAAAAAAAAAEIK4QcAAAAAAAAAAAgpYdlz8ntseUpKTc20pfFJSkmzJeDUSkvMtlOW0pO8Zc8UHRmnzq4OdXS1OX/b1dntTJ199cqagzpWe0An6spUVX9Yre1NdqsYrYa6GlsaH84dwPQz0fMGAAAAAAAIrNSsbFsabFLCj4SERD38wx8qKyvLrVdWVur2L39ZTU2Nbj2QCD8wVcxKn6+C7HNVOOsc928gVdUdVmXdQfdvVV25DlfvVU3jMbsU/hB+ABgrwg8AAAAAAILLKQk/TODx2c9+VglJiW7oUVx8jl7euNFdtnrNGu3c+a4bgjQ1NOpnP/tZwIIQwg8Eq9TEmcqfuVj5Wc7k/DX1U6Wzu0N7yjc705vuX1NHf4QfAMaK8AMAAAAAgOAy6eGHCT7+5YEHVFBY6Nabmpq08aWX9OMf/dCtf/FLX9aaK65w1ktw6/tLS/X3d94ZkACE8APBpmDWeTpn3kp3CgamBYgbhBzerEPHd9u5IPwAMFaEHwAAAAAABJdJDz9MuPGJT35SD373u3rjjTfceQODDROQGBdffLG+8c1v6te/+lVvODIRhB8IFt7Aw4QfwcqEHyYEeXf/K2puq7NzpyfCDwBjRfgBAAAAAEBwGS78CLd/JyQx0RNsmODDhB7e4MN0fbW4uNgte+d7wxHvY4CpLCEuTRefuVZfvvbf9MmLvxHUwYcxd2aRrjz/C/rClf+iC+ZfaecCAAAAAAAAQGiZcPhhWnTMzJop09WVlzvg+Q9+qH9+4AH9ywPfdcvelh+GWbegoKDfPGCqOWfeR3XbFd/VFef/rbJS59m5U0NGUo4+vuwOfXbldwI++DoAAAAAAAAAnG4Ricmp99jyuDz2k5+4Y3387oUX9OabnlYd3/u3f1NWdrY7b8+ePbro4ou16MxF7gDoHR3tSktLd+dddvnlbvdXExEbG29L4xMTG2dLwOgkxaXpygtu1UfP+axioz3j2ExUR2ebahordbRmvw5Wvud2TVVR/aGOOfXjtWWqbqhQbVOl6purFeb8L1D7TU+apXMKPqqE2BRV1R9Wa/vEx+GZKtrbWm1pfDh3ANPPRM8bAAAAAABMCz3DjLQRFmYLgRE7TA9TEx7z4/d/eMkNNf7vdx9w66arK9Pi46mfPamfPfmkO887Jsin1q7t7RLrv3/zTq1es0ZXX3mFWx8vxvzAqVSUd4lWFt+kzJQ5ds7YtXe26lDlLpUc3qojJz9UXdMJNbfV26WjExMdrxnJeZqZkuscS65mpOQ5U65SE2baNcausbVGb7z/K73xwcQCyamCMT8AjBVjfgAAAAAA4EdPj0zIEBERoYioKIVHRSsiMlLhTj0sPNzNQnq6O9Xd2aWujg5naleXU3ZmTjgMmfQxP4BQFxudqKuXfFE3fOQfxhV8VNYc0JaS3+g/X/vf+v82fFpPvfpP2r7vRR09WTrm4MNoa2/W4ao9ert0o156+3E99co9+t6vv6DHX/oHN7w42XDUrjl6ibFpuuL8W3TTynsmFKIAAAAAAAAACHEm8HAmE27EJCYpeWaWEjIyFZucoui4ODcEMcsMk2+ER0QqMibGWTdR8WnpSpoxQwnpGYqIjna25dleoE2426srrrjCHdTcDGD+1vbtqqys1MUrVmjJkiVKS01z/35i7Vrt2rlTv3vht+5jTEuQa669Vvv37++dN150e4XJNit9vj5z6f/UojnL7JzR23XoT9q443E3oNhX8ZbbtZTn2zw56ppPqPToDm3b+ztV1h5QV3eX0pKyFBEeZdcYmRkP5IycJaqsO6i6puN2buih2ysAY0W3VwAAAACAac/byiMySvEpqYpNTHKDjvEw4UhUbKxi4s09/h51dXZ4bp2OoTXIpHZ7ZQYt/8d77lFBYYE+/7m/cbu18s4zoYhhgo/vOHXvsp8++R/aX7q/d95E0O0VJtO8rHP0qUvudMfEGK32rla9u/+Pemf/Jh2p2mvnnj7J8RlaNOciXbjwY8pMnm3njqynp1u/evP/aeeBP9o5oYVurwCMFd1eAQAAAACmM29LDxN4mNAi0Hq6u9VSX6fO9naFjTIAGa7bqwmHH4Z3/A7fMT2MrKws969pDeJlwo9fPPtsv3FCJoLwA5PlzNyL9elL/4etjay1o1HbSl7Qjv1/1MmGCjs3eMTHJGvFWX/lTGvtnNF55d2f6bX3/svWQgfhB4CxIvwAAAAAAExLtrVHZHS04lMn/55YR2uLWurr5cYfI4Qgkz7mR2OjJ/C4+OKL3XDDTIYJPbzBh3e+WcfwPgYIRufPv2JMwceew5v1xMb/qU3v/iwogw/DjC2ycce/6/GX/t453i127shWnvNZXb/872wNAAAAAAAAwLRhgw8zjsepCD6MqFi7LxN8TGAskIC0/DChxr9897sqKChw66b1x8Y/vKQf//hHbt2M8bHmijW9oYgZ6+Pvv/nNCXd5ZdDyA4F27dIva+mCa2xteKa1x6s7f67Ne563c6aO8wtX6+KzPjXqrrD2H3tH/7Hpblub+mj5AWCsaPkBAAAAAJhWbPARk5DgTEOPrTFZujo61FRbozATgAzRAmTSu70yTLDx2c99zh343HR3Zcb72LjxJXfZmjVXuON+mFYgpsXHz558MiDBh0H4gUD6m1X3qiD7XFsbnmnt8erOp3Ws5oCdM/WYrrCuOP8WnVuwys4Z3uaS5/Xi9kdtbWoj/AAwVoQfAAAAAIBpw6fFR2xSsmfeaWDG/2g2AYip+AlATkn44WtgS5BAtvQYiPADgWK6djqvcI2tDe/ld/5Df9n9jK1NfavP+7wuOetTtja8X77+Xe06+JqtTV2EHwDGivADAAAAADAt2ODjVI3xMZKOlma1NDT4DUBOefhxKhF+IBAuO/vT7tgWo/HsGw9q54FXbC10LFvwMV299Iu2NryfbvpfOnBsp61NTYQfAMaK8AMAAAAAMB309PQoLCxcSTNm2DmnX0t9nTpaW53jGn34EZABz4GprHjeR0cdfJhBzUMx+DC27P2tHv7t7WrraLZzhvb5Vf9bWanzbA0AAAAAAABAKDDBh/P/FZuU5JkRJGITPcfjOb7RIfzAtDY362x98qL/ZmvD+7fn1+vg8V22FppO1JXp/9vwaR2pKrFzhrZ2xdeVFJduawAAAAAAAACmNBssRERGKSo21i0Hi7Dw8L5B10cZgBB+YNpKTZip6y68w23CNZL7fr5WJxuO2lroe/QP39SJunJb88+0/Lhu+d8pIjzSzgEAAAAAAAAwVbmRQo9p9RGYAc4vOHexPvNX12tWdpadMzEx8fEyA3+Mtu1HRGJy6j22PCXFxjpPeAJiYuNsCdPNX11yp+ZkLrS1oX3/+S+pqa3W1qaPtz58URcuuFZRkTF2zmAZSTnO8miVHn3Hzpk62ttabWl8OHcA089EzxsAAAAAAAQt25oiLDKit4up8Vowv0D3fesf9Nef+oSSEhP1jdvX67IVF6mtvV3HKo+rvb3DrjlGYWFut1ddHR29g5/HOtsfCgOeT3DQ4oz/+pQteVR/+he2hGC2bNHHdfUF621taL/8y3e169Brtjb9pCfl6O+u+7GtDe2Jl/+XDlZOrQHQGfAcwFgx4DkAAAAAIFS5Y2m4rT6SFB2fYOeOjWnhcdvf3KiPXbVa//nL5/ToT59SQ2OTkhIT9LErVzvz1+iMwnn67Ysv67XX33SmzfaRo9fV2amm6io3+DCDnzPgOeAjISZZyxd+3NaG9qf3Nkzr4MM42VChJ//4bVsb2kfOvsGWAAAAAAAAAExFppXEeIIPE3p8+++/rueeftwNJK6/8RY9+PAjbvBhmL8//+Vzuum2O9xljU1N+sYdX9Sm5zfo67evd1uKjFZEZKTCnGk06PZqgl3XxN9wli15tPzifVtCsDI36hfNWW5r/m3d+1u99Pbjtja91TQeU2NLjRbMudDOGSw9MVttHc06PIqB0oMF3V4BGCu6vQIAAAAAhCS3y6seRUREjCn8MC06Pv/XN+jb//ANnayp1X+769v67R9eVqMNPfwxy97c9pbbMuRQ+WEtnF+oO9bfrDUrL1V0dLSqne0M93ijp6tLXZ3tbtdXw3XRRfgRROHHhXc8oDsvrNWLWyvsnFFYd5ce+W+XKPY3f9GpiF1uuPthfe2z1+rjH3emlfmq/MM2HbHLpoLMlFxdt+zvFBkRZecMtvPAq3p+y/dtDUbFyX1KiEvV7Iwz7JzBctILtefIFrW0Ndg5wY3wA8BYEX4AAAAAAEKROy6G8/8iY2IVFRvrzhuON/S47+67FBMTrX/+3kP66c+fGTG0GOhQ2WG3+6tnf/M7tzXIFR+9zB0fZEFhgaKjo/Rh6QG7Zn893d3qbGtzu76KS0h0//pDt1dT3Yb7tX79/XrGVieTCWfW5NrKFGW6u4qJGvqmtWnl8Ie3H7M1+Nr8wfNqaWu0tcHiY1P0kSK6vwIAAAAAAACmGhOAREQP/YNxLzOex6+f/nddcG6x/v7b9+pLX79Lb72zyy4d7LJLLnK7wxquayvTLZYZB8Rs685v36fznW2vv/mzdulg4ZGRnsBmBIQfGJ11d+nW4nipcbcee3y3mu3sqWTuzCItOeNqW/Nv857n1dRaZ2vwVd1wRJtLnrM1/84tWKWivEtsDQAAAAAAAMDU0KOIyOHDj+d+/u/u2B6P/fSpEUMPMw7IDx+8X//4D193u7jau2+/XeKfaU1y2+dv1AP/9C13/c/edoddMlh4RITz/0eOP+j26hR0ezV0V1FL9dUH/5du/SuzrFhRlfFK12632yvzmLXF+Vp7681a6zxu1dwTOnnh1/WPt37C3c45cb/Tn3Y7m1j+FX3vG0t10mzTls+48Gbd6u6vOGDdYd1QdImiCuq19Y5/1cY5S3XVeTMV1X5Cb0+hbq+uPP9WzUgZuulK+Yk9+s3Wh2wN/hyr2a9Fc5YpITbFzhksNXGm3t73kq0FL7q9AjBWdHsFAAAAAAg57ngfbsEdP8MMWD4U0zVVUmKi1qy8TPm5c3S08riqT9bYpR4mxLj9tr/V/777H/Sn1zfrzrvv1VvvDh2SGBecu1j/7/5/UlJSotvyY+Mrf1J7e4ddOpg5wramRvdYY53jGeqYafkx2ZZ/Rdklt2v9es+0saZIV6zzLLrh7ptVuP8Ju2yvUkzLCh95BdLTZtnju6Xim3WjNnjWfblcec52/Q4/nVikFLu/x3ZmaM3da+2CiXlmw/267xR1rzUZTKuPs/IutjX/Nu8ZvlUDnJNKR7PbOmY4szMWaH7OBbYGAAAAAAAAILj1uP8XFj58XODtmurL37jLDRx+9K/3u607vP76r653u8RaOL9A1994ix58+BG3S6uhmKDEtPR44N673dYeZtsjtRBxmbDDzWyGb/1B+DHZNm+Tlj+sRx7xTH1jZixVdlqzSrc7y13P6qWd/TuTKtv8A201BWcbpY0+65bVD93tVONuvbTBU9x6vNpTgBbNWW5L/n1Q/qZ2l/3F1jCct/b9QQeO7bQ1/4ryVtgSAAAAAAAAgGBnYoTeRiAjMAHFd/75QV3/13+rF1562Z331KMP6TOf+oT+6V/+1Q0xjh6rdOcPxRuUmCCj9MAh/fyXY/hhunOgozlUwo/Jtm6VFtdssq07btfGcjtfuUpJtEVMqsjwKC3KHT78oNXH2GwuGb71x5m5FyspPsPWAAAAAAAAAAS7nu5OWxod70DlxhmF8/RP//ygXvvLm26wYcbvMON+DGQGPjetRUxQYgZMN91inXN2/6ElRtIzypSG8GOSXTjT9wawae1hi3pWJeXxKlyy1NaluWkTG78E/pngIy0x29YG23ngjzp03AyggtEqObxF5Sc+sLXBYqMTRuxmDAAAAAAAAEDw6O7ssqWJMWN8LJhfqOeeftzt1sqM6eEd0Pxnj3xfbzvLzYDmww2YPpzurtEdJ+HHJNv60AbtSltlu71aJ+0vV97qu3SDs+yZe59QacHNvV1irUgbsjMrTMCi3Itsyb8PyjfbEsbivbI/25J/RbmX2BIAAAAAAACAYGaGDO9sb/dUJsh0i2VadJhxP8yg6GZMj03Pb9AF5xbrs+u/qkd/+tSwY4GMpLuz0z3ekYRlz8kfZU9ewSk1NdOWxicppbcpxrhk/NenbMmj+tO/sKVQs1RfffBmLfbbVVe5NgbpYOimxcftH/+B2/WVP9UNR/TQb7486qZS6JMQm6KvXPuw+3coT7z8P3WwcnwJ7mRrqKuxpfGZ6LkDwNQz0fMGAAAAAABBx46f0dPTrYjIKCVmjO9++9Y/vuAOhD5Uaw7T+mPgMtMa5Nt//3XlzMrWTbfdYeeOrKWhXu3NTQoLC1dqVrY7+Lo/hB+EHyHtojM/oSvP/4KtDfbG+8/qpR3/bmsYq6svWK9liz5ua4O98cGv9NLbj9tacCH8ADBWhB8AAAAAgFBkfhhuwg/T/iMla+jhA4Zjwg+3xce37xtxsHPDjP1husRqbGrWl77+D2NqCVJ/4oQ7PokJP9LM8Q4RftDtFULaojnDD3T+weE3bQnjMVLXV7PSCm0JAAAAAAAAQPAKcwOQ7o6xDXrua2/pAXecD9Oa4/xzFrsBhy9T/8xfXe8u94z98d6Yg4+uzk51d3U4pZE7vgpYy4/Y2GhdsuwsXb58sc4tWqjmzhbFJiQoOjJWzW1tqjherUMHjqh032G99Op2NTa12EdODC0/MJSUhBn6+ieGbnVgBjn/94132RrG63Mrv6PCnPNtrb+W9kb98zN/bWvBhZYfAMaKlh8AAAAAgFDkDgngTN3OFBUbq4TUsd/38nZ7VXHsuNZ//iY36DijcJ5d2seMAfLaX97Uz3/53KhaiAzU0tCgtqZGhZvWHs40XMuPCYcfc/Nna+WlRbr8kjOVOyNbOempSkpM0/uHStUTHu+8WKmKjI1Rc90xpaZkKCshyu2D66EnfqFnnvuzDpUft1san9MdfiB4Fc46X5/76HdsbbA/vP2Y3vzgOVvDeK0svkmXLf6MrQ32499/XUdP7rO14EH4gREtuk43rsiyleE17n5Bz2+usDWEKsIPAAAAAEBIcsf9cP7Xbbq/6lFq9qwhA4WhDDXmhxnXw9sCZKjxQEbNOba6ymNuMSw8TOZ/7rEOYULdXt3yxc/rX//tPl26fKHCe7rV0dmm6NhE9Tg77mnvVFvdCTWfPKj64wfV1tykjjZnXmuHwsMidckFC3T//7hRN37iIrs1ILBmJM+xJf8OHAvOgbinmsPVe23Jv5x0ur4CAAAAAAAAgpYbdHhaUpg/rY0NnvkBYLq0MqHHhIMPR1tTkxvS9DveYYwr/DgjP0f//Ws3qLh4ro6Wf6jGhlbV1beo1vlrkiHTsqOlpUU1NTU6UVOrkzV1OnnypKqOHlJERIS7zsn6BlXV1mnZBYX6+vorlD0zxW4dCIzMlKHDj/auNp2oL7M1TMSBY++qs9v0s+dfTsYZtgQAAAAAAAAgWHmihDA3/OjpNgOgBw9zPC2N9U7JtPcYnTGHH+eema9H/8+XNSsrRVVHD+jkiXI37Ojs6lFzW7s6OjyjrPdERqils0Mtre1qampQW3OLOp3l0dGxMq+baQXS2d6lrs4ezZqRps9/5lKdkT++keQBf7JSB/cp53Wi9pC6usY/eA/6dHa160jV0K0/aPkBAAAAAAAABDc3UAgLs40pwtRUe9IURs1fl1eB1NxQp75GH6MLQMYUfszMSte//o+blZ6WrPTkJCXFxSohwZkS4xQXF6fw8HDFxsaqqalJW/+4RXvefEubf/+KGuoa3cdHRkaqo6NDXV1d6gyLcQ6y032MaQ0SFxetr996rebOynDXBSYqKy3flgY7UVduS6dJ+mX62N/ephu/YKe//Yzm2kXDyr5Ma276Qt/jzPT5z+mjZ+faFU6Pg8ffs6XBZqXPtyUAAAAAAAAAQcmTenjaVTjljrY2tbc0u/NGYzKDD3Mc7c3Osbihh4097PEOZ0wDnv/d363TTZedp+joaB1pqFd1c7siw6WYyDBPa46OLl1y/nn6+29+R7PbOzQnLUkz0pL1YVSEZi0oUM7MWVo0v9ANO3770otu91dSt1o6utXW1a3CnNmqPFytO7/7M88OR4EBz+FPelKO/u66H9vaYBt3/Ltef/9ZWzvFsq/RX107WzGm3O18B8KdL2p3g17/9//UIXcF/1IuuFFXn5swZGLZXfWW/vO5t23t1MqfuVg3r/k/tjbYg7+6WfXN1bYWHBjwHMBYMeA5AAAAACCk9bgjarj37Xt6ut2uppIyZyoyOtqz/DTobG9XQ9VxhYWHy/Q4ZXqhcmMPG36kZg3dm9SoW3587JoLlTsrTa2dHep0XoKo8Ah1tra5gUdTU4saGhrccT66nRektaZeedk5mp2Tr9jYeHVXnnSXmandOVjT+qO+uUV1Tc2qaWh1xwppamx19hKunIxkFcR0eXYKjFNW6vDtKE7UnabxPhZ+Sp/2Bh+tR/TCnxs1qt7z0q/RGht8dFWX6tX/+qme/smjevq/3lJZiye/DM88X9ecpkYWTW11tuRfYly6LQEAAAAAAAAISjZQMH/ckCEsXA3VVerqGHq838lk9tt4sso9Ds/x2AW9heGNKvzIzEjSovmzVXW8Wg31nkHNuzu7dOR4lU5U16qmrlkNjSbEaFRHc6vSYhNUerJFB47Wacu+KtXXtaq1tVOtzmO6OrpV39ispnapqbXH+dut1vYOd3m4879653W8+vprlZ6cYPcOjN3M1KG7vDKOn4bww7Tc+MwlaYowlc4abf357zR8ZNBn0cocuflq/fv6r1//URWNzhfIaHxbf3n6tzrmDl8SptTzVrizT7XWdk/XdkNJjqc7OwAAAAAAACDYmZDB3Gd021e45R7VVx93W2CcSm6Lj+oTnt6jTPBhjsf8/1EGH8aowo9zivLcsTnMuB4tnc7OeiIUH5+szFnzNCP3TGXNmqvMzEylpaWpvbFZ8ZHRUke3GuqqFeasnpWU6h6k2UZbm2kt0uF2fRUVFaVYZ924qBjFmIMOi1K3s+202bN14QWL7d6BwKttPG5Lp0q0812J93zhuhu09clfaN+omnwY0Sp/r1y1rW06/OfX7Txfx1Ra52n9oegUz99TrGWE8CMplpYfAAAAAAAAwFTgxgsmcHCncHeg8fqq42MaA2QizH7M/kym4G314R6PXT5aI4Yf0VGROufsfHecj7jkRDU7O2xvbXXHKojsblZ0Z7WiIjvdYMQMdq7IMJ157plKiIlRTX2HorukjsRYRUTGKMo5uuNHj+nE8ZNuy5GWugZVHjio2oqj6mppUkRYtNq6TEgSrcWLz3T2HWWPApjq2rXv+Q9U396gt3/+n2MIPox2NZX8Qb976j/0p2N2VpDp7HKOsXXodizJ8YQfAAAAAAAAwJRggwZP+OEJQMzfxpqTaqqrcccCmQxmu021Ne5+fPfrTmYF5+9YjBh+FM7LchMW079We1OLTtbU6WhltSoqjqmxsU51dZ6ptrbWnZqbm/Vh5QkdOl6tlvYwVbe06fW9B1TbUO8ceL1qamr0wQcfKLy1RRU7dmrLa1v19hs79JdXtuiFFzepVRHqjIhReGScCgqG77oImFK6X9dvn/xP7THD2wRUtHISPF/87qZy9+/p0NxWb0uDJcbR7RUAAAAAAAAwZdjAoTd8CAtXuDO1NzWp9liF2hobzcjonnUnytmO2V5NZYXam5vc/Uw0+DBGDD+yZmaorVVqrmnW8aNVOl55UieOV+vDPXtVduCQDpQd0cED5dp36Ij+smWXvnT3/9Ovn35BJ1sa1R4Vptr2NsW/V67v/eBZ/eDp3+mdD0qVmpquk4drlTdrvubmFaihJ06dUana9s77ajFdh4VHSBHRmjHMSO0ArIVrNTfWFLpV/dYud9bp0NLeYEuDMeYHAAAAAAAAMMXY4MEEEOGmHG6mcHd+U32tao5VqKWhXl2d4xsQ3TzOPN6EKWZ7Zm9m+2Y/7v7s/scTfBgRicmp99iyX8uXL1Z6QqIinN20dXRob8kB/em1v6h0b6kO7TukhNgYNbZ06mdPv6Ct2z7QgYNmEJIwxcSlKCYxUckx8UqtrlJHa6veOlqj1996X3PSMjQjLlVdYbFKz56j2vpqHT12XO1dnTrrvKXOk4tVl7O/EyertX9fqT0S/2Jj421pfGJi42wJoSQ/a7HmOdNQXt31c1s6zdLP1tn5MQrraVf5O++NegD0XunX6FNXZirSlOs/0HNvnr6WH2X/J1VbF77rd9p14i/qeKfSrhkc2k2qOwGcO4DpZ6LnDQAAAAAAphyfAMKTQbhxiPs/o6OtzW21YYbKMENd9PR4usTqF1z09Dj/1+Ms71Rnu7N+c7Oa6+vU4kzuQOrOOn2tPTwtPsy83scPIzYx0ZYGG7Hlx8ysGYqMinF3aAYs3/H2ezpQcVK1dR06MzFF8RXHtH3LTp2sqXcO3gxAEqaT3T3afviw3qtq1CsnqvTnk/Uqc55zlzN1dHbrD5veVHRnhGZlZis5Ok4LZ2QrKTZSWbOyFRWbpB7nCXZ2hykqYmLBBhDS0i/Tx66frWhTNoOo/9LfYOjBobtxfOkvAAAAAAAAgNPMBBFuCOH529sKJCzczQxMa43uzg61NjWo8WS16o4fU83RIzpZcVgnj5S7f03dzDfLzXpmffM49/Em8PBp7eHdj/P/3N2P14jhR1JsnBrbWtTW3q32DpPOdGl2YpwuLSzQ7Bmz1NUVJTU2y7Rs6e7q21xXW5v2HvhQBz7cpw+cg2zoDFdHT7fbKiQpKUWtilRzS5d6WjqVHJOk2LhkzXW22dHV40xh6lKEmqqP2q0B6Cf7Gv3VJxco2XzlTPDx07EOog4AAAAAAAAAo2cCCTeOMH9N8NH714QXEW6QEe789XRd5X8yyz2BifPXPM5nO+527X4CYcTwIyIyTDVNbTrR1q769nbNzc1xf2ne0SXtP1GnuMh43XDeYiXHRau7p1M94Z5BTnqc44t0ym1tbYpUh7OjHvU4D1qzfLluvenvlJg9X0drqlTf1qqGzihde/U6xTn/a3f20d3Rqe6uLjXVHHe3BQRadIQ7SMbUtPBT+vS1sxVjyh0n9GoQBB/h8W77EwAAAAAAAAChzA0qPCFFb7m31YYJNTw9SPXvxspT94QenvX6Wo84WzJld3OecqCMGH6os0etHe1q6g5Ts7N6xpxZSotPVEJsnGanz1B0bJLqGtv0Lzf/te6/7W91yaJF6uzuUXd3t5qbm9XV1aWajh6dbG9TdkaCrrv8aqU6j4nsaFZ71SHFdrXoggvO0by5ZyohPl111SfV3tPltjBprTlpDwIYm9rG4ceXmJmaZ0tTS8oFN+ozl6Qpwil31+7VC//xa1UEQYuPiPwUW/Kv61CtLQEAAAAAAACY8kxg4Z1MdNFbt4GHG4L0Td7QwzN5Aw8zzzOZeqCNGH60tXVo8RlnaOnZZ+qS4sX6yIoVSpw1T/G5C9TQ0KSquha1KEpxiTM0OzVdt157ndviwwxgYiZXd7e6ero0P/sMpccnSx3Vio/t1pzZ2So45wLFpue6XV3t3vOBnvnPn2rrqy+rq6lW7U1jHv4ZcB2vO2hL/s1InWtLU0W0slberGvPTXBbUbXtf1H/+cvXxj5A+mnS08SYHwAAAAAAAEBIsgFG72Rm+f2fWdX5ayfn/7kPnywjhh8HyyuVmJCsaPWos7ZWEdWtKpo9X50NHTrR3KUTdVWKistSd3SG4lKyFBUVpetXrHDW7nAOvlM9PZ3q7u5wW4IsnOOs11qr2oZqdYRFKatomSLS5qorKlYnT55UzIwsRfRIGZFS8okKZ52RG6YA/lTWHLIl/2ZOsfBj7lWf06qCKKfUrRNb/ku/fKXcsyBIROan2hIAAAAAAACAac0bbPibTqGw7Dn5tnmGfxcsnq/VK1dJZTtUXVqm1LQszcgqUJei1NrZo4iweDW3RysmIVKZM1IVHx+jjs4WfefH/08pcWEqyMxSenqqktKzVVy4UB2NneqKSVdcZqQSE2eqTXHqaO9SRfl+lR59S3v27lNia4fiY6K1peygalqG/8V4amqmLY1PUkqaLSHU3PGxHygzJdfW+tt/7B39x6a7be1UWaxr/na5UkeV6fWo9rXH9Lt9ntpFN92meaMcpqS99FH94lVbOUUSv7JUMZcNHShVf/oXthQ8GupqbGl8OHcA089EzxsAAAAAACCwUrOybWmwEW/D7i45pJzIZFXvb1BXWKy6lKYDRyrVFVmv1o5aHSl7Vx/u+61KPnhRJXvfU019s7p6opU1I0e1zd3KyZyjs3MWKFGRqmsNV2Vnu062HFdCfIo6w+JV09ikiorjbouRmIgWzYyXkvISFZ4QrsaOIBjMAFNWZe3QXV9N1TE/gtVwLT+6DtF9HQAAAAAAAIBTa8SWH8baSz+q5KpqZRTMVVukCSQ6Fd7WqrCwFjVVfqi21m5FJcdLTW1qSFqorOwcHa46pOj4BLfLrK7WMDXWNCh7drZy56VJx6uUmDZPrYk52nNgjyJN91jtxxXWVqOojhMKj07WvqN1euHNDzwHMAxafmAol569Th8953O2Nti//OImNbfV2xomIuO/PmVLg7W9dkiNP9hma8GDlh8AxoqWHwAAAAAABJcJtfwwXnv3LcUWzlfMjGRlZKUqvvu4musqVbrrQ1V8UKnDVbWq2HtU0V3NOmN+mhIzopz1Zqquod6dwqK7NOeMmcopSFOMs8e4uAS1djbpYPmHio7tUXJqlGamxqt111bt/fV27fj5Rr26+X27d2B8jteW2ZJ/M1Om2qDnwSnqrBm25F/XwVpbAgAAAAAAAIBTY1ThR3VdnXYf2afY1Dil5Gcr9YxFSqgpUWxNuWrrWtVR36C48B6lnXWOkmfMVFp6ggrzZ6n4zHkqyJuh3NlpypqVqYzsmepu7VZXWLxau8x4Ic2KjXLqPT1qD4tTdEKKu4/6mclq6jm1g58g9AzX7ZVxZu5FtoSJGGmw807CDwAAAAAAAACn2KjCD+PPb7+ryuNHFR0drcTsHEWevVxpS5bogmsu1SUfu1rnfnqdUuctVGRMtMKjoxQREab4+FilJcUpOSNRmfnZ6nEe2x6foLq2brV09agj7ENFph5XZFS4mrva1TXvPIXnp+iduhF74goa99z0m34TgkdN4zE1tQ093sSZuRfbEiYi5vJ8W/Kv6yBjfgAAAAAAAAA4tUYdfhgbfvcnHT96Qk3NnYrPLtSc85Yr67xlis2brw5nU12KUKd61NXVpY72dkVFRioiOloxMbHq6elQd0+nlJyklrgIKc6z6+a6V5Qc16OcGSmas3CB/nLUWQYEyAflb9rSYMkJGcqfudjWMB7hM+MVMTfF1gYzg513N7fbGgAAAAAAAACcGmMKP07U1Oun//kbtba0KTo+zm3hoUhnExGezRzdWaKjr76l/c+9qrIX/qKKP2xW2W/+rL3//hvt/ZentO//PqGqp55TdFODktJTNXPGYkXHpigh2XR5laGnfvG8TlQzmCgCZ0/ZZlvyb8GcJbaE8Yi5dPhWH22vDt/1GAAAAAAAAABMhjGFH8ahw8f048c36NjxE27m0dPVqa6ObkUnxav9/TJVvrxNDdtL1PDOPjVu36uGHR+q5b2DqttzVC17KtXq1MMbG5099yg6JkExcQt06OQR/evDj2pf6QG7F5wWy7+i7z3ysB6x07fW2fk+bri7b/kjD35FF9r5wepA5Ttq72y1tcEWzllmSxiPmMuHHzS+bfsRWwIAAAAAAACAU2fM4Ydx7Hi1Hv73X+lPm99Rd3e3Ow6IYcqRpquriAhFOH8V5fwNj3Hmxbrzo6Ki3K6wWpuaVVdXq+buLm3dWqd//eef6/CRCncbOF3W6lu3FCne1vy58I4HtCbXVqaILucz9kH5G7Y2WEbSbC2YvdTWMBZRZ81QxIwEWxus44MT6j7ebGsAAAAAAAAAcOqMK/zwevGVrfrhE7/U9p171NTYpvbWDkVHRComMkoxzt+4MGeKDFNseLjzN0LR4VGKjYhSR3W93tn5vh798U+16aWtdms4fZbqqw+uUp5TKnv5dm0s98ztZ91durU4Xmrcrcce362pdEt7/9GdtuTfkjOutiWMRdy6s2zJv7ZXD9kSAAAAAAAAAJxaEwo/jGMnavTc7/+kf/3Rf+kvDbXaF9at2jCpW2GKCo9QWHi42sPDVBXerd3hbfpNT4N+sPVN/f7FP6ryeJXdSihbq2/5dg9lupbqrTvLhuhmyrSy8Ne9lOl26lt3eLqn+t4dAWqxsDxXqmlW884ndN8GO2+AG5ypTOXa+I0faKrFVabrq+6eHlsbzLT8oPXH2JhWH1FnzrA1/zq20poLAAAAAAAAwOkx4fDDq72jU6Vt7fpTWKd+qTb9e1irHlKTHu5u1A+76/RkZ61e7KrT7q4WtXV32UdNbxfecZEydz6h9etvd6ZNbjDihhymlUXam3a+M21O1q13r3UfY+QVSE8787/20DY7Z4I2P6vv33vnsNt7ZsP9um/9/XrG1qeS+uZqfVgx/GtF64+xGbHVx2uH1N3cbmsAAAAAAAAAcGoFLPzA2G09Xq344pv1yCMP6KvLn9V9tlXFhTMzpNxVfS0/VudKadm9rT/KNk+91hen25aS39qSf7T+GL3RtPpo/sVuWwIAAAAAAACAU4/w43TacL+nZcfjB1R4S1/3VnPT4t0uqHpbfphpCnY3FUz2H92hdw+8Ymv+0fpjdK7N+bxmHEi0tcHcVh8MdI6Qd55WfeE6FdoaAAAAAAAAggvhx6mQOE/LlttyXrLibdGM6+GO87H5B/ra+k0qS0zWXKf6TEm54guW9rb0MN1gPeLT7RXGZ+vekVt/nD//SluDP0vPuEbnZl2mz+z6Ky1/tkCJJ2Pskj60+kBwydGSdbfpxqvOs3WPwqtu06pFtjKZMlfqugH7BgAAAAAAwOQj/Jh0z+q+l6u12LTsMF1YLc9Q72/it7+puuV2/iOrpJftmBob7tdj++fpVm+3V8vr9di9z7oPwfgdqdo7YvdXq8/9nLLS5tkafM3JXKhLz+4blX9p5Ef0qY1XqGhjtsJ7PKcSWn0gaM0+S0sybRkAAAAAAAAhLyx7Tn6PLU9JqakTu5uVlJJmS+Nzz02/sSWPe576uC1NMaZ1iRlbxA/TBdfXHpK++uDNWuy3t6NybZwig6FnJM/WbVf+X8VGJ9g5g31YsV1PvfIdW4MRER6pT1/6P4ccF6Wsfo92LvhQbz/5sykTfjTU1djS+Ez03IFTxbT8WCntrFBOsfTnDa/IvPOm5Uf+wUe1aY93nWu1IMmUpcbdL+j5zRWeiq9F1+nGFVlusXH3PjUVJengT55XqVM321s2213kfLj26fdmPz7rS5XaYtY1LUGuny/vqbTyde8xYCqY6HkDAAAAAAAEVmpWti0NRvhB+DHtfPScz/VrweDPKzuf1mu7fm5rWFl8ky5b/Blb82/ngVf17Bv/19aCH+HHdGHDjz8+pZolt6mo1hNs+IYfpuydb/QPRiw3tEjSbht2pC2/SVcXNfQGGksyX9F2u36/x5sAJL9cT7+4w7Ns+Uqd3OwJYNxtfrQvkEHwI/wAAAAAACC4DBd+0O0Vpp03PnhWR0+a25dDW1l8o87IWWJr09tZeRePGHw0tdbq9289YmtAcCp9cbuaikxQYWe4cpSW3KyKfX0tPUoPViorf8A4HZlJSjxS7gYfRs3m91Vpy6oqkYpv041f8Ey9LUD8KK1N0tV2vRt9WoAAAAAAAAAgsAg/MO20tjfplZ1P2drQ1pz/t8pMHuYu5jQwJ3OBPnnRN2xtaH94+ydqaWuwNSBY7dCm1xu0YIlvsJGlFNvd1XDSUodZadESLajfrqd/8qg7bTli5w+SoyXFSdr7nGe9p5/bp0a7BAAAAAAAAIFF+DFBppsr3wlTw94j2/T6+7+0Nf9mpuRp7cXfVEr8DDtnekmKS9dnLvuWoiJj7Bz/dpS+7HZ5BUwJe57XFi3xaZ2xQwePxCtnfo6tS4X5Wao86OmmyqtmX4UaZ+eq0NaVmSrvyEH9gxHTksQWBxkQtJjWJLYIAAAAAACAsevp7ralwSISk1PvseUpKTY23pbGJyY2zpYw3Rw6vlsF2cVKSRg63EiKT9fsjDNUcmSrOrra7Nzp4YvX/KtSE2bamn9V9Uf0i9f/RZ1d7XbO1NHe1mpL48O5Y6pIUk7RPOnALlXYsfhralM0b1G6Osrf1oEqE2wcU9LF1+ry5Rdo8fkXKLX8Bb34zoCWTM0HVdKyUFdff6m7zuLZsWqPadeJHSWqOFytmPMvt4+fo+p9JzX3goVqdpbVKEfzlp6tC5z5zTte01st83T5GruNiGPaGzNfZycdU8lhWk5NBRM9bwAAAAAAgMAy9+jCIvy38WDAcwYtntYKs8/V51bda2tDKz36jv7zT/epo3N6BCDrr/5X5aTPt7WhPfOX+7X70Ou2NrUw4DmAsWLAcwAAAAAAgktSeobCzP8iwhUW7glBTGuQnq5u/f9jIVsw97fwjgAAAABJRU5ErkJggg==;" parent="1" vertex="1">
+ <mxGeometry x="20.75" y="90" width="355.33" height="200" as="geometry" />
+ </mxCell>
+ </UserObject>
+ <UserObject label="&lt;span style=&quot;font-size: 16px&quot;&gt;&lt;b&gt;dashboard.js&lt;/b&gt;&lt;/span&gt;&lt;div&gt;&lt;font style=&quot;font-size: 14px&quot;&gt;real-time netdata charts&lt;/font&gt;&lt;/div&gt;&lt;div&gt;&lt;font style=&quot;font-size: 14px&quot;&gt;on any web page&lt;/font&gt;&lt;/div&gt;" id="6wNVMjbWM3VSl3cxvCD3-271">
+ <mxCell style="whiteSpace=wrap;html=1;strokeColor=#59DE9C;fillColor=#FFFFFF;fontSize=14;align=center;" parent="1" vertex="1">
+ <mxGeometry x="75" y="470" width="249" height="74" as="geometry" />
+ </mxCell>
+ </UserObject>
+ <UserObject label="&lt;b&gt;&lt;font style=&quot;font-size: 20px&quot;&gt;Query Engine&lt;/font&gt;&lt;/b&gt;&lt;br&gt;Data reduction &amp;amp; re-sampling&lt;br&gt;" link="https://learn.netdata.cloud/docs/agent/web/api/queries" linkTarget="_blank" id="6wNVMjbWM3VSl3cxvCD3-272">
+ <mxCell style="whiteSpace=wrap;html=1;strokeColor=#59DE9C;fillColor=#FFFFFF;fontSize=14;align=center;" parent="1" vertex="1">
+ <mxGeometry x="254" y="607" width="196" height="70" as="geometry" />
+ </mxCell>
+ </UserObject>
+ <mxCell id="6wNVMjbWM3VSl3cxvCD3-273" value="" style="html=1;shadow=0;dashed=0;align=center;verticalAlign=middle;shape=mxgraph.arrows2.arrow;dy=0.51;dx=19.57;direction=north;notch=0;fillColor=#59DE9C;strokeColor=none;" parent="1" vertex="1">
+ <mxGeometry x="100" y="446" width="70" height="30" as="geometry" />
+ </mxCell>
+ <mxCell id="6wNVMjbWM3VSl3cxvCD3-274" value="" style="html=1;shadow=0;dashed=0;align=center;verticalAlign=middle;shape=mxgraph.arrows2.arrow;dy=0.51;dx=19.57;direction=north;notch=0;fillColor=#59DE9C;strokeColor=none;" parent="1" vertex="1">
+ <mxGeometry x="229" y="446" width="70" height="30" as="geometry" />
+ </mxCell>
+ <mxCell id="178" value="" style="html=1;shadow=0;dashed=0;align=center;verticalAlign=middle;shape=mxgraph.arrows2.arrow;dy=0.51;dx=19.57;direction=north;notch=0;fillColor=#59DE9C;strokeColor=none;" parent="1" vertex="1">
+ <mxGeometry x="159" y="534" width="70" height="30" as="geometry" />
+ </mxCell>
+ <mxCell id="6wNVMjbWM3VSl3cxvCD3-291" value="" style="html=1;shadow=0;dashed=0;align=center;verticalAlign=middle;shape=mxgraph.arrows2.jumpInArrow;dy=15;dx=38;arrowHead=55;strokeColor=none;fillColor=#59DE9C;fontSize=14;flipH=1;direction=east;" parent="1" vertex="1">
+ <mxGeometry x="710" y="300" width="100" height="190" as="geometry" />
+ </mxCell>
+ <mxCell id="6wNVMjbWM3VSl3cxvCD3-293" value="" style="html=1;shadow=0;dashed=0;align=center;verticalAlign=middle;shape=mxgraph.arrows2.arrow;dy=0.51;dx=19.57;direction=north;notch=0;fillColor=#59DE9C;strokeColor=none;" parent="1" vertex="1">
+ <mxGeometry x="512" y="534" width="70" height="30" as="geometry" />
+ </mxCell>
+ <mxCell id="6wNVMjbWM3VSl3cxvCD3-294" value="" style="html=1;shadow=0;dashed=0;align=center;verticalAlign=middle;shape=mxgraph.arrows2.arrow;dy=0.51;dx=19.57;direction=north;notch=0;fillColor=#59DE9C;strokeColor=none;" parent="1" vertex="1">
+ <mxGeometry x="357" y="533" width="70" height="30" as="geometry" />
+ </mxCell>
+ <mxCell id="50" value="" style="html=1;shadow=0;dashed=0;align=center;verticalAlign=middle;shape=mxgraph.arrows2.arrow;dy=0.51;dx=19.57;direction=north;notch=0;fillColor=#59DE9C;strokeColor=none" parent="1" vertex="1">
+ <mxGeometry x="1131" y="863" width="70" height="30" as="geometry" />
+ </mxCell>
+ <mxCell id="6wNVMjbWM3VSl3cxvCD3-276" value="" style="html=1;shadow=0;dashed=0;align=center;verticalAlign=middle;shape=mxgraph.arrows2.jumpInArrow;dy=13;dx=33;arrowHead=58;strokeColor=none;fillColor=#59DE9C;fontSize=14;" parent="1" vertex="1">
+ <mxGeometry x="1054" y="597" width="66" height="115" as="geometry" />
+ </mxCell>
+ <mxCell id="6wNVMjbWM3VSl3cxvCD3-277" value="" style="html=1;shadow=0;dashed=0;align=center;verticalAlign=middle;shape=mxgraph.arrows2.arrow;dy=0.51;dx=19.57;direction=east;notch=0;fillColor=#59DE9C;strokeColor=none;" parent="1" vertex="1">
+ <mxGeometry x="1014.5" y="709" width="35" height="55" as="geometry" />
+ </mxCell>
+ <mxCell id="6wNVMjbWM3VSl3cxvCD3-281" value="" style="html=1;shadow=0;dashed=0;align=center;verticalAlign=middle;shape=mxgraph.arrows2.arrow;dy=0.51;dx=19.57;direction=north;notch=0;fillColor=#59DE9C;strokeColor=none;" parent="1" vertex="1">
+ <mxGeometry x="310" y="667" width="70" height="30" as="geometry" />
+ </mxCell>
+ <mxCell id="6wNVMjbWM3VSl3cxvCD3-284" value="" style="html=1;shadow=0;dashed=0;align=center;verticalAlign=middle;shape=mxgraph.arrows2.arrow;dy=0.51;dx=19.57;direction=north;notch=0;fillColor=#59DE9C;strokeColor=none;" parent="1" vertex="1">
+ <mxGeometry x="739" y="863" width="70" height="30" as="geometry" />
+ </mxCell>
+ <mxCell id="6wNVMjbWM3VSl3cxvCD3-285" value="" style="html=1;shadow=0;dashed=0;align=center;verticalAlign=middle;shape=mxgraph.arrows2.arrow;dy=0.51;dx=19.57;direction=north;notch=0;fillColor=#59DE9C;strokeColor=none;" parent="1" vertex="1">
+ <mxGeometry x="369" y="863" width="70" height="30" as="geometry" />
+ </mxCell>
+ <mxCell id="6wNVMjbWM3VSl3cxvCD3-286" value="" style="html=1;shadow=0;dashed=0;align=center;verticalAlign=middle;shape=mxgraph.arrows2.arrow;dy=0.51;dx=19.57;direction=north;notch=0;fillColor=#59DE9C;strokeColor=none;" parent="1" vertex="1">
+ <mxGeometry x="891" y="863" width="70" height="30" as="geometry" />
+ </mxCell>
+ <mxCell id="6wNVMjbWM3VSl3cxvCD3-287" value="" style="html=1;shadow=0;dashed=0;align=center;verticalAlign=middle;shape=mxgraph.arrows2.arrow;dy=0.51;dx=19.57;direction=north;notch=0;fillColor=#59DE9C;strokeColor=none;" parent="1" vertex="1">
+ <mxGeometry x="1011" y="863" width="70" height="30" as="geometry" />
+ </mxCell>
+ <mxCell id="6wNVMjbWM3VSl3cxvCD3-296" value="" style="html=1;shadow=0;dashed=0;align=center;verticalAlign=middle;shape=mxgraph.arrows2.jumpInArrow;dy=8.5;dx=34;arrowHead=55;strokeColor=none;fillColor=#59DE9C;fontSize=14;flipH=1;direction=north;" parent="1" vertex="1">
+ <mxGeometry x="801" y="526" width="50" height="120" as="geometry" />
+ </mxCell>
+ <mxCell id="6wNVMjbWM3VSl3cxvCD3-297" value="" style="html=1;shadow=0;dashed=0;align=center;verticalAlign=middle;shape=mxgraph.arrows2.jumpInArrow;dy=15;dx=38;arrowHead=55;strokeColor=none;fillColor=#59DE9C;fontSize=14;" parent="1" vertex="1">
+ <mxGeometry x="880" y="420" width="90" height="221" as="geometry" />
+ </mxCell>
+ <mxCell id="6wNVMjbWM3VSl3cxvCD3-298" value="" style="html=1;shadow=0;dashed=0;align=center;verticalAlign=middle;shape=mxgraph.arrows2.jumpInArrow;dy=15;dx=38;arrowHead=55;strokeColor=none;fillColor=#59DE9C;fontSize=14;flipH=1;direction=east;" parent="1" vertex="1">
+ <mxGeometry x="710" y="299" width="100" height="190" as="geometry" />
+ </mxCell>
+ <mxCell id="6wNVMjbWM3VSl3cxvCD3-299" value="" style="html=1;shadow=0;dashed=0;align=center;verticalAlign=middle;shape=mxgraph.arrows2.arrow;dy=0.51;dx=19.57;direction=north;notch=0;fillColor=#59DE9C;strokeColor=none;" parent="1" vertex="1">
+ <mxGeometry x="717" y="583" width="70" height="30" as="geometry" />
+ </mxCell>
+ <mxCell id="6wNVMjbWM3VSl3cxvCD3-300" value="" style="html=1;shadow=0;dashed=0;align=center;verticalAlign=middle;shape=mxgraph.arrows2.arrow;dy=0.51;dx=19.57;direction=north;notch=0;fillColor=#59DE9C;strokeColor=none;" parent="1" vertex="1">
+ <mxGeometry x="512" y="533" width="70" height="30" as="geometry" />
+ </mxCell>
+ <mxCell id="6wNVMjbWM3VSl3cxvCD3-301" value="" style="html=1;shadow=0;dashed=0;align=center;verticalAlign=middle;shape=mxgraph.arrows2.arrow;dy=0.51;dx=19.57;direction=north;notch=0;fillColor=#59DE9C;strokeColor=none;" parent="1" vertex="1">
+ <mxGeometry x="357" y="532" width="70" height="30" as="geometry" />
+ </mxCell>
+ <mxCell id="6wNVMjbWM3VSl3cxvCD3-302" value="" style="html=1;shadow=0;dashed=0;align=center;verticalAlign=middle;shape=mxgraph.arrows2.arrow;dy=0.51;dx=19.57;direction=north;notch=0;fillColor=#59DE9C;strokeColor=none;" parent="1" vertex="1">
+ <mxGeometry x="310" y="583" width="70" height="30" as="geometry" />
+ </mxCell>
+ <mxCell id="6wNVMjbWM3VSl3cxvCD3-303" value="" style="html=1;shadow=0;dashed=0;align=center;verticalAlign=middle;shape=mxgraph.arrows2.twoWayArrow;dy=0.52;dx=21.1;strokeColor=none;fillColor=#59DE9C;fontSize=14;direction=south;" parent="1" vertex="1">
+ <mxGeometry x="133" y="576" width="60" height="50" as="geometry" />
+ </mxCell>
+ <mxCell id="6wNVMjbWM3VSl3cxvCD3-304" value="" style="html=1;shadow=0;dashed=0;align=center;verticalAlign=middle;shape=mxgraph.arrows2.arrow;dy=0.51;dx=19.57;direction=north;notch=0;fillColor=#59DE9C;strokeColor=none;" parent="1" vertex="1">
+ <mxGeometry x="159" y="533" width="70" height="30" as="geometry" />
+ </mxCell>
+ <mxCell id="6wNVMjbWM3VSl3cxvCD3-305" value="" style="html=1;shadow=0;dashed=0;align=center;verticalAlign=middle;shape=mxgraph.arrows2.arrow;dy=0.51;dx=19.57;direction=north;notch=0;fillColor=#59DE9C;strokeColor=none;" parent="1" vertex="1">
+ <mxGeometry x="229" y="445" width="70" height="30" as="geometry" />
+ </mxCell>
+ <mxCell id="6wNVMjbWM3VSl3cxvCD3-306" value="" style="html=1;shadow=0;dashed=0;align=center;verticalAlign=middle;shape=mxgraph.arrows2.arrow;dy=0.51;dx=19.57;direction=north;notch=0;fillColor=#59DE9C;strokeColor=none;" parent="1" vertex="1">
+ <mxGeometry x="100" y="445" width="70" height="30" as="geometry" />
+ </mxCell>
+ <mxCell id="6wNVMjbWM3VSl3cxvCD3-307" value="" style="html=1;shadow=0;dashed=0;align=center;verticalAlign=middle;shape=mxgraph.arrows2.arrow;dy=0.51;dx=19.57;direction=north;notch=0.1;fillColor=#59DE9C;strokeColor=none;" parent="1" vertex="1">
+ <mxGeometry x="910" y="666" width="70" height="30" as="geometry" />
+ </mxCell>
+ <mxCell id="6wNVMjbWM3VSl3cxvCD3-308" value="" style="html=1;shadow=0;dashed=0;align=center;verticalAlign=middle;shape=mxgraph.arrows2.arrow;dy=0.51;dx=19.57;direction=north;notch=0;fillColor=#59DE9C;strokeColor=none;" parent="1" vertex="1">
+ <mxGeometry x="716" y="666" width="70" height="30" as="geometry" />
+ </mxCell>
+ <mxCell id="6wNVMjbWM3VSl3cxvCD3-313" value="" style="html=1;shadow=0;dashed=0;align=center;verticalAlign=middle;shape=mxgraph.arrows2.arrow;dy=0.51;dx=19.57;direction=north;notch=0;fillColor=#59DE9C;strokeColor=none;" parent="1" vertex="1">
+ <mxGeometry x="103" y="1006" width="70" height="120" as="geometry" />
+ </mxCell>
+ <mxCell id="6wNVMjbWM3VSl3cxvCD3-314" value="" style="html=1;shadow=0;dashed=0;align=center;verticalAlign=middle;shape=mxgraph.arrows2.arrow;dy=0.51;dx=19.57;direction=north;notch=0;fillColor=#59DE9C;strokeColor=none;" parent="1" vertex="1">
+ <mxGeometry x="183" y="1007" width="70" height="30" as="geometry" />
+ </mxCell>
+ <mxCell id="6wNVMjbWM3VSl3cxvCD3-315" value="" style="html=1;shadow=0;dashed=0;align=center;verticalAlign=middle;shape=mxgraph.arrows2.arrow;dy=0.51;dx=19.57;direction=north;notch=0;fillColor=#59DE9C;strokeColor=none;" parent="1" vertex="1">
+ <mxGeometry x="264" y="1007" width="70" height="120" as="geometry" />
+ </mxCell>
+ <UserObject label="&lt;div&gt;&lt;b&gt;&lt;font style=&quot;font-size: 20px&quot;&gt;&amp;nbsp; ML Engine&lt;/font&gt;&lt;/b&gt;&lt;/div&gt;&amp;nbsp; &amp;nbsp;Anomaly Detection" link="https://learn.netdata.cloud/docs/agent/ml" linkTarget="_blank" id="6RedLYv8twKyCitmQhae-261">
+ <mxCell style="whiteSpace=wrap;html=1;strokeColor=#59DE9C;fillColor=#FFFFFF;fontSize=14;align=center;" parent="1" vertex="1">
+ <mxGeometry x="460" y="607" width="180" height="70" as="geometry" />
+ </mxCell>
+ </UserObject>
+ <mxCell id="6RedLYv8twKyCitmQhae-263" value="" style="html=1;shadow=0;dashed=0;align=center;verticalAlign=middle;shape=mxgraph.arrows2.arrow;dy=0.51;dx=19.57;direction=north;notch=0;fillColor=#59DE9C;strokeColor=none;" parent="1" vertex="1">
+ <mxGeometry x="512" y="666" width="70" height="30" as="geometry" />
+ </mxCell>
+ <mxCell id="6RedLYv8twKyCitmQhae-265" value="" style="html=1;shadow=0;dashed=0;align=center;verticalAlign=middle;shape=mxgraph.arrows2.arrow;dy=0.52;dx=24;notch=0;fillColor=#59DE9C;strokeColor=none;" parent="1" vertex="1">
+ <mxGeometry x="618" y="629" width="40" height="30" as="geometry" />
+ </mxCell>
+ <mxCell id="6RedLYv8twKyCitmQhae-267" value="" style="html=1;shadow=0;dashed=0;align=center;verticalAlign=middle;shape=mxgraph.arrows2.arrow;dy=0.51;dx=19.57;direction=north;notch=0.1;fillColor=#59DE9C;strokeColor=none;" parent="1" vertex="1">
+ <mxGeometry x="1106" y="772.5" width="70" height="30" as="geometry" />
+ </mxCell>
+ <mxCell id="6RedLYv8twKyCitmQhae-268" value="" style="html=1;shadow=0;dashed=0;align=center;verticalAlign=middle;shape=mxgraph.arrows2.arrow;dy=0.51;dx=19.57;direction=north;notch=0.1;fillColor=#59DE9C;strokeColor=none;" parent="1" vertex="1">
+ <mxGeometry x="601" y="772.5" width="70" height="30" as="geometry" />
+ </mxCell>
+ <mxCell id="6RedLYv8twKyCitmQhae-270" value="" style="shape=image;verticalLabelPosition=bottom;labelBackgroundColor=default;verticalAlign=top;aspect=fixed;imageAspect=0;image=https://raw.githubusercontent.com/netdata/netdata-ui/a17e8628d120ab739d1c98bf21ed6a240d12ba6d/src/components/icon/assets/anomalies_lens.svg;" parent="1" vertex="1">
+ <mxGeometry x="466.5" y="626" width="30" height="30" as="geometry" />
+ </mxCell>
+ <mxCell id="6RedLYv8twKyCitmQhae-272" value="" style="html=1;shadow=0;dashed=0;align=center;verticalAlign=middle;shape=mxgraph.arrows2.arrow;dy=0.51;dx=19.57;direction=north;notch=0;fillColor=#59DE9C;strokeColor=none;" parent="1" vertex="1">
+ <mxGeometry x="512" y="583" width="70" height="30" as="geometry" />
+ </mxCell>
+ <mxCell id="6RedLYv8twKyCitmQhae-273" value="" style="endArrow=none;dashed=1;html=1;dashPattern=1 1;strokeColor=#59DE9C;strokeWidth=3;fontSize=13;entryX=0.421;entryY=0.191;startArrow=block;startFill=1;entryPerimeter=0;" parent="1" edge="1">
+ <mxGeometry width="50" height="50" relative="1" as="geometry">
+ <mxPoint x="820.5975332688117" y="1010" as="sourcePoint" />
+ <mxPoint x="835.1999999999998" y="1148.82" as="targetPoint" />
+ </mxGeometry>
+ </mxCell>
+ <mxCell id="6RedLYv8twKyCitmQhae-274" value="" style="endArrow=none;dashed=1;html=1;dashPattern=1 1;strokeColor=#59DE9C;strokeWidth=3;fontSize=13;entryX=0.5;entryY=0;startArrow=block;startFill=1;" parent="1" edge="1">
+ <mxGeometry width="50" height="50" relative="1" as="geometry">
+ <mxPoint x="813.31" y="1010" as="sourcePoint" />
+ <mxPoint x="799.81" y="1089.5" as="targetPoint" />
+ </mxGeometry>
+ </mxCell>
+ <UserObject label="" link="https://www.netdata.cloud/integrations" linkTarget="_blank" id="gruJ99u_ENbLo-s2ro46-261">
+ <mxCell style="shape=image;verticalLabelPosition=bottom;labelBackgroundColor=default;verticalAlign=top;aspect=fixed;imageAspect=0;image=data:image/png,iVBORw0KGgoAAAANSUhEUgAAB4sAAALeCAYAAACz/vcRAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAAFiUAABYlAUlSJPAAAP+lSURBVHhe7N0HgBTV/Qfw77br/Y7emwgWUBEVrKCIiliiRKMx9ijRYIn4F7tiwxLRiMZEg90YS0SNFbAAKqIiIkV6b9d72fJ/vzcze3N7e3AHV3b3vh+c291pu85v3ptXpjgCCoiIiIiIiIiIiIiIiIiIqF1xmq9ERERERERERERERERERNSOsLOYiIiIiIiIiIiIiIiIiKgdYmcxEREREREREREREREREVE7xM5iIiIiIiIiIiIiIiIiIqJ2iJ3FRERERERERERERERERETtEDuLiYiIiIiIiIiIiIiIiIjaIXYWExERERERERERERERERG1Q+wsJiIiIiIiIiIiIiIiIiJqh9hZTERERERERERERERERETUDrGzmIiIiIiIiIiIiIiIiIioHWJnMRERERERERERERERERFRO+QIKOb7PRo+6jTzHUWqhXM+MN81HeMb+Rjf2Mb4xjbGN7YxvrGN8Y1tjG9sY3xjG+Mb2xjf2Mb4xjbGN7YxvrGN8Y1tjG9s25f48spiIiIiIiIiIiIiIiIiIqJ2iJ3FRERERERERERERERERETtEDuLiYiIiIiIiIiIiIiIiIjaIXYWExERERERERERERERERG1Q+wsJiIiIiIiIiIiIiIiIiJqh9hZTERERERERERERERERETUDrGzmIiIiIiIiIiIiIiIiIioHWJnMRERERERERERERERERFROxQjncWT8Mas+3GB+YmIiNqzszFj1jO4zfxEbYmxiEmTn8Ebk833FFNumxlJ6ZXl++ih8vqZZqyYP0Qnxi06ME4xxpZ3NrPIOp5Ti2B+QBR5zrkfbzx2tvmBol+443TLHbsp2sVG+2dkdBarzHT2nA+wsIEGoQseewOzrcxW5rXNd9vMNzDjnOmYMH4KXjHHNYr1nbv5Xmprksg+qI29UAXihXPsCW8S3lAxtArJsq/omNrnscd65iRzpOw7YeKv12+Mr/O91PzqxdKIt7X97dOCsZoj6d0caapbEa5tWK5dxr6csb+Erp/2Rt20F1nexsTxV2Gq+ak+9dtteQE1RZh8OYwLHnvGTHN7ikUInV/b03nI99nz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Mr5EkU6Sg5W+jMFKr0y/REREREREe0XXdYAD9u+NZ595HC+9+CiOP2aYbsTjFcVELcc6eV39QXZWJk4adRxGHTsSBw3eH263Gz27d8PJatypY0ahb++e+upB6RRojhPTpb1i9LFH6/UfefihelzXLp0w6jhjnHR+WL+vOb6PqD0rV+nt54REvJqehftzOuHeDp3xXGYOvkhKwTpPHEpcTsiNqiWlyat8Xq/Gy3SZT+a/P6ejXl7WU8Fjc4sL5n1qYP7cMqLimcXUeHxmRGxjfGMb4xvbGN/YxvjGNsY3tjG+sY3xjW2Mb2xjfGMb4xvbGN/YxvjGNsY3tjG+sW1f4stTHoiIiIiIiIiIiIiIiIiI2iF2FhMRERERERERERERERERtUPsLCYiIiIiIiIiIiIiIiIiaofYWUxERERERERERERERERE1A6xs5iIiIiIiIiIiIiIiIiIqB1iZzERERERERERERERERERUTvEzmIiIiIiIiIiIiIiIiIionaIncVERERERERERERERERERO0QO4uJiIiIiIiIiIiIiIiIiNohdhYTEREREREREREREREREbVD7CwmIiIiIiIiIiIiIiIiImqH2FlMRERERERERERERERERNQOsbOYiIiIiIiIiIiIiIiIiKgdYmcxEREREREREREREREREVE7xM5iIiIiIiIiIiIiIiIiIqJ2iJ3FRERERERERERERERERETtEDuLiYiIiIiIiIiIiIiIiIjaIXYWExERERERERERERERERG1Q+wsJiIiIiIiIiIiIiIiIiJqh9hZTERERERERERERERERETUDrGzmIiIiIiIiIiIiIiIiIioHWJnMRERERERERERERERERFRO8TOYiIiIiIiIiIiIiIiIiKidoidxURERERERERERERERERE7RA7i4mIiIiIiIiIiIiIiIiI2iF2FhMRERERERERERERERERtUPsLCYiIiIiIiIiIiIiIiIiaofYWUxERERERERERERERERE1A6xs5iIiIiIiIiIiIiIiIiIqB1yBBTzPRERERERERERERERERERtRO8spiIiIiIiIiIiIiIiIiIqB1iZzERERERERERERERERERUTvEzmIiIiIiIiIiIiIiIiIionaIncVERERERERERERERERERO0QO4uJiIiIiIiIiIiIiIiIiNohdhYTERER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hmEYhmEYhmEYhmEYhmEYhmFaIOwsZhiGYRiGYRiGYRiGYRiGYRiGYRiGaYGws5hhGIZhGIZhGIZhGIZhGIZhGIZhGKYFws5ihmEYhmEYhmEYhmEYhmEYhmEYhmGYFgg7ixmGYRiGYRiGYRiGYRiGYRiGYRiGYVog7CxmGIZhGIZhGIZhGIZhGIZhGIZhGIZpgbCzmGEYhmEYhmEYhmEYhmEYhmEYhmEYpgXCzmKGYRiGYRiGYRiGYRiGYRiGYRiGYZgWCDuLGYZhGIZhGIZhGIZhGIZhGIZhGIZhWiDsLGYYhmEYhmEYhmEYhmEYhmEYhmEYhmmBsLOYYRiGYRiGYRiGYRiGYRiGYRiGYRimBcLOYoZhGIZhGIZhGIZhGIZhGIZhGIZhmBYIO4sZhmEYhmEYhmEYhmEYhmEYhmEYhmFaIOwsZhiGYRiGYRiGYRiGYRiGYRiGYRiGaYGws5hhGIZhGIZhGIZhGIZhGIZhGIZhGKYFws5ihmEYhmEYhmEYhmEYhmEYhmEYhmGYFgg7ixmGYRiGYRiGYRiGYRiGYRiGYRiGYVog7CxmGIZhGIZhGIZhGIZhGIZhGIZhGIZpgbCzmGEYhmEYhmEYhmEYhmEYhmEYhmEYpgXCzmKGYRiGYRiGYRiGYRiGYRiGYRiGYZgWCDuLGYZhGIZhGIZhGIZhGIZhGIZhGIZhWiDsLGYYhmEYhmEYhmEYhmEYhmEYhmEYhmmBsLOYYRiGYRiGYRiGYRiGYRiGYRiGYRimBaLyCLzfGYZhGIZhGIZhGIZhGIZhGIZhGIZhmBYCzyxmGIZhGIZhGIZhGIZhGIZhGIZhGIZpgbCzmGEYhmEYhmEYhmEYhmEYhmEYhmEYpgXCzmKGYRiGYRiGYRiGYRiGYRiGYRiGYZgWCDuLGYZhGIZhGIZhGIZhGIZhGIZhGIZhWiDsLGYYhmEYhmEYhmEYhmEYhmEYhmEYhmmBsLOYYRiGYRiGYRiGYRiGYRiGYRiGYRimBcLOYoZhGIZhGIZhGIZhGIZhGIZhGIZhmBYIO4sZhmEYhmEYhmEYhmEYhmEYhmEYhmFaIOwsZhiGYRiGYRiGYRiGYRiGYRiGYRiGaYGws5hhGIZhGIZhGIZhGIZhGIZhGIZhGKYFws5ihmEYhmEYhmEYhmEYhmEYhmEYhmGYFgg7ixmGYRiGYRiGYRiGYRiGYRiG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WZbjhEIhnHf5NZ3Hj33eESecof+GF159I7o1ERER0QAUDiEsPYnXvgfvZw/Av+pNwNWklg+sXi5E/YnRZNKTDE+N6AhKREREvQGDxUREREREREREREREREREAxCDxUREREREREREREREREREAxCDxUREREREREREREREREREAxCDxUREREREREREREREREREAxCDxUREREREREREREREREREAxCDxUREREREREREREREREREAxCDxUREREREREREREREREREAxCDxUREREREREREREREREREAxCDxUREREREREREREREREREAxCDxUREREREREREREREREREAxCDxUREREREREREREREREREAxCDxUREREREREREREREREREAxCDxUREREREREREREREREREAxCDxUREREREREREREREREREAxCDxUREREREREREREREREREAxCDxUREREREREREREREREREAxCDxUREREREREREREREREREAxCDxUREREREREREREREREREAxCDxUREREREREREREREREREAxCDxUREREREREREREREREREAxCDxUREREREREREREREREREAxCDxUREREREREREREREREREAxCDxUREREREREREREREREREAxCDxUREREREREREREREREREAxCDxUREREREREREREREREREAxCDxUREREREREREREREREREAxCDxUREREREREREREREREREAxCDxUREREREREREREREREREAxCDxUREREREREREREREREREA5AhrETniYiIiIiIiIiIiIiIiIhogGDPYiIiIiIiIiIiIiIiIiKiAYjBYiIiIiIiIiIiIiIiIiKiAYjBYiIiIiIiIiIiIiIiIiKiAYjBYiIiIiIiIiIiIiIiIiKiAYjBYiIiIiIiIiIiIiIiIiKiAYjBYiIiIiIiIiIiIiIiIiKiAYjBYiIiIiIiIiIiIiIiIiKiAYjBYiIiIiIiIiIiIiIiIiKiAYjBYiIiIiIiIiIiIiIiIiKiAYjBYiIiIiIiIiIiIiIiIiKiAYjBYiIiIiIiIiIiIiIiIiKiAYjBYiIiIiIiIiIiIiIiIiKiAYjBYiIiIiIiIiIiIiIiIiKiAYjBYiIiIiIiIiIiIiIiIiKiAcgQVqLzOzVj9inROeqt5n/wenRu9zF9ez+mb//G9O3f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;" parent="1" vertex="1">
+ <mxGeometry x="113" y="1334" width="1078.5" height="409.95" as="geometry" />
+ </mxCell>
+ </UserObject>
+ <mxCell id="gruJ99u_ENbLo-s2ro46-262" value="&lt;span style=&quot;font-size: 19px;&quot;&gt;Hundreds of pre-built integrations&lt;/span&gt;" style="text;html=1;resizable=0;points=[];autosize=1;align=center;verticalAlign=top;spacingTop=-4;fontSize=19;" parent="1" vertex="1">
+ <mxGeometry x="116" y="1304" width="291" height="23" as="geometry" />
+ </mxCell>
+ </root>
+ </mxGraphModel>
+ </diagram>
+</mxfile>
diff --git a/docs/Add-more-charts-to-netdata.md b/docs/Add-more-charts-to-netdata.md
index 6090644e..35a89fba 100644
--- a/docs/Add-more-charts-to-netdata.md
+++ b/docs/Add-more-charts-to-netdata.md
@@ -5,9 +5,9 @@ custom_edit_url: https://github.com/netdata/netdata/edit/master/docs/Add-more-ch
# Add more charts to Netdata
-This file has been deprecated. Please see our [collectors docs](/collectors/README.md) for more information.
+This file has been deprecated. Please see our [collectors docs](https://github.com/netdata/netdata/blob/master/collectors/README.md) for more information.
## Available data collection modules
-See the [list of supported collectors](/collectors/COLLECTORS.md) to see all the sources Netdata can collect metrics
+See the [list of supported collectors](https://github.com/netdata/netdata/blob/master/collectors/COLLECTORS.md) to see all the sources Netdata can collect metrics
from.
diff --git a/docs/Running-behind-apache.md b/docs/Running-behind-apache.md
index 989c51fc..d152306f 100644
--- a/docs/Running-behind-apache.md
+++ b/docs/Running-behind-apache.md
@@ -1,6 +1,10 @@
<!--
title: "Netdata via apache's mod_proxy"
-custom_edit_url: https://github.com/netdata/netdata/edit/master/docs/Running-behind-apache.md
+custom_edit_url: "https://github.com/netdata/netdata/edit/master/docs/Running-behind-apache.md"
+sidebar_label: "Netdata via apache's mod_proxy"
+learn_status: "Published"
+learn_topic_type: "Tasks"
+learn_rel_path: "Setup/Expose local dashboard through proxy"
-->
# Netdata via apache's mod_proxy
@@ -35,7 +39,6 @@ Also, enable the rewrite module:
sudo a2enmod rewrite
```
----
## Netdata on an existing virtual host
@@ -314,7 +317,7 @@ or
bind to = ::1
```
----
+
You can also use a unix domain socket. This will also provide a faster route between apache and Netdata:
@@ -338,7 +341,7 @@ At the apache side, prepend the 2nd argument to `ProxyPass` with `unix:/tmp/netd
ProxyPass "/netdata/" "unix:/tmp/netdata.sock|http://localhost:19999/" connectiontimeout=5 timeout=30 keepalive=on
```
----
+
If your apache server is not on localhost, you can set:
@@ -350,7 +353,7 @@ If your apache server is not on localhost, you can set:
*note: Netdata v1.9+ support `allow connections from`*
-`allow connections from` accepts [Netdata simple patterns](/libnetdata/simple_pattern/README.md) to match against the connection IP address.
+`allow connections from` accepts [Netdata simple patterns](https://github.com/netdata/netdata/blob/master/libnetdata/simple_pattern/README.md) to match against the connection IP address.
## prevent the double access.log
diff --git a/docs/Running-behind-caddy.md b/docs/Running-behind-caddy.md
index 0282d075..d7d61375 100644
--- a/docs/Running-behind-caddy.md
+++ b/docs/Running-behind-caddy.md
@@ -1,6 +1,10 @@
<!--
title: "Netdata via Caddy"
-custom_edit_url: https://github.com/netdata/netdata/edit/master/docs/Running-behind-caddy.md
+custom_edit_url: "https://github.com/netdata/netdata/edit/master/docs/Running-behind-caddy.md"
+sidebar_label: "Netdata via Caddy"
+learn_status: "Published"
+learn_topic_type: "Tasks"
+learn_rel_path: "Setup/Expose local dashboard through proxy"
-->
# Netdata via Caddy
diff --git a/docs/Running-behind-h2o.md b/docs/Running-behind-h2o.md
index c49e4e16..8a1e22b2 100644
--- a/docs/Running-behind-h2o.md
+++ b/docs/Running-behind-h2o.md
@@ -1,6 +1,10 @@
<!--
title: "Running Netdata behind H2O"
-custom_edit_url: https://github.com/netdata/netdata/edit/master/docs/Running-behind-h2o.md
+custom_edit_url: "https://github.com/netdata/netdata/edit/master/docs/Running-behind-h2o.md"
+sidebar_label: "Running Netdata behind H2O"
+learn_status: "Published"
+learn_topic_type: "Tasks"
+learn_rel_path: "Setup/Expose local dashboard through proxy"
-->
# Running Netdata behind H2O
@@ -101,7 +105,7 @@ Using the above, you access Netdata on the backend servers, like this:
### Encrypt the communication between H2O and Netdata
-In case Netdata's web server has been [configured to use TLS](/web/server/README.md#enabling-tls-support), it is
+In case Netdata's web server has been [configured to use TLS](https://github.com/netdata/netdata/blob/master/web/server/README.md#enabling-tls-support), it is
necessary to specify inside the H2O configuration that the final destination is using TLS. To do this, change the
`http://` on the `proxy.reverse.url` line in your H2O configuration with `https://`
@@ -142,7 +146,7 @@ If your H2O server is on `localhost`, you can use this to ensure external access
bind to = 127.0.0.1 ::1
```
----
+
You can also use a unix domain socket. This will provide faster communication between H2O and Netdata as well:
@@ -157,7 +161,7 @@ In the H2O configuration, use a line like the following to connect to Netdata vi
proxy.reverse.url http://[unix:/run/netdata/netdata.sock]
```
----
+
If your H2O server is not on localhost, you can set:
@@ -169,7 +173,7 @@ If your H2O server is not on localhost, you can set:
*note: Netdata v1.9+ support `allow connections from`*
-`allow connections from` accepts [Netdata simple patterns](/libnetdata/simple_pattern/README.md) to match against
+`allow connections from` accepts [Netdata simple patterns](https://github.com/netdata/netdata/blob/master/libnetdata/simple_pattern/README.md) to match against
the connection IP address.
## Prevent the double access.log
diff --git a/docs/Running-behind-haproxy.md b/docs/Running-behind-haproxy.md
index ee1790cf..f87eaa1f 100644
--- a/docs/Running-behind-haproxy.md
+++ b/docs/Running-behind-haproxy.md
@@ -1,6 +1,10 @@
<!--
title: "Netdata via HAProxy"
-custom_edit_url: https://github.com/netdata/netdata/edit/master/docs/Running-behind-haproxy.md
+custom_edit_url: "https://github.com/netdata/netdata/edit/master/docs/Running-behind-haproxy.md"
+sidebar_label: "Netdata via HAProxy"
+learn_status: "Published"
+learn_topic_type: "Tasks"
+learn_rel_path: "Setup/Expose local dashboard through proxy"
-->
# Netdata via HAProxy
diff --git a/docs/Running-behind-lighttpd.md b/docs/Running-behind-lighttpd.md
index 2623560e..6350b474 100644
--- a/docs/Running-behind-lighttpd.md
+++ b/docs/Running-behind-lighttpd.md
@@ -1,6 +1,10 @@
<!--
title: "Netdata via lighttpd v1.4.x"
-custom_edit_url: https://github.com/netdata/netdata/edit/master/docs/Running-behind-lighttpd.md
+custom_edit_url: "https://github.com/netdata/netdata/edit/master/docs/Running-behind-lighttpd.md"
+sidebar_label: "Netdata via lighttpd v1.4.x"
+learn_status: "Published"
+learn_topic_type: "Tasks"
+learn_rel_path: "Setup/Expose local dashboard through proxy"
-->
# Netdata via lighttpd v1.4.x
@@ -27,7 +31,7 @@ $SERVER["socket"] == ":19998" {
}
```
----
+
If the only thing the server is exposing via the web is Netdata (and thus no suburl rewriting required),
then you can get away with just
@@ -51,7 +55,7 @@ auth.require = ( "" => ( "method" => "digest",
other auth methods, and more info on htdigest, can be found in lighttpd's [mod_auth docs](http://redmine.lighttpd.net/projects/lighttpd/wiki/Docs_ModAuth).
----
+
It seems that lighttpd (or some versions of it), fail to proxy compressed web responses.
To solve this issue, disable web response compression in Netdata.
diff --git a/docs/Running-behind-nginx.md b/docs/Running-behind-nginx.md
index 0cb16309..a94f4058 100644
--- a/docs/Running-behind-nginx.md
+++ b/docs/Running-behind-nginx.md
@@ -1,6 +1,10 @@
<!--
title: "Running Netdata behind Nginx"
-custom_edit_url: https://github.com/netdata/netdata/edit/master/docs/Running-behind-nginx.md
+custom_edit_url: "https://github.com/netdata/netdata/edit/master/docs/Running-behind-nginx.md"
+sidebar_label: "Running Netdata behind Nginx"
+learn_status: "Published"
+learn_topic_type: "Tasks"
+learn_rel_path: "Setup/Expose local dashboard through proxy"
-->
# Running Netdata behind Nginx
@@ -169,7 +173,7 @@ Using the above, you access Netdata on the backend servers, like this:
### Encrypt the communication between Nginx and Netdata
-In case Netdata's web server has been [configured to use TLS](/web/server/README.md#enabling-tls-support), it is
+In case Netdata's web server has been [configured to use TLS](https://github.com/netdata/netdata/blob/master/web/server/README.md#enabling-tls-support), it is
necessary to specify inside the Nginx configuration that the final destination is using TLS. To do this, please, append
the following parameters in your `nginx.conf`
@@ -212,7 +216,7 @@ If your Nginx is on `localhost`, you can use this to protect your Netdata:
bind to = 127.0.0.1 ::1
```
----
+
You can also use a unix domain socket. This will also provide a faster route between Nginx and Netdata:
@@ -232,7 +236,6 @@ upstream backend {
}
```
----
If your Nginx server is not on localhost, you can set:
@@ -244,7 +247,7 @@ If your Nginx server is not on localhost, you can set:
*note: Netdata v1.9+ support `allow connections from`*
-`allow connections from` accepts [Netdata simple patterns](/libnetdata/simple_pattern/README.md) to match against the
+`allow connections from` accepts [Netdata simple patterns](https://github.com/netdata/netdata/blob/master/libnetdata/simple_pattern/README.md) to match against the
connection IP address.
## Prevent the double access.log
diff --git a/docs/agent-cloud.md b/docs/agent-cloud.md
index ed54325c..b5b99661 100644
--- a/docs/agent-cloud.md
+++ b/docs/agent-cloud.md
@@ -13,24 +13,24 @@ hosted web interface that gives you real-time visibility into your entire infras
There are two main ways to use your Agent(s) with Netdata Cloud. You can use both these methods simultaneously, or just
one, based on your needs:
-- Use Netdata Cloud's web interface for monitoring an entire infrastructure, with any number of Agents, in one
- centralized dashboard.
-- Use **Visited nodes** to quickly navigate between the dashboards of nodes you've recently visited.
+- Use Netdata Cloud's web interface for monitoring an entire infrastructure, with any number of Agents, in one
+ centralized dashboard.
+- Use **Visited nodes** to quickly navigate between the dashboards of nodes you've recently visited.
## Monitor an infrastructure with Netdata Cloud
We designed Netdata Cloud to help you see health and performance metrics, plus active alarms, in a single interface.
Here's what a small infrastructure might look like:
-![Animated GIF of Netdata
-Cloud](https://user-images.githubusercontent.com/1153921/80828986-1ebb3b00-8b9b-11ea-957f-2c8d0d009e44.gif)
+![Animated GIF of Netdata Cloud](https://user-images.githubusercontent.com/1153921/80828986-1ebb3b00-8b9b-11ea-957f-2c8d0d009e44.gif)
-[Read more about Netdata Cloud](https://learn.netdata.cloud/docs/cloud/) to better understand how it gives you real-time
+[Read more about Netdata Cloud](https://github.com/netdata/netdata/blob/master/docs/cloud/cloud.mdx) to better
+understand how it gives you real-time
visibility into your entire infrastructure, and why you might consider using it.
-Next, [get started in 5 minutes](https://learn.netdata.cloud/docs/cloud/get-started/), or read our [connection to Cloud
-reference](/claim/README.md) for a complete investigation of Cloud's security and encryption features, plus instructions
-for Docker containers.
+Next, [get started in 5 minutes](https://github.com/netdata/netdata/blob/master/docs/cloud/get-started.mdx), or read our
+[connection to Cloud reference](https://github.com/netdata/netdata/blob/master/claim/README.md) for a complete
+investigation of Cloud's security and encryption features, plus instructions for Docker containers.
## Navigate between dashboards with Visited nodes
@@ -46,15 +46,13 @@ Netdata Cloud account, sign in with your preferred method.
Cloud redirects you back to your node's dashboard, which is now connected to your Netdata Cloud account. You can now see
the Visited nodes menu, which is populated by a single node.
-![An Agent's dashboard with the Visited nodes
-menu](https://user-images.githubusercontent.com/1153921/80830383-b6ba2400-8b9d-11ea-9eb2-379c7eccd22f.png)
+![An Agent's dashboard with the Visited nodes menu](https://user-images.githubusercontent.com/1153921/80830383-b6ba2400-8b9d-11ea-9eb2-379c7eccd22f.png)
If you previously went through the Cloud onboarding process to create a Space and War Room, you will also see these in
the Visited Nodes menu. You can click on your Space or any of your War Rooms to navigate to Netdata Cloud and continue
monitoring your infrastructure from there.
-![A Agent's dashboard with the Visited nodes menu, plus Spaces and War
-Rooms](https://user-images.githubusercontent.com/1153921/80830382-b6218d80-8b9d-11ea-869c-1170b95eeb4a.png)
+![A Agent's dashboard with the Visited nodes menu, plus Spaces and War Rooms](https://user-images.githubusercontent.com/1153921/80830382-b6218d80-8b9d-11ea-869c-1170b95eeb4a.png)
To add more Agents to your Visited nodes menu, visit them and sign in again. This process connects that node to your
Cloud account and further populates the menu.
@@ -62,16 +60,19 @@ Cloud account and further populates the menu.
Once you've added more than one node, you can use the menu to switch between various dashboards without remembering IP
addresses or hostnames or saving bookmarks for every node you want to monitor.
-![Switching between dashboards with Visited
-nodes](https://user-images.githubusercontent.com/1153921/80831018-e158ac80-8b9e-11ea-882e-1d82cdc028cd.gif)
+![Switching between dashboards with Visited nodes](https://user-images.githubusercontent.com/1153921/80831018-e158ac80-8b9e-11ea-882e-1d82cdc028cd.gif)
## What's next?
The Agent-Cloud integration is highly adaptable to the needs of any infrastructure or user. If you want to learn more
about how you might want to use or configure Cloud, we recommend the following:
-- Get an overview of Cloud's features by reading [Cloud documentation](https://learn.netdata.cloud/docs/cloud/).
-- Follow the 5-minute [get started with Cloud](https://learn.netdata.cloud/docs/cloud/get-started/) guide to finish
- onboarding and connect your first nodes.
-- Better understand how agents connect securely to the Cloud with [connect agent to Cloud](/claim/README.md) and [Agent-Cloud
- link](/aclk/README.md) documentation.
+- Get an overview of Cloud's features by
+ reading [Cloud documentation](https://github.com/netdata/netdata/blob/master/docs/cloud/cloud.mdx).
+- Follow the
+ 5-minute [get started with Cloud](https://github.com/netdata/netdata/blob/master/docs/cloud/cloud.mdx)
+ guide to finish
+ onboarding and connect your first nodes.
+- Better understand how agents connect securely to the Cloud
+ with [connect agent to Cloud](https://github.com/netdata/netdata/blob/master/claim/README.md) and
+ [Agent-Cloud link](https://github.com/netdata/netdata/blob/master/aclk/README.md) documentation.
diff --git a/docs/anonymous-statistics.md b/docs/anonymous-statistics.md
index 99bd3dc7..13eb465c 100644
--- a/docs/anonymous-statistics.md
+++ b/docs/anonymous-statistics.md
@@ -20,7 +20,7 @@ We use the statistics gathered from this information for two purposes:
Netdata collects usage information via two different channels:
-- **Agent dashboard**: We use the [PostHog JavaScript integration](https://posthog.com/docs/integrations/js-integration) (with sensitive event attributes overwritten to be anonymized) to send product usage events when you access an [Agent's dashboard](/web/gui/README.md).
+- **Agent dashboard**: We use the [PostHog JavaScript integration](https://posthog.com/docs/integrations/js-integration) (with sensitive event attributes overwritten to be anonymized) to send product usage events when you access an [Agent's dashboard](https://github.com/netdata/netdata/blob/master/web/gui/README.md).
- **Agent backend**: The `netdata` daemon executes the [`anonymous-statistics.sh`](https://github.com/netdata/netdata/blob/6469cf92724644f5facf343e4bdd76ac0551a418/daemon/anonymous-statistics.sh.in) script when Netdata starts, stops cleanly, or fails.
You can opt-out from sending anonymous statistics to Netdata through three different [opt-out mechanisms](#opt-out).
@@ -65,7 +65,7 @@ Starting with v1.21, we additionally collect information about:
- Failures to build the dependencies required to use Cloud features.
- Unavailability of Cloud features in an agent.
-- Failures to connect to the Cloud in case the [connection process](/claim/README.md) has been completed. This includes error codes
+- Failures to connect to the Cloud in case the [connection process](https://github.com/netdata/netdata/blob/master/claim/README.md) has been completed. This includes error codes
to inform the Netdata team about the reason why the connection failed.
To see exactly what and how is collected, you can review the script template `daemon/anonymous-statistics.sh.in`. The
@@ -82,13 +82,13 @@ installation, including manual, offline, and macOS installations. Create the fil
.opt-out-from-anonymous-statistics` from your Netdata configuration directory.
**Pass the option `--disable-telemetry` to any of the installer scripts in the [installation
-docs](/packaging/installer/README.md).** You can append this option during the initial installation or a manual
+docs](https://github.com/netdata/netdata/blob/master/packaging/installer/README.md).** You can append this option during the initial installation or a manual
update. You can also export the environment variable `DISABLE_TELEMETRY` with a non-zero or non-empty value
(e.g: `export DISABLE_TELEMETRY=1`).
When using Docker, **set your `DISABLE_TELEMETRY` environment variable to `1`.** You can set this variable with the following
command: `export DISABLE_TELEMETRY=1`. When creating a container using Netdata's [Docker
-image](/packaging/docker/README.md#create-a-new-netdata-agent-container) for the first time, this variable will disable
+image](https://github.com/netdata/netdata/blob/master/packaging/docker/README.md#create-a-new-netdata-agent-container) for the first time, this variable will disable
the anonymous statistics script inside of the container.
Each of these opt-out processes does the following:
diff --git a/docs/cloud/alerts-notifications/add-discord-notification.md b/docs/cloud/alerts-notifications/add-discord-notification.md
new file mode 100644
index 00000000..386e6035
--- /dev/null
+++ b/docs/cloud/alerts-notifications/add-discord-notification.md
@@ -0,0 +1,59 @@
+<!--
+title: "Add Discord notification configuration"
+sidebar_label: "Add Discord notification configuration"
+custom_edit_url: "https://github.com/netdata/netdata/blob/master/docs/cloud/alerts-notifications/add-discord-notification-configuration.md"
+sidebar_position: "1"
+learn_status: "Published"
+learn_topic_type: "Tasks"
+learn_rel_path: "Operations/Alerts"
+learn_docs_purpose: "Instructions on how to add notification configuration for Discord"
+-->
+
+From the Netdata Cloud UI, you can manage your space's notification settings and enable the configuration to deliver notifications on Discord.
+
+#### Prerequisites
+
+To enable Discord notifications you need:
+
+- A Netdata Cloud account
+- Access to the space as an **administrator**
+- Have a Discord server able to receive webhook integrations. For mode details check [how to configure this on Discord](#settings-on-discord)
+
+#### Steps
+
+1. Click on the **Space settings** cog (located above your profile icon)
+1. Click on the **Notification** tab
+1. Click on the **+ Add configuration** button (near the top-right corner of your screen)
+1. On the **Discord** card click on **+ Add**
+1. A modal will be presented to you to enter the required details to enable the configuration:
+ 1. **Notification settings** are Netdata specific settings
+ - Configuration name - you can optionally provide a name for your configuration you can easily refer to it
+ - Rooms - by specifying a list of Rooms you are select to which nodes or areas of your infrastructure you want to be notified using this configuration
+ - Notification - you specify which notifications you want to be notified using this configuration: All Alerts and unreachable, All Alerts, Critical only
+ 1. **Integration configuration** are the specific notification integration required settings, which vary by notification method. For Discord:
+ - Define the type channel you want to send notifications to: **Text channel** or **Forum channel**
+ - Webhook URL - URL provided on Discord for the channel you want to receive your notifications. For more details check [how to configure this on Discord](#settings-on-discord)
+ - Thread name - if the Discord channel is a **Forum channel** you will need to provide the thread name as well
+
+#### Settings on Discord
+
+#### Enable webhook integrations on Discord server
+
+To enable the webhook integrations on Discord you need:
+1. Go to *Integrations** under your **Server Settings
+
+ ![image](https://user-images.githubusercontent.com/82235632/214091719-89372894-d67f-4ec5-98d0-57c7d4256ebf.png)
+
+1. **Create Webhook** or **View Webhooks** if you already have some defined
+1. When you create a new webhook you specify: Name and Channel
+1. Once you have this configured you will need the Webhook URL to add your notification configuration on Netdata UI
+
+ ![image](https://user-images.githubusercontent.com/82235632/214092713-d16389e3-080f-4e1c-b150-c0fccbf4570e.png)
+
+For more details please read this article from Discord: [Intro to Webhooks](https://support.discord.com/hc/en-us/articles/228383668).
+
+#### Related topics
+
+- [Alerts Configuration](https://github.com/netdata/netdata/blob/master/health/README.md)
+- [Alert Notifications](https://github.com/netdata/netdata/blob/master/docs/cloud/alerts-notifications/notifications.mdx)
+- [Manage notification methods](https://github.com/netdata/netdata/blob/master/docs/cloud/alerts-notifications/manage-notification-methods.md) \ No newline at end of file
diff --git a/docs/cloud/alerts-notifications/add-pagerduty-notification-configuration.md b/docs/cloud/alerts-notifications/add-pagerduty-notification-configuration.md
new file mode 100644
index 00000000..6e47cfd9
--- /dev/null
+++ b/docs/cloud/alerts-notifications/add-pagerduty-notification-configuration.md
@@ -0,0 +1,60 @@
+<!--
+title: "Add PagerDuty notification configuration"
+sidebar_label: "Add PagerDuty notification configuration"
+custom_edit_url: "https://github.com/netdata/netdata/blob/master/docs/cloud/alerts-notifications/add-pagerduty-notification-configuration.md"
+sidebar_position: "1"
+learn_status: "Published"
+learn_topic_type: "Tasks"
+learn_rel_path: "Operations/Alerts"
+learn_docs_purpose: "Instructions on how to add notification configuration for PagerDuty"
+-->
+
+From the Cloud interface, you can manage your space's notification settings and from these you can add specific configuration to get notifications delivered on PagerDuty.
+
+#### Prerequisites
+
+To add PagerDuty notification configurations you need
+
+- A Cloud account
+- Access to the space as and **administrator**
+- Space will needs to be on **Business** plan or higher
+- Have a PagerDuty service to receive events, for mode details check [how to configure this on PagerDuty](#settings-on-pagerduty)
+
+#### Steps
+
+1. Click on the **Space settings** cog (located above your profile icon)
+1. Click on the **Notification** tab
+1. Click on the **+ Add configuration** button (near the top-right corner of your screen)
+1. On the **PagerDuty** card click on **+ Add**
+1. A modal will be presented to you to enter the required details to enable the configuration:
+ 1. **Notification settings** are Netdata specific settings
+ - Configuration name - you can optionally provide a name for your configuration you can easily refer to it
+ - Rooms - by specifying a list of Rooms you are select to which nodes or areas of your infrastructure you want to be notified using this configuration
+ - Notification - you specify which notifications you want to be notified using this configuration: All Alerts and unreachable, All Alerts, Critical only
+ 1. **Integration configuration** are the specific notification integration required settings, which vary by notification method. For PagerDuty:
+ - Integration Key - is a 32 character key provided by PagerDuty to receive events on your service. For more details check [how to configure this on PagerDuty](#settings-on-pagerduty)
+
+#### Settings on PagerDuty
+
+#### Enable webhook integrations on PagerDuty
+
+To enable the webhook integrations on PagerDuty you need:
+1. Create a service to receive events from your services directory page:
+
+ ![image](https://user-images.githubusercontent.com/2930882/214254148-03714f31-7943-4444-9b63-7b83c9daa025.png)
+
+1. At step 3, select `Events API V2` Integration:or **View Webhooks** if you already have some defined
+
+ ![image](https://user-images.githubusercontent.com/2930882/214254466-423cf493-037d-47bd-b9e6-fc894897f333.png)
+
+1. Once the service is created you will be redirected to its configuration page, where you can copy the **integration key**, that you will need need to add to your notification configuration on Netdata UI:
+
+
+ ![image](https://user-images.githubusercontent.com/2930882/214255916-0d2e53d5-87cc-408a-9f5b-0308a3262d5c.png)
+
+
+#### Related topics
+
+- [Alerts Configuration](https://github.com/netdata/netdata/blob/master/health/README.md)
+- [Alert Notifications](https://github.com/netdata/netdata/blob/master/docs/cloud/alerts-notifications/notifications.mdx)
+- [Manage notification methods](https://github.com/netdata/netdata/blob/master/docs/cloud/alerts-notifications/manage-notification-methods.md) \ No newline at end of file
diff --git a/docs/cloud/alerts-notifications/add-slack-notification-configuration.md b/docs/cloud/alerts-notifications/add-slack-notification-configuration.md
new file mode 100644
index 00000000..d8d6185f
--- /dev/null
+++ b/docs/cloud/alerts-notifications/add-slack-notification-configuration.md
@@ -0,0 +1,63 @@
+<!--
+title: "Add Slack notification configuration"
+sidebar_label: "Add Slack notification configuration"
+custom_edit_url: "https://github.com/netdata/netdata/blob/master/docs/cloud/alerts-notifications/add-slack-notification-configuration.md"
+sidebar_position: "1"
+learn_status: "Published"
+learn_topic_type: "Tasks"
+learn_rel_path: "Operations/Alerts"
+learn_docs_purpose: "Instructions on how to add notification configuration for Slack"
+-->
+
+From the Cloud interface, you can manage your space's notification settings and from these you can add specific configuration to get notifications delivered on Slack.
+
+#### Prerequisites
+
+To add discord notification configurations you need
+
+- A Netdata Cloud account
+- Access to the space as an **administrator**
+- Space will needs to be on **Business** plan or higher
+- Have a Slack app on your workspace to receive the webhooks, for mode details check [how to configure this on Slack](#settings-on-slack)
+
+#### Steps
+
+1. Click on the **Space settings** cog (located above your profile icon)
+1. Click on the **Notification** tab
+1. Click on the **+ Add configuration** button (near the top-right corner of your screen)
+1. On the **Slack** card click on **+ Add**
+1. A modal will be presented to you to enter the required details to enable the configuration:
+ 1. **Notification settings** are Netdata specific settings
+ - Configuration name - you can optionally provide a name for your configuration you can easily refer to it
+ - Rooms - by specifying a list of Rooms you are select to which nodes or areas of your infrastructure you want to be notified using this configuration
+ - Notification - you specify which notifications you want to be notified using this configuration: All Alerts and unreachable, All Alerts, Critical only
+ 1. **Integration configuration** are the specific notification integration required settings, which vary by notification method. For Slack:
+ - Webhook URL - URL provided on Slack for the channel you want to receive your notifications. For more details check [how to configure this on Slack](#settings-on-slack)
+
+#### Settings on Slack
+
+To enable the webhook integrations on Slack you need:
+1. Create an app to receive webhook integrations. Check [Create an app](https://api.slack.com/apps?new_app=1) from Slack documentation for further details
+1. Install the app on your workspace
+1. Configure Webhook URLs for your workspace
+ - On your app go to **Incoming Webhooks** and click on **activate incoming webhooks**
+
+ ![image](https://user-images.githubusercontent.com/2930882/214251948-486229bb-195b-499b-92e4-4be59a567a19.png)
+
+ - At the bottom of **Webhook URLs for Your Workspace** section you have **Add New Webhook to Workspace**
+ - After pressing that specify the channel where you want your notifications to be delivered
+
+ ![image](https://user-images.githubusercontent.com/82235632/214103532-95f9928d-d4d6-4172-9c24-a4ddd330e96d.png)
+
+ - Once completed copy the Webhook URL that you will need to add to your notification configuration on Netdata UI
+
+ ![image](https://user-images.githubusercontent.com/82235632/214104412-13aaeced-1b40-4894-85f6-9db0eb35c584.png)
+
+For more details please check Slacks's article [Incoming webhooks for Slack](https://slack.com/help/articles/115005265063-Incoming-webhooks-for-Slack).
+
+
+#### Related topics
+
+- [Alerts Configuration](https://github.com/netdata/netdata/blob/master/health/README.md)
+- [Alert Notifications](https://github.com/netdata/netdata/blob/master/docs/cloud/alerts-notifications/notifications.mdx)
+- [Manage notification methods](https://github.com/netdata/netdata/blob/master/docs/cloud/alerts-notifications/manage-notification-methods.md) \ No newline at end of file
diff --git a/docs/cloud/alerts-notifications/add-webhook-notification-configuration.md b/docs/cloud/alerts-notifications/add-webhook-notification-configuration.md
new file mode 100644
index 00000000..e6d04233
--- /dev/null
+++ b/docs/cloud/alerts-notifications/add-webhook-notification-configuration.md
@@ -0,0 +1,105 @@
+<!--
+title: "Add webhook notification configuration"
+sidebar_label: "Add webhook notification configuration"
+custom_edit_url: "https://github.com/netdata/netdata/blob/master/docs/cloud/alerts-notifications/add-webhook-notification-configuration.md"
+sidebar_position: "1"
+learn_status: "Published"
+learn_topic_type: "Tasks"
+learn_rel_path: "Operations/Alerts"
+learn_docs_purpose: "Instructions on how to add notification configuration for webhook"
+-->
+
+From the Cloud interface, you can manage your space's notification settings and from these you can add specific configuration to get notifications delivered on a webhook using a predefined schema.
+
+#### Prerequisites
+
+To add discord notification configurations you need
+
+- A Netdata Cloud account
+- Access to the space as an **administrator**
+- Space needs to be on **Pro** plan or higher
+- Have an app that allows you to receive webhooks following a predefined schema, for mode details check [how to create the webhook service](#webhook-service)
+
+#### Steps
+
+1. Click on the **Space settings** cog (located above your profile icon)
+1. Click on the **Notification** tab
+1. Click on the **+ Add configuration** button (near the top-right corner of your screen)
+1. On the **webhook** card click on **+ Add**
+1. A modal will be presented to you to enter the required details to enable the configuration:
+ 1. **Notification settings** are Netdata specific settings
+ - Configuration name - you can optionally provide a name for your configuration you can easily refer to it
+ - Rooms - by specifying a list of Rooms you are select to which nodes or areas of your infrastructure you want to be notified using this configuration
+ - Notification - you specify which notifications you want to be notified using this configuration: All Alerts and unreachable, All Alerts, Critical only
+ 1. **Integration configuration** are the specific notification integration required settings, which vary by notification method. For webhook:
+ - Webhook URL - webhook URL is the url of the service that Netdata will send notifications to. In order to keep the communication secured, we only accept HTTPS urls. Check [how to create the webhook service](#webhook-service).
+ - Extra headers - these are optional key-value pairs that you can set to be included in the HTTP requests sent to the webhook URL. For mode details check [Extra headers](#extra-headers)
+ - Authorization Mechanism - Netdata webhook integration supports 3 different authorization mechanisms. For mode details check [Authorization mechanism](#authorization-mechanism):
+ - Mutual TLS (recommended) - default authentication mechanism used if no other method is selected.
+ - Basic - the client sends a request with an Authorization header that includes a base64-encoded string in the format **username:password**. These will settings will be required inputs.
+ - Bearer - the client sends a request with an Authorization header that includes a **bearer token**. This setting will be a required input.
+
+#### Webhook service
+
+A webhook integration allows your application to receive real-time alerts from Netdata by sending HTTP requests to a specified URL. In this document, we'll go over the steps to set up a generic webhook integration, including adding headers, and implementing different types of authorization mechanisms.
+
+##### Netdata webhook integration
+
+A webhook integration is a way for one service to notify another service about events that occur within it. This is done by sending an HTTP POST request to a specified URL (known as the "webhook URL") when an event occurs.
+
+Netdata webhook integration service will send alert notifications to the destination service as soon as they are detected.
+
+The notification content sent to the destination service will be a JSON object having these properties:
+
+| field | type | description |
+| :-- | :-- | :-- |
+| message | string | A summary message of the alert. |
+| alarm | string | The alarm the notification is about. |
+| info | string | Additional info related with the alert. |
+| chart | string | The chart associated with the alert. |
+| context | string | The chart context. |
+| space | string | The space where the node that raised the alert is assigned. |
+| family | string | Context family. |
+| class | string | Classification of the alert, e.g. "Error". |
+| severity | string | Alert severity, can be one of "warning", "critical" or "clear". |
+| date | string | Date of the alert in ISO8601 format. |
+| duration | string | Duration the alert has been raised. |
+| critical_count | integer | umber of critical alerts currently existing on the same node. |
+| warning_count | integer | Number of warning alerts currently existing on the same node. |
+| alarm_url | string | Netdata Cloud URL for this alarm. |
+
+##### Extra headers
+
+When setting up a webhook integration, the user can specify a set of headers to be included in the HTTP requests sent to the webhook URL.
+
+By default, the following headers will be sent in the HTTP request
+
+| **Header** | **Value** |
+|:-------------------------------:|-----------------------------|
+| Content-Type | application/json |
+
+##### Authorization mechanism
+
+Netdata webhook integration supports 3 different authorization mechanisms:
+
+1. Mutual TLS (recommended)
+
+In mutual Transport Layer Security (mTLS) authorization, the client and the server authenticate each other using X.509 certificates. This ensures that the client is connecting to the intended server, and that the server is only accepting connections from authorized clients.
+
+To take advantage of mutual TLS, you can configure your server to verify Netdata's client certificate. To do that you need to download our [CA certificate file](http://localhost) and configure your server to use it as the
+
+This is the default authentication mechanism used if no other method is selected.
+
+2. Basic
+
+In basic authorization, the client sends a request with an Authorization header that includes a base64-encoded string in the format username:password. The server then uses this information to authenticate the client. If this authentication method is selected, the user can set the user and password that will be used when connecting to the destination service.
+
+3. Bearer
+
+In bearer token authorization, the client sends a request with an Authorization header that includes a bearer token. The server then uses this token to authenticate the client. Bearer tokens are typically generated by an authentication service, and are passed to the client after a successful authentication. If this method is selected, the user can set the token to be used for connecting to the destination service.
+
+#### Related topics
+
+- [Alerts Configuration](https://github.com/netdata/netdata/blob/master/health/README.md)
+- [Alert Notifications](https://github.com/netdata/netdata/blob/master/docs/cloud/alerts-notifications/notifications.mdx)
+- [Manage notification methods](https://github.com/netdata/netdata/blob/master/docs/cloud/alerts-notifications/manage-notification-methods.md)
diff --git a/docs/cloud/alerts-notifications/manage-notification-methods.md b/docs/cloud/alerts-notifications/manage-notification-methods.md
new file mode 100644
index 00000000..115aaae7
--- /dev/null
+++ b/docs/cloud/alerts-notifications/manage-notification-methods.md
@@ -0,0 +1,88 @@
+<!--
+title: "Manage notification methods"
+sidebar_label: "Manage notification methods"
+custom_edit_url: "https://github.com/netdata/netdata/blob/master/docs/cloud/alerts-notifications/manage-notification-methods.md"
+learn_status: "Published"
+learn_topic_type: "Tasks"
+learn_rel_path: "Operations/Alerts"
+learn_docs_purpose: "Instructions on how to manage notification methods"
+-->
+
+From the Cloud interface, you can manage your space's notification settings as well as allow users to personalize their notifications setting
+
+### Manage space notification settings
+
+#### Prerequisites
+
+To manage space notification settings, you will need the following:
+
+- A Netdata Cloud account
+- Access to the space as an **administrator**
+
+#### Available actions per notification methods based on service level
+
+| **Action** | **Personal service level** | **System service level** |
+| :- | :-: | :-: |
+| Enable / Disable | X | X |
+| Edit | | X | |
+| Delete | X | X |
+| Add multiple configurations for same method | | X |
+
+Notes:
+* For Netadata provided ones you can't delete the existing notification method configuration.
+* Enable, Edit and Add actions over specific notification methods will only be allowed if your plan has access to those ([service classification](https://github.com/netdata/netdata/blob/master/docs/cloud/alerts-notifications/notifications.mdx#service-classification))
+
+#### Steps
+
+1. Click on the **Space settings** cog (located above your profile icon)
+1. Click on the **Notification** tab
+1. You will be presented with a table of the configured notification methods for the space. You will be able to:
+ 1. **Add a new** notification method configuration.
+ - Choose the service from the list of the available ones, you'll may see a list of unavailable options if your plan doesn't allow some of them (you will see on the
+ card the plan level that allows a specific service)
+ - You can optionally provide a name for the configuration so you can easily refer to what it
+ - Define filtering criteria. To which Rooms will this apply? What notifications I want to receive? (All Alerts and unreachable, All Alerts, Critical only)
+ - Depending on the service different inputs will be present, please note that there are mandatory and optional inputs
+ - If you doubts on how to configure the service you can find a link at the top of the modal that takes you to the specific documentation page to help you
+ 1. **Edit an existing** notification method configuration. Personal level ones can't be edited here, see [Manage user notification settings](#manage-user-notification-settings). You will be able to change:
+ - The name provided for it
+ - Filtering criteria
+ - Service specific inputs
+ 1. **Enable/Disable** a given notification method configuration.
+ - Use the toggle to enable or disable the notification method configuration
+ 1. **Delete an existing** notification method configuartion. Netdata provided ones can't be deleted, e.g. Email
+ - Use the trash icon to delete your configuration
+
+### Manage user notification settings
+
+#### Prerequisites
+
+To manage user specific notification settings, you will need the following:
+
+- A Cloud account
+- Have access to, at least, a space
+
+Note: If an administrator has disabled a Personal [service level](https://github.com/netdata/netdata/blob/master/docs/cloud/alerts-notifications/notifications.md#service-level) notification method this will override any user specific setting.
+
+#### Steps
+
+1. Click on the **User notification settings** shortcut on top of the help button
+1. You are presented with:
+ - The Personal [service level](https://github.com/netdata/netdata/blob/master/docs/cloud/alerts-notifications/notifications.md#service-level) notification methods you can manage
+ - The list spaces and rooms inside those where you have access to
+ - If you're an administrator, Manager or Troubleshooter you'll also see the Rooms from a space you don't have access to on **All Rooms** tab and you can activate notifications for them by joining the room
+1. On this modal you will be able to:
+ 1. **Enable/Disable** the notification method for you, this applies accross all spaces and rooms
+ - Use the the toggle enable or disable the notification method
+ 1. **Define what notifications you want** to per space/room: All Alerts and unreachable, All Alerts, Critical only or No notifications
+ 1. **Activate notifications** for a room you aren't a member of
+ - From the **All Rooms** tab click on the Join button for the room(s) you want
+
+#### Related topics
+
+- [Alert Notifications](https://github.com/netdata/netdata/blob/master/docs/cloud/alerts-notifications/notifications.mdx)
+- [Alerts Configuration](https://github.com/netdata/netdata/blob/master/health/README.md)
+- [Add webhook notification configuration](https://github.com/netdata/netdata/blob/master/docs/cloud/alerts-notifications/add-webhook-notification-configuration.md)
+- [Add Discord notification configuration](https://github.com/netdata/netdata/blob/master/docs/cloud/alerts-notifications/add-discord-notification-configuration.md)
+- [Add Slack notification configuration](https://github.com/netdata/netdata/blob/master/docs/cloud/alerts-notifications/add-slack-notification-configuration.md)
+- [Add PagerDuty notification configuration](https://github.com/netdata/netdata/blob/master/docs/cloud/alerts-notifications/add-pagerduty-notification-configuration.md)
diff --git a/docs/cloud/alerts-notifications/notifications.mdx b/docs/cloud/alerts-notifications/notifications.mdx
new file mode 100644
index 00000000..e594606e
--- /dev/null
+++ b/docs/cloud/alerts-notifications/notifications.mdx
@@ -0,0 +1,155 @@
+---
+title: "Alert notifications"
+description: >-
+ "Configure Netdata Cloud to send notifications to your team whenever any node on your infrastructure
+ triggers a pre-configured or custom alert threshold."
+custom_edit_url: "https://github.com/netdata/netdata/blob/master/docs/cloud/alerts-notifications/notifications.mdx"
+sidebar_label: "Alert notifications"
+learn_status: "Published"
+learn_topic_type: "Tasks"
+learn_rel_path: "Operations/Alerts"
+---
+
+import Callout from '@site/src/components/Callout'
+
+Netdata Cloud can send centralized alert notifications to your team whenever a node enters a warning, critical, or
+unreachable state. By enabling notifications, you ensure no alert, on any node in your infrastructure, goes unnoticed by
+you or your team.
+
+Having this information centralized helps you:
+* Have a clear view of the health across your infrastructure, [seeing all a alerts in one place](https://github.com/netdata/netdata/blob/master/docs/cloud/alerts-notifications/view-active-alerts.mdx)
+* Easily [setup your alert notification process](https://github.com/netdata/netdata/blob/master/docs/cloud/alerts-notifications/manage-notification-methods.md):
+methods to use and where to use them, filtering rules, etc.
+* Quickly troubleshoot using [Metric Correlations](https://github.com/netdata/netdata/blob/master/docs/cloud/insights/metrics-correlations.md)
+or [Anomaly Advisor](https://github.com/netdata/netdata/blob/master/docs/cloud/insights/anomaly-advisor.mdx)
+
+If a node is getting disconnected often or has many alerts, we protect you and your team from alert fatigue by sending
+you a flood protection notification. Getting one of these notifications is a good signal of health or performance issues
+on that node.
+
+Admins must enable alert notifications for their [Space(s)](https://github.com/netdata/netdata/blob/master/docs/cloud/alerts-notifications/manage-notification-methods.md#manage-space-notification-settings). All users in a
+Space can then personalize their notifications settings from within their [account
+menu](https://github.com/netdata/netdata/blob/master/docs/cloud/alerts-notifications/#manage-user-notification-settings).
+
+<Callout type="notice">
+
+Centralized alert notifications from Netdata Cloud is a independent process from [notifications from
+Netdata](https://github.com/netdata/netdata/blob/master/docs/monitor/enable-notifications.md). You can enable one or the other, or both, based on your needs. However,
+the alerts you see in Netdata Cloud are based on those streamed from your Netdata-monitoring nodes. If you want to tweak
+or add new alert that you see in Netdata Cloud, and receive via centralized alert notifications, you must
+[configure](https://github.com/netdata/netdata/blob/master/docs/monitor/configure-alarms.md) each node's alert watchdog.
+
+</Callout>
+
+### Alert notifications
+
+Netdata Cloud can send centralized alert notifications to your team whenever a node enters a warning, critical, or unreachable state. By enabling notifications,
+you ensure no alert, on any node in your infrastructure, goes unnoticed by you or your team.
+
+If a node is getting disconnected often or has many alerts, we protect you and your team from alert fatigue by sending you a flood protection notification.
+Getting one of these notifications is a good signal of health or performance issues on that node.
+
+Alert notifications can be delivered through different methods, these can go from an Email sent from Netdata to the use of a 3rd party tool like PagerDuty.
+
+Notification methods are classified on two main attributes:
+* Service level: Personal or System
+* Service classification: Community or Business
+
+Only administrators are able to manage the space's alert notification settings.
+All users in a Space can personalize their notifications settings, for Personal service level notification methods, from within their profile menu.
+
+> ⚠️ Netdata Cloud supports different notification methods and their availability will depend on the plan you are at.
+> For more details check [Service classification](#service-classification) or [netdata.cloud/pricing](https://www.netdata.cloud/pricing).
+
+#### Service level
+
+##### Personal
+
+The notifications methods classified as **Personal** are what we consider generic, meaning that these can't have specific rules for them set by the administrators.
+
+These notifications are sent to the destination of the channel which is a user-specific attribute, e.g. user's e-mail, and the users are the ones that will then be able to
+manage what specific configurations they want for the Space / Room(s) and the desired Notification level, they can achieve this from their User Profile page under
+**Notifications**.
+
+One example of such a notification method is the E-mail.
+
+##### System
+
+For **System** notification methods, the destination of the channel will be a target that usually isn't specific to a single user, e.g. slack channel.
+
+These notification methods allow for fine-grain rule settings to be done by administrators and more than one configuration can exist for them since. You can specify
+different targets depending on Rooms or Notification level settings.
+
+Some examples of such notification methods are: Webhook, PagerDuty, slack.
+
+#### Service classification
+
+##### Community
+
+Notification methods classified as Community can be used by everyone independent on the plan your space is at.
+These are: Email and discord
+
+##### Pro
+
+Notification methods classified as Pro are only available for **Pro** and **Business** plans
+These are: webhook
+
+##### Business
+
+Notification methods classified as Business are only available for **Business** plans
+These are: PagerDuty, slack
+
+## Flood protection
+
+If a node has too many state changes like firing too many alerts or going from reachable to unreachable, Netdata Cloud
+enables flood protection. As long as a node is in flood protection mode, Netdata Cloud does not send notifications about
+this node. Even with flood protection active, it is possible to access the node directly, either via Netdata Cloud or
+the local Agent dashboard at `http://NODE:19999`.
+
+## Anatomy of an alert notification
+
+Email alarm notifications show the following information:
+
+- The Space's name
+- The node's name
+- Alarm status: critical, warning, cleared
+- Previous alarm status
+- Time at which the alarm triggered
+- Chart context that triggered the alarm
+- Name and information about the triggered alarm
+- Alarm value
+- Total number of warning and critical alerts on that node
+- Threshold for triggering the given alarm state
+- Calculation or database lookups that Netdata uses to compute the value
+- Source of the alarm, including which file you can edit to configure this alarm on an individual node
+
+Email notifications also feature a **Go to Node** button, which takes you directly to the offending chart for that node
+within Cloud's embedded dashboards.
+
+Here's an example email notification for the `ram_available` chart, which is in a critical state:
+
+![Screenshot of an alarm notification email from Netdata Cloud](https://user-images.githubusercontent.com/1153921/87461878-e933c480-c5c3-11ea-870b-affdb0801854.png)
+
+## What's next?
+
+Netdata Cloud's alarm notifications feature leverages the alarms configuration on each node in your infrastructure. If
+you'd like to tweak any of these alarms, or even add new ones based on your needs, read our [health
+quickstart](https://github.com/netdata/netdata/blob/master/docs/monitor/configure-alarms.md).
+
+You can also [view active alarms](https://github.com/netdata/netdata/blob/master/docs/cloud/alerts-notifications/view-active-alerts.mdx) in Netdata Cloud for an instant
+visualization of the health of your infrastructure.
+
+### Related Topics
+
+#### **Related Concepts**
+- [Rooms](https://github.com/netdata/netdata/blob/master/docs/cloud/war-rooms.md)
+- [Metric Correlations](https://github.com/netdata/netdata/blob/master/docs/cloud/insights/metrics-correlations.md)
+- [Anomaly Advisor](https://github.com/netdata/netdata/blob/master/docs/cloud/insights/anomaly-advisor.mdx)
+
+#### Related Tasks
+- [View Active alarms](https://github.com/netdata/netdata/blob/master/docs/cloud/alerts-notifications/view-active-alerts.mdx)
+- [Manage notification methods](https://github.com/netdata/netdata/blob/master/docs/cloud/alerts-notifications/manage-notification-methods.md)
+- [Add webhook notification configuration](https://github.com/netdata/netdata/blob/master/docs/cloud/alerts-notifications/add-webhook-notification-configuration.md)
+- [Add Discord notification configuration](https://github.com/netdata/netdata/blob/master/docs/cloud/alerts-notifications/add-discord-notification-configuration.md)
+- [Add Slack notification configuration](https://github.com/netdata/netdata/blob/master/docs/cloud/alerts-notifications/add-slack-notification-configuration.md)
+- [Add PagerDuty notification configuration](https://github.com/netdata/netdata/blob/master/docs/cloud/alerts-notifications/add-pagerduty-notification-configuration.md)
diff --git a/docs/cloud/alerts-notifications/smartboard.mdx b/docs/cloud/alerts-notifications/smartboard.mdx
new file mode 100644
index 00000000..b9240ce4
--- /dev/null
+++ b/docs/cloud/alerts-notifications/smartboard.mdx
@@ -0,0 +1,46 @@
+---
+title: "Alerts smartboard"
+description: ""
+type: "reference"
+custom_edit_url: "https://github.com/netdata/netdata/blob/master/docs/cloud/alerts-notifications/smartboard.mdx"
+sidebar_label: "Alerts smartboard"
+learn_status: "Published"
+learn_topic_type: "Tasks"
+learn_rel_path: "Operations/Alerts"
+---
+
+The Alerts view gives you a high level of availability and performance information for every node you're
+monitoring with Netdata Cloud. We expect it to become the "home base" for many Netdata Cloud users who want to instantly
+understand what's going on with their infrastructure and exactly where issues might be.
+
+The Alerts view is available entirely for free to all users and for any number of nodes.
+
+## Alerts table and filtering
+
+The Alerts view shows all active alerts in your War Room, including the alert's name, the most recent value, a
+timestamp of when it became active, and the relevant node.
+
+You can use the checkboxes in the filter pane on the right side of the screen to filter the alerts displayed in the
+table
+by Status, Class, Type & Componenet, Role, Operating System, or Node.
+
+Click on any of the alert names to see the alert.
+
+## View active alerts
+
+In the `Active` subtab, you can see exactly how many **critical** and **warning** alerts are active across your nodes.
+
+## View configured alerts
+
+You can view all the configured alerts on all the agents that belong to a War Room in the `Alert Configurations` subtab.
+From within the Alerts view, you can click the `Alert Configurations` subtab to see a high level view of the states of
+the alerts on the nodes within this War Room and drill down to the node level where each alert is configured with their
+latest status.
+
+
+
+
+
+
+
+
diff --git a/docs/cloud/alerts-notifications/view-active-alerts.mdx b/docs/cloud/alerts-notifications/view-active-alerts.mdx
new file mode 100644
index 00000000..1035b682
--- /dev/null
+++ b/docs/cloud/alerts-notifications/view-active-alerts.mdx
@@ -0,0 +1,76 @@
+---
+title: "View active alerts"
+description: >-
+ "Track the health of your infrastructure in one place by taking advantage of the powerful health monitoring
+ watchdog running on every node."
+type: "how-to"
+custom_edit_url: "https://github.com/netdata/netdata/blob/master/docs/cloud/alerts-notifications/view-active-alerts.mdx"
+sidebar_label: "View active alerts"
+learn_status: "Published"
+learn_topic_type: "Tasks"
+learn_rel_path: "Operations/Alerts"
+---
+
+Netdata Cloud receives information about active alerts on individual nodes in your infrastructure and updates the
+interface based on those status changes.
+
+Netdata Cloud doesn't produce alerts itself but rather receives and aggregates alerts from each node in your
+infrastructure based on their configuration. Every node comes with hundreds of pre-configured alerts that have been
+tested by Netdata's community of DevOps engineers and SREs, but you may want to customize existing alerts or create new
+ones entirely.
+
+Read our doc on [health alerts](https://github.com/netdata/netdata/blob/master/docs/monitor/configure-alarms.md) to
+learn how to tweak existing alerts or create new
+health entities based on the specific needs of your infrastructure. By taking charge of alert configuration, you'll
+ensure Netdata Cloud always delivers the most relevant alerts about the well-being of your nodes.
+
+## View all active alerts
+
+The [Alerts Smartboard](https://github.com/netdata/netdata/blob/master/docs/cloud/alerts-notifications/smartboard.mdx)
+provides a high-level interface for viewing the number of critical or warning alerts and where they are in your
+infrastructure.
+
+![The Alerts Smartboard](https://user-images.githubusercontent.com/1153921/119025635-2fcb1b80-b959-11eb-9fdb-7f1a082f43c5.png)
+
+Click on the **Alerts** tab in any War Room to open the Smartboard. Alternatively, click on any of the alert badges in
+the [Nodes view](https://github.com/netdata/netdata/blob/master/docs/cloud/visualize/nodes.md) to jump to the Alerts
+Smartboard.
+
+From here, filter active alerts using the **critical** or **warning** boxes, or hover over a box in
+the [nodes map](https://github.com/netdata/netdata/blob/master/docs/cloud/alerts-notifications/smartboard.mdx#nodes-map)
+to see a
+popup node-specific alert information.
+
+## View alerts in context with charts
+
+If you click on any of the alerts, either in a nodes map popup or the alerts table, Netdata Cloud navigates you to the
+single-node dashboard and scrolls to the relevant chart. Netdata Cloud also draws a highlight and the value at the
+moment your node triggered this alert.
+
+![An alert in context with charts and dimensions](https://user-images.githubusercontent.com/1153921/119039593-4a0cf580-b969-11eb-840c-4ecb123df9f5.png)
+
+You can
+then [select this area](https://github.com/netdata/netdata/blob/master/docs/dashboard/interact-charts.mdx#select)
+with `Alt/⌘ + mouse selection` to highlight the alerted timeframe while you explore other charts for root cause
+analysis.
+
+Or, select the area and
+run [Metric Correlations](https://github.com/netdata/netdata/blob/master/docs/cloud/insights/metric-correlations.md) to
+filter the single-node
+dashboard to only those charts most likely to be connected to the alert.
+
+## What's next?
+
+Learn more about the features of the Smartboard in
+its [reference](https://github.com/netdata/netdata/blob/master/docs/cloud/alerts-notifications/smartboard.mdx)
+doc. To stay notified of active alerts,
+enable [centralized alert notifications](https://github.com/netdata/netdata/blob/master/docs/cloud/alerts-notifications/notifications.mdx)
+from Netdata Cloud.
+
+If you're through with setting up alerts, it might be time
+to [invite your team](https://github.com/netdata/netdata/blob/master/docs/cloud/manage/invite-your-team.md).
+
+Check out our recommendations on organizing and
+using [Spaces](https://github.com/netdata/netdata/blob/master/docs/cloud/spaces.md) and
+[War Rooms](https://github.com/netdata/netdata/blob/master/docs/cloud/war-rooms.md) to streamline your processes once
+you find an alert in Netdata Cloud.
diff --git a/docs/cloud/beta-architecture/new-architecture.md b/docs/cloud/beta-architecture/new-architecture.md
new file mode 100644
index 00000000..c51f08fb
--- /dev/null
+++ b/docs/cloud/beta-architecture/new-architecture.md
@@ -0,0 +1,36 @@
+---
+title: "Test the New Cloud Architecture"
+description: "Would you like to be the first to try our new architecture and provide feedback? If so, this guide will help you sign up for our beta testing group."
+custom_edit_url: "https://github.com/netdata/netdata/blob/master/docs/cloud/beta-architecture/new-architecture.md"
+---
+
+To enhance the stability and reliability of Netdata Cloud, we did extensive work on our backend, and we would like to give you the opportunity
+to be among the first users to try these changes to our Cloud architecture and provide feedback.
+
+The backend architecture changes should offer notable improvements in reliability and stability in Netdata Cloud,
+but more importantly, it allows us to develop new features and enhanced functionality, including features and enhancements
+that you have specifically requested. Features that will be developed on the new architecture include:
+
+- Parent/Child Cloud relationships
+- Alert logs
+- Alert management
+- Much more
+
+## Enabling the new architecture
+
+To enable the new architecture, first ensure that you have installed the latest Netdata version following
+[our guide](https://github.com/netdata/netdata/blob/master/docs/get-started.mdx). Then, you or your administrator will need to retrieve the Space IDs
+within Netdata Cloud by clicking `Manage Space` in the left pane, selecting the `Space` tab, and copying the value in the `Space Id` field.
+You can then send an email to [beta@Netdata.cloud](mailto:beta@netdata.cloud) requesting to be included in our beta testers, and include
+in the body of the email a list of Space IDs for any space you would like to have whitelisted for the update. If you received an email
+invitation, you can also just reply to the invitation with your Space IDs in the body of the reply.
+
+Feel free to send the Space IDs for multiple spaces to test the new infrastructure on each of them.
+
+## Reporting issues
+
+After you are set up with the new architecture changes, we ask that you report any issues you encounter in our
+[designated Discord channel](https://discord.gg/dGzdemHwHh). This feedback
+will help us ensure the highest performance of the new architecture and expedite the development and release
+of the aforementioned enhancements and features.
+
diff --git a/docs/cloud/cheatsheet.mdx b/docs/cloud/cheatsheet.mdx
new file mode 100644
index 00000000..c1d0a471
--- /dev/null
+++ b/docs/cloud/cheatsheet.mdx
@@ -0,0 +1,231 @@
+---
+title: "'Netdata management and configuration cheatsheet'"
+description: "'Connecting an Agent to the Cloud allows a Netdata Agent, running on a distributed node, to securely connect to Netdata Cloud via the encrypted Agent-Cloud link (ACLK).'"
+image: "/cheatsheet/cheatsheet-meta.png"
+sidebar_label: "Cheatsheet"
+custom_edit_url: "https://github.com/netdata/netdata/blob/master/docs/cloud/cheatsheet.mdx"
+part_of_learn: "True"
+learn_status: "Published"
+learn_topic_type: "Getting started"
+learn_rel_path: "Getting started"
+---
+
+import {
+ OneLineInstallWget,
+ OneLineInstallCurl,
+} from '@site/src/components/OneLineInstall/';
+
+Use our management &amp; configuration cheatsheet to simplify your interactions with Netdata, including configuration,
+using charts, managing the daemon, and more.
+
+## Install Netdata
+
+#### Install Netdata
+
+<OneLineInstallWget />
+
+Or, if you have cURL but not wget (such as on macOS):
+
+<OneLineInstallCurl />
+
+#### Claim a node to Netdata Cloud
+
+To do so, sign in to Netdata Cloud, click the `Claim Nodes` button, choose the `War Rooms` to add nodes to, then click `Copy` to copy the full script to your clipboard. Paste that into your node’s terminal and run it.
+
+## Metrics collection & retention
+
+You can tweak your settings in the netdata.conf file.
+📄 [Find your netdata.conf file](https://learn.netdata.cloud/guides/step-by-step/step-04#find-your-netdataconf-file)
+
+Open a new terminal and navigate to the netdata.conf file. Use the edit-config script to make changes: `sudo ./edit-config netdata.conf`
+
+The most popular settings to change are:
+
+#### Increase metrics retention (4GiB)
+
+```
+sudo ./edit-config netdata.conf
+```
+
+```
+[global]
+ dbengine multihost disk space = 4096
+```
+
+#### Reduce the collection frequency (every 5 seconds)
+
+```
+sudo ./edit-config netdata.conf
+```
+
+```
+[global]
+ update every = 5
+```
+
+#### Enable/disable plugins (groups of collectors)
+
+```
+sudo ./edit-config netdata.conf
+```
+
+```
+[plugins]
+ go.d = yes # enabled
+ node.d = no # disabled
+```
+
+#### Enable/disable specific collectors
+
+```
+sudo ./edit-config go.d.conf
+```
+
+> `Or python.d.conf, node.d.conf, edbpf.conf, and so on`.
+
+```
+modules:
+ activemq: no # disabled
+ bind: no # disabled
+ cockroachdb: yes # enabled
+```
+
+#### Edit a collector's config (example)
+
+```
+$ sudo ./edit-config go.d/mysql.conf
+$ sudo ./edit-config ebpf.conf
+$ sudo ./edit-config python.d/anomalies.conf
+```
+
+## Configuration
+
+#### The Netdata config directory: `/etc/netdata`
+
+> If you don't have such a directory:
+> 📄 [Find your netdata.conf file](https://learn.netdata.cloud/guides/step-by-step/step-04#find-your-netdataconf-file)
+> The cheatsheet assumes you’re running all commands from within the Netdata config directory!
+
+#### Edit Netdata's main config file: `$ sudo ./edit-config netdata.conf`
+
+#### Edit Netdata's other config files (examples):
+
+- `$ sudo ./edit-config apps_groups.conf`
+- `$ sudo ./edit-config ebpf.conf`
+- `$ sudo ./edit-config health.d/load.conf`
+- `$ sudo ./edit-config go.d/prometheus.conf`
+
+#### View the running Netdata configuration: `http://NODE:19999/netdata.conf`
+
+> Replace `NODE` with the IP address or hostname of your node. Often `localhost`.
+
+## Alarms & notifications
+
+#### Add a new alarm
+
+```
+sudo touch health.d/example-alarm.conf
+sudo ./edit-config health.d/example-alarm.conf
+```
+
+#### Configure a specific alarm
+
+```
+sudo ./edit-config health.d/example-alarm.conf
+```
+
+#### Silence a specific alarm
+
+```
+sudo ./edit-config health.d/example-alarm.conf
+ to: silent
+```
+
+#### Disable alarms and notifications
+
+```
+[health]
+ enabled = no
+```
+
+> After any change, reload the Netdata health configuration
+
+```
+netdatacli reload-health
+```
+
+or if that command doesn't work on your installation, use:
+
+```
+killall -USR2 netdata
+```
+
+## Manage the daemon
+
+| Intent | Action |
+| :-------------------------- | --------------------------------------------------------------------: |
+| Start Netdata | `$ sudo systemctl start netdata` |
+| Stop Netdata | `$ sudo systemctl stop netdata` |
+| Restart Netdata | `$ sudo systemctl restart netdata` |
+| Reload health configuration | `$ sudo netdatacli reload-health` <br></br> `$ killall -USR2 netdata` |
+| View error logs | `less /var/log/netdata/error.log` |
+
+## See metrics and dashboards
+
+#### Netdata Cloud: `https://app.netdata.cloud`
+
+#### Local dashboard: `https://NODE:19999`
+
+> Replace `NODE` with the IP address or hostname of your node. Often `localhost`.
+
+#### Access the Netdata API: `http://NODE:19999/api/v1/info`
+
+## Interact with charts
+
+| Intent | Action |
+| -------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: |
+| Stop a chart from updating | `click` |
+| Zoom | **Cloud** <br/> use the `zoom in` and `zoom out` buttons on any chart (upper right corner) <br/><br/> **Agent**<br/>`SHIFT` or `ALT` + `mouse scrollwheel` <br/> `SHIFT` or `ALT` + `two-finger pinch` (touchscreen) <br/> `SHIFT` or `ALT` + `two-finger scroll` (touchscreen) |
+| Zoom to a specific timeframe | **Cloud**<br/>use the `select and zoom` button on any chart and then do a `mouse selection` <br/><br/> **Agent**<br/>`SHIFT` + `mouse selection` |
+| Pan forward or back in time | `click` & `drag` <br/> `touch` & `drag` (touchpad/touchscreen) |
+| Select a certain timeframe | `ALT` + `mouse selection` <br/> WIP need to evaluate this `command?` + `mouse selection` (macOS) |
+| Reset to default auto refreshing state | `double click` |
+
+## Dashboards
+
+#### Disable the local dashboard
+
+Use the `edit-config` script to edit the `netdata.conf` file.
+
+```
+[web]
+mode = none
+```
+
+#### Change the port Netdata listens to (port 39999)
+
+```
+[web]
+default port = 39999
+```
+
+#### Opt out from anonymous statistics
+
+```
+sudo touch .opt-out-from-anonymous-statistics
+```
+
+## Understanding the dashboard
+
+**Charts**: A visualization displaying one or more collected/calculated metrics in a time series. Charts are generated
+by collectors.
+
+**Dimensions**: Any value shown on a chart, which can be raw or calculated values, such as percentages, averages,
+minimums, maximums, and more.
+
+**Families**: One instance of a monitored hardware or software resource that needs to be monitored and displayed
+separately from similar instances. Example, disks named
+**sda**, **sdb**, **sdc**, and so on.
+
+**Contexts**: A grouping of charts based on the types of metrics collected and visualized.
+**disk.io**, **disk.ops**, and **disk.backlog** are all contexts.
diff --git a/docs/cloud/cloud.mdx b/docs/cloud/cloud.mdx
new file mode 100644
index 00000000..764ba0e8
--- /dev/null
+++ b/docs/cloud/cloud.mdx
@@ -0,0 +1,74 @@
+---
+title: "Netdata Cloud docs"
+description: "Netdata Cloud is real-time visibility for entire infrastructures. View key metrics, insightful charts, and active alarms from all your nodes."
+custom_edit_url: "https://github.com/netdata/learn/blob/master/docs/cloud.mdx"
+---
+
+import { Grid, Box, BoxList, BoxListItem } from '@site/src/components/Grid/'
+import { RiExternalLinkLine } from 'react-icons/ri'
+
+This is the documentation for the Netdata Cloud web application, which works in parallel with the open-source Netdata
+monitoring agent to help you monitor your entire infrastructure [for free <RiExternalLinkLine className="inline-block"
+/>](https://netdata.cloud/pricing/) in real time and troubleshoot problems that threaten the health of your
+nodes before they occur.
+
+Netdata Cloud requires the open-source [Netdata](/docs/) monitoring agent, which is the basis for the metrics,
+visualizations, and alarms that you'll find in Netdata Cloud. Every time you view a node in Netdata Cloud, its metrics
+and metadata are streamed to Netdata Cloud, then proxied to your browser, with an infrastructure that ensures [data
+privacy <RiExternalLinkLine className="inline-block" />](https://netdata.cloud/privacy/).
+
+
+Read [_What is Netdata?_](https://github.com/netdata/netdata/blob/master/docs/overview/what-is-netdata.md) for details about how Netdata and Netdata Cloud work together
+and how they're different from other monitoring solutions, or the
+[FAQ <RiExternalLinkLine className="inline-block" />](https://community.netdata.cloud/tags/c/general/29/faq) for answers to common questions.
+
+<Grid columns="1" className="mb-16">
+ <Box
+ to="/docs/cloud/get-started"
+ title="Get started with Netdata Cloud"
+ cta="Go"
+ image={true}>
+ Ready to get real-time visibility into your entire infrastructure? This guide will help you get started on Netdata Cloud, from signing in for a free account to connecting your nodes.
+ </Box>
+</Grid>
+
+## Learn about Netdata Cloud's features
+
+<Grid columns="2">
+ <Box
+ title="Spaces and War Rooms">
+ <BoxList>
+ <BoxListItem to="/docs/cloud/spaces" title="Spaces" />
+ <BoxListItem to="/docs/cloud/war-rooms" title="War Rooms" />
+ </BoxList>
+ </Box>
+ <Box
+ title="Dashboards">
+ <BoxList>
+ <BoxListItem to="/docs/cloud/visualize/overview" title="Overview" />
+ <BoxListItem to="/docs/cloud/visualize/nodes" title="Nodes view" />
+ <BoxListItem to="/docs/cloud/visualize/kubernetes" title="Kubernetes" />
+ <BoxListItem to="/docs/cloud/visualize/dashboards" title="Create new dashboards" />
+ </BoxList>
+ </Box>
+ <Box
+ title="Alerts and notifications">
+ <BoxList>
+ <BoxListItem to="/docs/cloud/alerts-notifications/view-active-alerts" title="View active alerts" />
+ <BoxListItem to="/docs/cloud/alerts-notifications/smartboard" title="Alerts Smartboard" />
+ <BoxListItem to="/docs/cloud/alerts-notifications/notifications" title="Alert notifications" />
+ </BoxList>
+ </Box>
+ <Box
+ title="Troubleshooting with Netdata Cloud">
+ <BoxListItem to="/docs/cloud/insights/metric-correlations" title="Metric Correlations" />
+ </Box>
+ <Box
+ title="Management and settings">
+ <BoxList>
+ <BoxListItem to="/docs/cloud/manage/sign-in" title="Sign in with email, Google, or GitHub" />
+ <BoxListItem to="/docs/cloud/manage/invite-your-team" title="Invite your team" />
+ <BoxListItem to="/docs/cloud/manage/themes" title="Choose your Netdata Cloud theme" />
+ </BoxList>
+ </Box>
+</Grid>
diff --git a/docs/cloud/data-privacy.mdx b/docs/cloud/data-privacy.mdx
new file mode 100644
index 00000000..c99cff94
--- /dev/null
+++ b/docs/cloud/data-privacy.mdx
@@ -0,0 +1,39 @@
+---
+title: "Data privacy in the Netdata Cloud"
+description: "Keeping your data safe and secure is our priority.Netdata never stores your personal information in the Netdata Cloud."
+custom_edit_url: "https://github.com/netdata/netdata/blob/master/docs/cloud/data-privacy.mdx"
+sidebar_label: "Data privacy in the Netdata Cloud"
+learn_status: "Published"
+learn_topic_type: "Concepts"
+learn_rel_path: "Concepts"
+---
+
+[Data privacy](https://netdata.cloud/privacy/) is very important to us. We firmly believe that your data belongs to
+you. This is why **we don't store any metric data in Netdata Cloud**.
+
+Your local installations of the Netdata Agent form the basis for the Netdata Cloud. All the data that you see in the web browser when using Netdata Cloud, is actually streamed directly from the Netdata Agent to the Netdata Cloud dashboard.
+The data passes through our systems, but it isn't stored. You can learn more about [the Agent's security design](https://github.com/netdata/netdata/blob/master/docs/netdata-security.md) in the Agent documentation.
+
+However, to be able to offer the stunning visualizations and advanced functionality of Netdata Cloud, it does store a limited number of _metadata_.
+
+## Metadata
+
+Let's look at the metadata Netdata Cloud stores using the publicly available demo server `frankfurt.my-netdata.io`:
+
+- The email address you used to sign up/or sign in
+- For each node connected to your Spaces in Netdata Cloud:
+ - Hostname (as it appears in Netdata Cloud)
+ - Information shown in `/api/v1/info`. For example: [https://frankfurt.my-netdata.io/api/v1/info](https://frankfurt.my-netdata.io/api/v1/info).
+ - The chart metadata shown in `/api/v1/charts`. For example: [https://frankfurt.my-netdata.io/api/v1/info](https://frankfurt.my-netdata.io/api/v1/info).
+ - Alarm configurations shown in `/api/v1/alarms?all`. For example: [https://frankfurt.my-netdata.io/api/v1/alarms?all](https://frankfurt.my-netdata.io/api/v1/alarms?all).
+ - Active alarms shown in `/api/v1/alarms`. For example: [https://frankfurt.my-netdata.io/api/v1/alarms](https://frankfurt.my-netdata.io/api/v1/alarms).
+
+How we use them:
+
+- The data is stored in our production database on AWS. Some of it is also used in Google BigQuery, our data lake, for analytics purposes. These analytics are crucial for our product development process.
+- Email is used to identify users in regards to product use and to enrich our tools with product use, such as our CRM.
+- This data is only available to Netdata and never to a 3rd party.
+
+## Delete all personal data
+
+To remove all personal info we have about you (email and activities) you need to delete your cloud account by logging into https://app.netdata.cloud and accessing your profile, at the bottom left of your screen.
diff --git a/docs/cloud/get-started.mdx b/docs/cloud/get-started.mdx
new file mode 100644
index 00000000..b9f83af8
--- /dev/null
+++ b/docs/cloud/get-started.mdx
@@ -0,0 +1,133 @@
+---
+title: "Get started with Netdata Cloud"
+description: >-
+ "Ready to get real-time visibility into your entire infrastructure? This guide will help you get started on
+ Netdata Cloud."
+image: "/img/seo/cloud_get-started.png"
+custom_edit_url: "https://github.com/netdata/learn/blob/master/docs/cloud/get-started.mdx"
+---
+
+import Link from '@docusaurus/Link'
+import Callout from '@site/src/components/Callout'
+
+Ready to get real-time visibility into your entire infrastructure with Netdata Cloud? This guide will walk you through
+the onboarding process, such as setting up your Space and War Room and connecting your first nodes.
+
+## Before you start
+
+Before you get started with Netdata Cloud, you should have the open-source Netdata monitoring agent installed. See our
+[installation guide](https://github.com/netdata/netdata/blob/master/docs/get-started.mdx) for details.
+
+If you already have the Netdata agent running on your node(s), make sure to update it to v1.32 or higher. Read the
+[updating documentation](https://github.com/netdata/netdata/blob/master/packaging/installer/UPDATE.md) for information
+on how to update based on the method you used to install Netdata on that node.
+
+## Begin the onboarding process
+
+Get started by signing in to Netdata. Read
+the [sign in](https://github.com/netdata/netdata/blob/master/docs/cloud/manage/sign-in.mdx) doc for details on the
+authentication methods we use.
+
+<Link to="https://app.netdata.cloud" className="group">
+ <button className="relative text-text bg-gray-200 px-4 py-2 rounded">
+ <span className="z-10 relative font-semibold group-hover:text-gray-100">Sign in to Netdata</span>
+ <div className="opacity-0 group-hover:opacity-100 transition absolute z-0 inset-0 bg-gradient-to-r from-green to-green-lighter rounded"></div>
+ </button>
+</Link>
+
+Once signed in with your preferred method, a
+General [War Room](https://github.com/netdata/netdata/blob/master/docs/cloud/war-rooms.md) and
+a [Space](https://github.com/netdata/netdata/blob/master/docs/cloud/spaces.md)
+named for your login email are automatically created. You can configure more Spaces and War Rooms to help you you
+organize your team
+and the many systems that make up your infrastructure. For example, you can put product and infrastructure SRE teams in
+separate
+Spaces, and then use War Rooms to group nodes by their service (`nginx`), purpose (`webservers`), or physical
+location (`IAD`).
+
+Don't worry! You can always add more Spaces and War Rooms later if you decide to reorganize how you use Netdata Cloud.
+
+## Connect your nodes
+
+From within the created War Rooms, Netdata Cloud prompts you
+to [connect](https://github.com/netdata/netdata/blob/master/claim/README.md) your nodes to Netdata Cloud. Non-admin
+users can users can select from existing nodes already connected to the space or select an admin from a provided list to
+connect node.
+You can connect any node running Netdata, whether it's a physical or virtual machine, a Docker container, IoT device,
+and more.
+
+The connection process securely connects any node to Netdata Cloud using
+the [Agent-Cloud link](https://github.com/netdata/netdata/blob/master/aclk/README.md). By
+connecting a node, you prove you have write and administrative access to that node. Connecting to Cloud also prevents
+any third party
+from connecting a node that you control. Keep in mind:
+
+- _You can only connect any given node in a single Space_. You can, however, add that connected node to multiple War
+ Rooms
+ within that one Space.
+- You must repeat the connection process on every node you want to add to Netdata Cloud.
+
+<Callout type="notice">
+
+**Netdata Cloud ensures your data privacy by not storing metrics data from your nodes**. See our statement on Netdata
+Cloud [data privacy](https://github.com/netdata/netdata/blob/master/aclk/README.md/#data-privacy) for details on the
+data that's streamed from your nodes and the
+[connecting to cloud](https://github.com/netdata/netdata/blob/master/claim/README.md) doc for details about why we
+implemented the connection process and the encryption methods we use to secure your data in transit.
+
+</Callout>
+
+To connect a node, select which War Rooms you want to add this node to with the dropdown, then copy the script given by
+Netdata Cloud into your node's terminal.
+
+Hit **Enter**. The script should return `Agent was successfully claimed.`. If the claiming script returns errors, or if
+you don't see the node in your Space after 60 seconds, see
+the [troubleshooting information](https://github.com/netdata/netdata/blob/master/claim/README.md#troubleshooting).
+
+Repeat this process with every node you want to add to Netdata Cloud during onboarding. You can also add more nodes once
+you've finished onboarding by clicking the **Connect Nodes** button in
+the [Space management area](https://github.com/netdata/netdata/blob/master/docs/cloud/spaces.md/#manage-spaces).
+
+### Alternatives and other operating systems
+
+**Docker**: You can execute the claiming script Netdata running as a Docker container, or attach the claiming script
+when creating the container for the first time, such as when you're spinning up ephemeral containers. See
+the [connect an agent running in Docker](https://github.com/netdata/netdata/blob/master/claim/README.md#connect-an-agent-running-in-docker)
+documentation for details.
+
+**Without root privileges**: If you want to connect an agent without using root privileges, see our [connect
+documentation](https://github.com/netdata/netdata/blob/master/claim/README.md#connect-an-agent-without-root-privileges).
+
+**With a proxy**: If your node uses a proxy to connect to the internet, you need to configure the node's proxy settings.
+See
+our [connect through a proxy](https://github.com/netdata/netdata/blob/master/claim/README.md#connect-through-a-proxy)
+doc for details.
+
+## Add bookmarks to essential resources
+
+When an anomaly or outage strikes, your team needs to access other essential resources quickly. You can use Netdata
+Cloud's bookmarks to put these tools in one accessible place. Bookmarks are shared between all War Rooms in a Space, so
+any users in your Space will be able to see and use them.
+
+Bookmarks can link to both internal and external resources. You can bookmark your app's status page for quick updates
+during an outage, a messaging system on your organization's intranet, or other tools your team uses to respond to
+changes in your infrastructure.
+
+To add a new bookmark, click on the **Add bookmark** link. In the panel, name the bookmark, include its URL, and write a
+short description for your team's reference.
+
+## What's next?
+
+You finish onboarding
+by [inviting members of your team](https://github.com/netdata/netdata/blob/master/docs/cloud/manage/invite-your-team.md)
+to your Space. You
+can also invite them later. At this point, you're ready to use Cloud.
+
+Next, learn about the organization and interfaces
+behind [Spaces](https://github.com/netdata/netdata/blob/master/docs/cloud/spaces.md)
+and [War
+Rooms](https://github.com/netdata/netdata/blob/master/docs/cloud/war-rooms.md).
+
+If you're ready to explore, check out how to use
+the [Overview dashboard](https://github.com/netdata/netdata/blob/master/docs/cloud/visualize/overview.md), which is the
+default view for each new War Room you create.
diff --git a/docs/cloud/insights/anomaly-advisor.mdx b/docs/cloud/insights/anomaly-advisor.mdx
new file mode 100644
index 00000000..98a28d92
--- /dev/null
+++ b/docs/cloud/insights/anomaly-advisor.mdx
@@ -0,0 +1,86 @@
+---
+title: "Anomaly Advisor"
+description: "Quickly find anomalous metrics anywhere in your infrastructure."
+custom_edit_url: "https://github.com/netdata/netdata/blob/master/docs/cloud/insights/anomaly-advisor.mdx"
+sidebar_label: "Anomaly Advisor"
+learn_status: "Published"
+learn_topic_type: "Tasks"
+learn_rel_path: "Operations"
+---
+
+import ReactPlayer from 'react-player'
+
+The Anomaly Advisor feature lets you quickly surface potentially anomalous metrics and charts related to a particular highlight window of
+interest.
+
+<ReactPlayer playing true controls true url='https://user-images.githubusercontent.com/24860547/165943403-1acb9759-7446-4704-8955-c566d04ad7ab.mp4' />
+
+## Getting Started
+
+If you are running a Netdata version higher than `v1.35.0-29-nightly` you will be able to use the Anomaly Advisor out of the box with zero configuration. If you are on an earlier Netdata version you will need to first enable ML on your nodes by following the steps below.
+
+To enable the Anomaly Advisor you must first enable ML on your nodes via a small config change in `netdata.conf`. Once the anomaly detection models have trained on the Agent (with default settings this takes a couple of hours until enough data has been seen to train the models) you will then be able to enable the Anomaly Advisor feature in Netdata Cloud.
+
+### Enable ML on Netdata Agent
+
+To enable ML on your Netdata Agent, you need to edit the `[ml]` section in your `netdata.conf` to look something like the following example.
+
+```bash
+[ml]
+ enabled = yes
+```
+
+At a minimum you just need to set `enabled = yes` to enable ML with default params. More details about configuration can be found in the [Netdata Agent ML docs](https://learn.netdata.cloud/docs/agent/ml#configuration).
+
+**Note**: Follow [this guide](https://github.com/netdata/netdata/blob/master/docs/guides/step-by-step/step-04.md) if you are unfamiliar with making configuration changes in Netdata.
+
+When you have finished your configuration, restart Netdata with a command like `sudo systemctl restart netdata` for the config changes to take effect. You can find more info on restarting Netdata [here](https://github.com/netdata/netdata/blob/master/docs/configure/start-stop-restart.md).
+
+After a brief delay, you should see the number of `trained` dimensions start to increase on the "dimensions" chart of the "Anomaly Detection" menu on the Overview page. By default the `minimum num samples to train = 3600` parameter means at least 1 hour of data is required to train initial models, but you could set this to `900` if you want to train initial models quicker but on less data. Over time, they will retrain on up to `maximum num samples to train = 14400` (4 hours by default), but you could increase this is you wanted to train on more data.
+
+![image](https://user-images.githubusercontent.com/2178292/166474099-ba6f5ebe-12b2-4ef2-af9f-e84a05349791.png)
+
+Once this line flattens out all configured metrics should have models trained and predicting anomaly scores each second, ready to be used by the new "anomalies" tab of the Anomaly Advisor.
+
+## Using Anomaly Advisor
+
+To use the Anomaly Advisor, go to the "anomalies" tab. Once you highlight a particular timeframe of interest, a selection of the most anomalous dimensions will appear below.
+
+The aim here is to surface the most anomalous metrics in the space or room for the highlighted window to try and cut down on the amount of manual searching required to get to the root cause of your issues.
+
+![image](https://user-images.githubusercontent.com/2178292/164427337-a40820d2-8d36-4a94-8dfb-cfd3194941e0.png)
+
+The "Anomaly Rate" chart shows the percentage of anomalous metrics over time per node. For example, in the following image, 3.21% of the metrics on the "ml-demo-ml-disabled" node were considered anomalous. This elevated anomaly rate could be a sign of something worth investigating.
+
+**Note**: in this example the anomaly rates for this node are actually being calculated on the parent it streams to, you can run ml on the Agent itselt or on a parent the Agent stream to. Read more about the various configuration options in the [Agent docs](https://github.com/netdata/netdata/blob/master/ml/README.md).
+
+![image](https://user-images.githubusercontent.com/2178292/164428307-6a86989a-611d-47f8-a673-911d509cd954.png)
+
+The "Count of Anomalous Metrics" chart (collapsed by default) shows raw counts of anomalous metrics per node so may often be similar to the anomaly rate chart, apart from where nodes may have different numbers of metrics.
+
+The "Anomaly Events Detected" chart (collapsed by default) shows if the anomaly rate per node was sufficiently elevated to trigger a node level anomaly. Anomaly events will appear slightly after the anomaly rate starts to increase in the timeline, this is because a significant number of metrics in the node need to be anomalous before an anomaly event is triggered.
+
+Once you have highlighted a window of interest, you should see an ordered list of anomaly rate sparklines in the "Anomalous metrics" section like below.
+
+![image](https://user-images.githubusercontent.com/2178292/164427592-ab1d0eb1-57e2-4a05-aaeb-da4437a019b1.png)
+
+You can expand any sparkline chart to see the underlying raw data to see how it relates to the corresponding anomaly rate.
+
+![image](https://user-images.githubusercontent.com/2178292/164430105-f747d1e0-f3cb-4495-a5f7-b7bbb71039ae.png)
+
+On the upper right hand side of the page you can select which nodes to filter on if you wish to do so. The ML training status of each node is also displayed.
+
+On the lower right hand side of the page an index of anomaly rates is displayed for the highlighted timeline of interest. The index is sorted from most anomalous metric (highest anomaly rate) to least (lowest anomaly rate). Clicking on an entry in the index will scroll the rest of the page to the corresponding anomaly rate sparkline for that metric.
+
+### Usage Tips
+
+- If you are interested in a subset of specific nodes then filtering to just those nodes before highlighting tends to give better results. This is because when you highlight a region, Netdata Cloud will ask the Agents for a ranking over all metrics so if you can filter this early to just the subset of nodes you are interested in, less 'averaging' will occur and so you might be a less noisy ranking.
+- Ideally try and highlight close to a spike or window of interest so that the resulting ranking can narrow in more easily on the timeline you are interested in.
+
+You can read more detail on how anomaly detection in the Netdata Agent works in our [Agent docs](https://github.com/netdata/netdata/blob/master/ml/README.md).
+
+🚧 **Note**: This functionality is still **under active development** and considered experimental. We dogfood it internally and among early adopters within the Netdata community to build the feature. If you would like to get involved and help us with feedback, you can reach us through any of the following channels:
+- Email us at analytics-ml-team@netdata.cloud
+- Comment on the [beta launch post](https://community.netdata.cloud/t/anomaly-advisor-beta-launch/2717) in the Netdata community
+- Join us in the [🤖-ml-powered-monitoring](https://discord.gg/4eRSEUpJnc) channel of the Netdata discord.
+- Or open a discussion in GitHub if that's more your thing
diff --git a/docs/cloud/insights/metric-correlations.md b/docs/cloud/insights/metric-correlations.md
new file mode 100644
index 00000000..ce8835d3
--- /dev/null
+++ b/docs/cloud/insights/metric-correlations.md
@@ -0,0 +1,87 @@
+---
+title: "Metric Correlations"
+description: "Quickly find metrics and charts closely related to a particular timeframe of interest anywhere in your infrastructure to discover the root cause faster."
+custom_edit_url: "https://github.com/netdata/netdata/blob/master/docs/cloud/insights/metric-correlations.md"
+sidebar_label: "Metric Correlations"
+learn_status: "Published"
+learn_topic_type: "Tasks"
+learn_rel_path: "Operations"
+---
+
+The Metric Correlations (MC) feature lets you quickly find metrics and charts related to a particular window of interest that you want to explore further. By displaying the standard Netdata dashboard, filtered to show only charts that are relevant to the window of interest, you can get to the root cause sooner.
+
+Because Metric Correlations uses every available metric from your infrastructure, with as high as 1-second granularity, you get the most accurate insights using every possible metric.
+
+## Using Metric Correlations
+
+When viewing the overview or a single-node dashboard, the **Metric Correlations** button appears in the top right corner of the page.
+
+![The Metric Correlations button](https://user-images.githubusercontent.com/2178292/201082551-d805b20d-0472-455d-9f11-b2329adf3098.png)
+
+To start correlating metrics, click the **Metric Correlations** button, then hold the `Alt` key (or `⌘` on macOS) and click-and-drag a selection of metrics on a single chart. The selected timeframe needs to be at least 15 seconds for Metric Correlation to work.
+
+The menu then displays information about the selected area and reference baseline. Metric Correlations uses the reference baseline to discover which additional metrics are most closely connected to the selected metrics. The reference baseline is based upon the period immediately preceding the highlighted window and is the length of 4 times the highlighted window. This is to ensure that the reference baseline is always immediately before the highlighted window of interest and a bit longer so as to ensure it's a more representative short term baseline.
+
+Press the **Find Correlations** button to start up the correlations process, the button is only enabled when a valid timeframe is selected (at least 15 seconds). Once pressed, the process will score all available metrics on your nodes and return a filtered version of the Netdata dashboard. Now, you'll see only those metrics that have changed the most between a baseline window and the highlighted window you have selected.
+
+![Metric Correlations results](https://user-images.githubusercontent.com/2178292/181751182-25e0890d-a5f4-4799-9936-1523603cf97d.png)
+
+These charts are fully interactive, and whenever possible, will only show the _dimensions_ related to the timeline you selected.
+
+You can interact with all the scored metrics via the slider. Slide toward **show less** for more nuanced and significant results, or toward **show more** to "loosen" the threshold to explore other charts that may have changed too, but in a less significant manner.
+
+If you find something else interesting in the results, you can select another window and press **Find Correlations** again to kick the process off again.
+
+## Metric Correlations options
+
+MC enables a few input parameters that users can define to iteratively explore their data in different ways. As is usually the case in Machine Learning (ML), there is no "one size fits all" algorithm, what approach works best will typically depend on the type of data (which can be very different from one metric to the next) and even the nature of the event or incident you might be exploring in Netdata.
+
+So when you first run MC it will use the most sensible and general defaults. But you can also then vary any of the below options to explore further.
+
+### Method
+
+There are two algorithms available that aim to score metrics based on how much they have changed between the baseline and highlight windows.
+
+- `KS2` - A statistical test ([Two-sample Kolmogorov Smirnov](https://en.wikipedia.org/wiki/Kolmogorov%E2%80%93Smirnov_test#Two-sample_Kolmogorov%E2%80%93Smirnov_test)) comparing the distribution of the highlighted window to the baseline to try and quantify which metrics have most evidence of a significant change. You can explore our implementation [here](https://github.com/netdata/netdata/blob/d917f9831c0a1638ef4a56580f321eb6c9a88037/database/metric_correlations.c#L212).
+- `Volume` - A heuristic measure based on the percentage change in averages between highlighted window and baseline, with various edge cases sensibly controlled for. You can explore our implementation [here](https://github.com/netdata/netdata/blob/d917f9831c0a1638ef4a56580f321eb6c9a88037/database/metric_correlations.c#L516).
+
+### Aggregation
+
+Behind the scenes, Netdata will aggregate the raw data as needed such that arbitrary window lengths can be selected for MC. By default, Netdata will just `Average` raw data when needed as part of pre-processing. However other aggregations like `Median`, `Min`, `Max`, `Stddev` are also possible.
+
+### Data
+
+Netdata is different from typical observability agents since, in addition to just collecting raw metric values, it will by default also assign an "[Anomaly Bit](/docs/agent/ml#anomaly-bit)" related to each collected metric each second. This bit will be 0 for "normal" and 1 for "anomalous". This means that each metric also natively has an "[Anomaly Rate](/docs/agent/ml#anomaly-rate)" associated with it and, as such, MC can be run against the raw metric values or their corresponding anomaly rates.
+
+**Note**: Read more [here](https://github.com/netdata/netdata/blob/master/docs/guides/monitor/anomaly-detection.md) to learn more about the native anomaly detection features within netdata.
+
+- `Metrics` - Run MC on the raw metric values.
+- `Anomaly Rate` - Run MC on the corresponding anomaly rate for each metric.
+
+## Metric Correlations on the agent
+
+As of `v1.35.0` Netdata is able to run the Metric Correlations algorithm ([Two Sample Kolmogorov-Smirnov test](https://en.wikipedia.org/wiki/Kolmogorov%E2%80%93Smirnov_test#Two-sample_Kolmogorov%E2%80%93Smirnov_test)) on the agent itself. This avoids sending the underlying raw data to the original Netdata Cloud based microservice and so typically will be much much faster as no data moves around and the computation happens instead on the agent.
+
+When a Metric Correlations request is made to Netdata Cloud, if any node instances have MC enabled then the request will be routed to the node instance with the highest hops (e.g. a parent node if one is found or the node itself if not). If no node instances have MC enabled then the request will be routed to the original Netdata Cloud based service which will request input data from the nodes and run the computation within the Netdata Cloud backend.
+
+#### Enabling/Disabling Metric Correlations on the agent
+
+As of `v1.35.0-22-nightly` Metric Correlation has been enabled by default on all agents. After further optimizations to the implementation, the impact of running the metric correlations algorithm on the agent was less than the impact of preparing all the data to send to cloud for MC to run in the cloud, as such running MC on the agent is less impactful on local resources than running via cloud.
+
+Should you still want to, disabling nodes for Metric Correlation on the agent is a simple one line config change. Just set `enable metric correlations = no` in the `[global]` section of `netdata.conf`
+
+## Usage tips!
+
+- When running Metric Correlations from the [Overview tab](https://learn.netdata.cloud/docs/cloud/visualize/overview#overview) across multiple nodes, you might find better results if you iterate on the initial results by grouping by node to then filter to nodes of interest and run the Metric Correlations again. So a typical workflow in this case would be to:
+ - If unsure which nodes you are interested in then run MC on all nodes.
+ - Within the initial results returned group the most interesting chart by node to see if the changes are across all nodes or a subset of nodes.
+ - If you see a subset of nodes clearly jump out when you group by node, then filter for just those nodes of interest and run the MC again. This will result in less aggregation needing to be done by Netdata and so should help give clearer results as you interact with the slider.
+- Use the `Volume` algorithm for metrics with a lot of gaps (e.g. request latency when there are few requests), otherwise stick with `KS2`
+ - By default, Netdata uses the `KS2` algorithm which is a tried and tested method for change detection in a lot of domains. The [Wikipedia](https://en.wikipedia.org/wiki/Kolmogorov%E2%80%93Smirnov_test) article gives a good overview of how this works. Basically, it is comparing, for each metric, its cumulative distribution in the highlight window with its cumulative distribution in the baseline window. The statistical test then seeks to quantify the extent to which we can say these two distributions look similar enough to be considered the same or not. The `Volume` algorithm is a bit more simple than `KS2` in that it basically compares (with some edge cases sensibly handled) the average value of the metric across baseline and highlight and looks at the percentage change. Often both `KS2` and `Volume` will have significant agreement and return similar metrics.
+ - `Volume` might favour picking up more sparse metrics that were relatively flat and then came to life with some spikes (or vice versa). This is because for such metrics that just don't have that many different values in them, it is impossible to construct a cumulative distribution that can then be compared. So `Volume` might be useful in spotting examples of metrics turning on or off. ![example where volume captured network traffic turning on](https://user-images.githubusercontent.com/2178292/182336924-d02fd3d3-7f09-41da-9cfc-809d01396d9d.png)
+ - `KS2` since it relies on the full distribution might be better at highlighting more complex changes that `Volume` is unable to capture. For example a change in the variation of a metric might be picked up easily by `KS2` but missed (or just much lower scored) by `Volume` since the averages might remain not all that different between baseline and highlight even if their variance has changed a lot. ![example where KS2 captured a change in entropy distribution that volume alone might not have picked up](https://user-images.githubusercontent.com/2178292/182338289-59b61e6b-089d-431c-bc8e-bd19ba6ad5a5.png)
+- Use `Volume` and `Anomaly Rate` together to ask what metrics have turned most anomalous from baseline to highlighted window. You can expand the embedded anomaly rate chart once you have results to see this more clearly. ![example where Volume and Anomaly Rate together help show what dimensions where most anomalous](https://user-images.githubusercontent.com/2178292/182338666-6d19fa92-89d3-4d61-804c-8f10982114f5.png)
+
+## What's next?
+
+You can read more about all the ML powered capabilities of Netdata [here](https://github.com/netdata/netdata/blob/master/docs/guides/monitor/anomaly-detection.md). If you aren't yet familiar with the power of Netdata Cloud's visualization features, check out the [Nodes view](https://github.com/netdata/netdata/blob/master/docs/cloud/visualize/nodes.md) and learn how to [build new dashboards](https://github.com/netdata/netdata/blob/master/docs/cloud/visualize/dashboards.md).
diff --git a/docs/cloud/manage/invite-your-team.md b/docs/cloud/manage/invite-your-team.md
new file mode 100644
index 00000000..f294a627
--- /dev/null
+++ b/docs/cloud/manage/invite-your-team.md
@@ -0,0 +1,37 @@
+---
+title: "Invite your team"
+description: >-
+ "Invite your entire SRE, DevOPs, or ITOps team to Netdata Cloud to give everyone insights into your
+ infrastructure from a single pane of glass."
+custom_edit_url: "https://github.com/netdata/netdata/blob/master/docs/cloud/manage/invite-your-team.md"
+sidebar_label: "Invite your team"
+learn_status: "Published"
+learn_topic_type: "Tasks"
+learn_rel_path: "Operations"
+---
+
+Invite new users to your Space by clicking on **Invite Users** in
+the [Space](https://github.com/netdata/netdata/blob/master/docs/cloud/spaces.md) management area.
+
+![Opening the invitation panel in Netdata Cloud](https://user-images.githubusercontent.com/1153921/108529805-1b13b480-7292-11eb-862f-0499e3fdac17.png)
+
+Enter the email addresses for the users you want to invite to your Space. You can enter any number of email addresses,
+separated by a comma, to send multiple invitations at once.
+
+Next, choose the War Rooms you want to invite these users to. Once logged in, these users are not restricted only to
+these War Rooms. They can be invited to others, or join any that are public.
+
+Click the **Send** button to send an email invitation, which will prompt them
+to [sign up](https://github.com/netdata/netdata/blob/master/docs/cloud/manage/sign-in.mdx) and join your Space.
+
+![The invitation panel in Netdata Cloud](https://user-images.githubusercontent.com/1153921/97762959-53b33680-1ac7-11eb-8e9d-f3f4a14c0028.png)
+
+Any unaccepted invitations remain under **Invitations awaiting response**. These invitations can be rescinded at any
+time by clicking the trash can icon.
+
+## What's next?
+
+If your team members have trouble signing in, direct them to
+the [sign in guide](https://github.com/netdata/netdata/blob/master/docs/cloud/manage/sign-in.mdx). Once your
+team is onboarded to Netdata Cloud, they can view shared assets, such
+as [new dashboards](https://github.com/netdata/netdata/blob/master/docs/cloud/visualize/dashboards.md).
diff --git a/docs/cloud/manage/sign-in.mdx b/docs/cloud/manage/sign-in.mdx
new file mode 100644
index 00000000..32fcb22e
--- /dev/null
+++ b/docs/cloud/manage/sign-in.mdx
@@ -0,0 +1,88 @@
+---
+title: "Sign in with email, Google, or GitHub"
+description: "Learn how signing in to Cloud works via one of our three authentication methods, plus some tips if you're having trouble signing in."
+custom_edit_url: "https://github.com/netdata/netdata/blob/master/docs/cloud/manage/sign-in.mdx"
+sidebar_label: "Sign in with email, Google, or GitHub"
+learn_status: "Published"
+learn_topic_type: "Tasks"
+learn_rel_path: "Operations"
+---
+
+You can [sign in to Netdata](https://app.netdata.cloud/sign-in?cloudRoute=spaces?utm_source=docs&utm_content=sign_in_button_first_section) through one of three methods: email, Google, or GitHub. Email uses a
+time-sensitive link that authenticates your browser, and Google/GitHub both use OAuth to associate your email address
+with a Netdata Cloud account.
+
+No matter the method, your Netdata Cloud account is based around your email address. Netdata Cloud does not store
+passwords.
+
+
+## Email
+
+To sign in with email, visit [Netdata Cloud](https://app.netdata.cloud/sign-in?cloudRoute=spaces?utm_source=docs&utm_content=sign_in_button_email_section), enter your email address, and click
+the **Sign in by email** button.
+
+![Verify your email!](https://user-images.githubusercontent.com/82235632/125475486-c667635a-067f-4866-9411-9f7f795a0d50.png)
+
+Click the **Verify** button in the email to begin using Netdata Cloud.
+
+To use this same Netdata Cloud account on additional devices, request another sign in email, open the email on that
+device, and sign in.
+
+### Don't have a Netdata Cloud account yet?
+
+If you don't have a Netdata Cloud account yet you won't need to worry about it. During the sign in process we will create one for you and make the process seamless to you.
+
+After your account is created and you sign in to Netdata, you first are asked to agree to Netdata Cloud's [Privacy
+Policy](https://www.netdata.cloud/privacy/) and [Terms of Use](https://www.netdata.cloud/terms/). Once you agree with these you are directed
+through the Netdata Cloud onboarding process, which is explained in the [Netdata Cloud
+quickstart](https://github.com/netdata/netdata/blob/master/docs/cloud/get-started.mdx).
+
+### Troubleshooting
+
+You should receive your sign in email in less than a minute. The subject is **Verify your email!** and the sender is `no-reply@app.netdata.cloud` via `sendgrid.net`.
+
+If you don't see the email, try the following:
+
+- Check [Netdata Cloud status](https://status.netdata.cloud) for ongoing issues with our infrastructure.
+- Request another sign in email via the [sign in page](https://app.netdata.cloud/sign-in?cloudRoute=spaces?utm_source=docs&utm_content=sign_in_button_troubleshooting_section).
+- Check your spam folder.
+- In Gmail, check the **Updates** category.
+
+You may also want to add `no-reply@app.netdata.cloud` to your address book or contacts list, especially if you're using
+a public email service, such as Gmail. You may also want to whitelist/allowlist either the specific email or the entire
+`app.netdata.cloud` domain.
+
+## Google and GitHub OAuth
+
+When you use Google/GitHub OAuth, your Netdata Cloud account is associated with the email address that Netdata Cloud
+receives via OAuth.
+
+To sign in with Google or GitHub OAuth, visit [Netdata Cloud](https://app.netdata.cloud/sign-in?cloudRoute=spaces?utm_source=docs&utm_content=sign_in_button_google_github_section) and click the
+**Continue with Google/GitHub** or button. Enter your Google/GitHub username and your password. Complete two-factor
+authentication if you or your organization has it enabled.
+
+You are then signed in to Netdata Cloud or directed to the new-user onboarding if you have not signed up previously.
+
+## Reset a password
+
+Netdata Cloud does not store passwords and does not support password resets. All of our sign in methods do not
+require passwords, and use either links in emails or Google/GitHub OAuth for authentication.
+
+## Switch between sign in methods
+
+You can switch between sign in methods if the email account associated with each method is the same.
+
+For example, you first sign in via your email account, `user@example.com`, and later sign out. You later attempt to sign
+in via a GitHub account associated with `user@example.com`. Netdata Cloud recognizes that the two are the same and signs
+you in to your original account.
+
+However, if you first sign in via your `user@example.com` email account and then sign in via a Google account associated
+with `user2@example.com`, Netdata Cloud creates a new account and begins the onboarding process.
+
+It is not currently possible to link an account created with `user@example.com` to a Google account associated with
+`user2@example.com`.
+
+## What's next?
+
+If you haven't already onboarded to Netdata Cloud and connected your first nodes, visit
+the [get started guide](https://github.com/netdata/netdata/blob/master/docs/cloud/get-started.mdx).
diff --git a/docs/cloud/manage/themes.md b/docs/cloud/manage/themes.md
new file mode 100644
index 00000000..11d5cb32
--- /dev/null
+++ b/docs/cloud/manage/themes.md
@@ -0,0 +1,22 @@
+---
+title: "Choose your Netdata Cloud theme"
+description: "Switch between Light and Dark themes in Netdata Cloud to match your personal visualization preferences."
+custom_edit_url: "https://github.com/netdata/netdata/blob/master/docs/cloud/manage/themes.md"
+sidebar_label: "Choose your Netdata Cloud theme"
+learn_status: "Published"
+learn_topic_type: "Tasks"
+learn_rel_path: "Operations"
+---
+
+The Dark theme is the default for all new Netdata Cloud accounts.
+
+To change your theme across Netdata Cloud, click on your profile picture, then **Profile**. Click on the **Settings**
+tab, then choose your preferred theme: Light or Dark.
+
+**Light**:
+
+![Dark theme](https://user-images.githubusercontent.com/1153921/108530742-2ca98c00-7293-11eb-8c1e-1e0dd34eb87b.png)
+
+**Dark (default)**:
+
+![Light theme](https://user-images.githubusercontent.com/1153921/108530848-4519a680-7293-11eb-897d-1c470b67ceb0.png)
diff --git a/docs/cloud/netdata-functions.md b/docs/cloud/netdata-functions.md
new file mode 100644
index 00000000..e1b9dd0b
--- /dev/null
+++ b/docs/cloud/netdata-functions.md
@@ -0,0 +1,65 @@
+<!--
+title: "Netdata Functions"
+sidebar_label: "Netdata Functions"
+custom_edit_url: "https://github.com/netdata/netdata/blob/master/docs/cloud/netdata-functions.md"
+sidebar_position: "2800"
+learn_status: "Published"
+learn_topic_type: "Concepts"
+learn_rel_path: "Concepts"
+learn_docs_purpose: "Present the Netdata Functions what these are and why they should be used."
+-->
+
+Netdata Agent collectors are able to expose functions that can be executed in run-time and on-demand. These will be
+executed on the node - host where the function is made
+available.
+
+#### What is a function?
+
+Collectors besides the metric collection, storing, and/or streaming work are capable of executing specific routines on
+request. These routines will bring additional information
+to help you troubleshoot or even trigger some action to happen on the node itself.
+
+A function is a `key` - `value` pair. The `key` uniquely identifies the function within a node. The `value` is a
+function (i.e. code) to be run by a data collector when
+the function is invoked.
+
+For more details please check out documentation on how we use our internal collector to get this from the first collector that exposes
+functions - [plugins.d](https://github.com/netdata/netdata/blob/master/collectors/plugins.d/README.md#function).
+
+#### What functions are currently available?
+
+| Function | Description | plugin - module |
+| :-- | :-- | :-- |
+| processes | Detailed information on the currently running processes on the node. | [apps.plugin](https://github.com/netdata/netdata/blob/master/collectors/apps.plugin/README.md) |
+
+If you have ideas or requests for other functions:
+* open a [Feature request](https://github.com/netdata/netdata-cloud/issues/new?assignees=&labels=feature+request%2Cneeds+triage&template=FEAT_REQUEST.yml&title=%5BFeat%5D%3A+) on Netdata Cloud repo
+* engage with our community on the [Netdata Discord server](https://discord.com/invite/mPZ6WZKKG2).
+#### How do functions work with streaming?
+
+Via streaming, the definitions of functions are transmitted to a parent node so it knows all the functions available on
+any children connected to it.
+
+If the parent node is the one connected to Netdata Cloud it is capable of triggering the call to the respective children
+node to run the function.
+
+#### Why are they available only on Netdata Cloud?
+
+Since these functions are able to execute routines on the node and due the potential use cases that they can cover, our
+concern is to ensure no sensitive
+information or disruptive actions are exposed through the Agent's API.
+
+With the communication between the Netdata Agent and Netdata Cloud being
+through [ACLK](https://github.com/netdata/netdata/blob/master/aclk/README.md) this
+concern is addressed.
+
+## Related Topics
+
+### **Related Concepts**
+
+- [ACLK](https://github.com/netdata/netdata/blob/master/aclk/README.md)
+- [plugins.d](https://github.com/netdata/netdata/blob/master/collectors/plugins.d/README.md)
+
+### Related Tasks
+
+- [Run-time troubleshooting with Functions](https://github.com/netdata/netdata/blob/master/docs/cloud/runtime-troubleshooting-with-functions.md)
diff --git a/docs/cloud/runtime-troubleshooting-with-functions.md b/docs/cloud/runtime-troubleshooting-with-functions.md
new file mode 100644
index 00000000..3800ea20
--- /dev/null
+++ b/docs/cloud/runtime-troubleshooting-with-functions.md
@@ -0,0 +1,43 @@
+<!--
+title: "Run-time troubleshooting with Functions"
+sidebar_label: "Run-time troubleshooting with Functions"
+custom_edit_url: "https://github.com/netdata/netdata/blob/master/docs/cloud/runtime-troubleshooting-with-functions.md"
+learn_status: "Published"
+sidebar_position: "4"
+learn_topic_type: "Tasks"
+learn_rel_path: "Operations"
+learn_docs_purpose: "Instructions on how to use Functions"
+-->
+
+Netdata Functions feature allows you to execute on-demand a pre-defined routine on a node where a Netdata Agent is running. These routines are exposed by a given collector.
+These routines can be used to retrieve additional information to help you troubleshoot or to trigger some action to happen on the node itself.
+
+
+### Prerequisites
+
+The following is required to be able to run Functions from Netdata Cloud.
+* At least one of the nodes claimed to your Space should be on a Netdata agent version higher than `v1.37.1`
+* Ensure that the node has the collector that exposes the function you want enabled ([see current available functions](https://github.com/netdata/netdata/blob/master/docs/cloud/netdata-functions.md#what-functions-are-currently-available))
+
+### Execute a function (from functions view)
+
+1. From the right-hand bar select the **Function** you want to run
+2. Still on the right-hand bar select the **Node** where you want to run it
+3. Results will be displayed in the central area for you to interact with
+4. Additional filtering capabilities, depending on the function, should be available on right-hand bar
+
+### Execute a function (from Nodes view)
+
+1. Click on the functions icon for a node that has this active
+2. You are directed to the **Functions** tab
+3. Follow the above instructions from step 3.
+
+> ⚠️ If you get an error saying that your node can't execute Functions please check the [prerequisites](#prerequisites).
+
+## Related Topics
+
+### **Related Concepts**
+- [Netdata Functions](https://github.com/netdata/netdata/blob/master/docs/cloud/netdata-functions.md)
+
+#### Related References documentation
+- [External plugins overview](https://github.com/netdata/netdata/blob/master/collectors/plugins.d/README.md#function)
diff --git a/docs/cloud/spaces.md b/docs/cloud/spaces.md
new file mode 100644
index 00000000..31d8a47a
--- /dev/null
+++ b/docs/cloud/spaces.md
@@ -0,0 +1,91 @@
+---
+title: "Spaces"
+description: >-
+ "Organize your infrastructure monitoring on Netdata Cloud by creating Spaces, then groupingyour
+ Agent-monitored nodes."
+custom_edit_url: "https://github.com/netdata/netdata/blob/master/docs/cloud/spaces.md"
+sidebar_label: "Spaces"
+learn_status: "Published"
+learn_topic_type: "Tasks"
+learn_rel_path: "Operations"
+---
+
+A Space is a high-level container. It's a collaboration space where you can organize team members, access levels and the
+nodes you want to monitor.
+
+Let's talk through some strategies for creating the most intuitive Cloud experience for your team.
+
+## How to organize your Netdata Cloud
+
+You can use any number of Spaces you want, but as you organize your Cloud experience, keep in mind that _you can only
+add any given node to a single Space_. This 1:1 relationship between node and Space may dictate whether you use one
+encompassing Space for your entire team and separate them by War Rooms, or use different Spaces for teams monitoring
+discrete parts of your infrastructure.
+
+If you have been invited to Netdata Cloud by another user by default you will able to see this space. If you are a new
+user the first space is already created.
+
+The other consideration for the number of Spaces you use to organize your Netdata Cloud experience is the size and
+complexity of your organization.
+
+For small team and infrastructures we recommend sticking to a single Space so that you can keep all your nodes and their
+respective metrics in one place. You can then use
+multiple [War Rooms](https://github.com/netdata/netdata/blob/master/docs/cloud/war-rooms.md)
+to further organize your infrastructure monitoring.
+
+Enterprises may want to create multiple Spaces for each of their larger teams, particularly if those teams have
+different responsibilities or parts of the overall infrastructure to monitor. For example, you might have one SRE team
+for your user-facing SaaS application and a second team for infrastructure tooling. If they don't need to monitor the
+same nodes, you can create separate Spaces for each team.
+
+## Navigate between spaces
+
+Click on any of the boxes to switch between available Spaces.
+
+Netdata Cloud abbreviates each Space to the first letter of the name, or the first two letters if the name is two words
+or more. Hover over each icon to see the full name in a tooltip.
+
+To add a new Space click on the green **+** button . Enter the name of the Space and click **Save**.
+
+![Switch between Spaces](/img/cloud/main-page-add-space.png)
+
+## Manage Spaces
+
+Manage your spaces by selecting in a particular space and clicking in the small gear icon in the lower left corner. This
+will open a side tab in which you can:
+
+1. _Configure this Space*_, in the first tab (**Space**) you can change the name, description or/and some privilege
+ options of this space
+
+2. _Edit the War Rooms*_, click on the **War rooms** tab to add or remove War Rooms.
+
+3. _Connect nodes*_, click on **Nodes** tab. Copy the claiming script to your node and run it. See the
+ [connect to Cloud doc](https://github.com/netdata/netdata/blob/master/claim/README.md) for details.
+
+4. _Manage the users*_, click on **Users**.
+ The [invitation doc](https://github.com/netdata/netdata/blob/master/docs/cloud/manage/invite-your-team.md)
+ details the invitation process.
+
+5. _Manage notification setting*_, click on **Notifications** tab to turn off/on notification methods.
+
+6. _Manage your bookmarks*_, click on the **Bookmarks** tab to add or remove bookmarks that you need.
+
+:::note \* This action requires admin rights for this space
+:::
+
+## Obsoleting offline nodes from a Space
+
+Netdata admin users now have the ability to remove obsolete nodes from a space.
+
+- Only admin users have the ability to obsolete nodes
+- Only offline nodes can be marked obsolete (Live nodes and stale nodes cannot be obsoleted)
+- Node obsoletion works across the entire space, so the obsoleted node will be removed from all rooms belonging to the
+ space
+- If the obsoleted nodes eventually become live or online once more they will be automatically re-added to the space
+
+![Obsoleting an offline node](https://user-images.githubusercontent.com/24860547/173087202-70abfd2d-f0eb-4959-bd0f-74aeee2a2a5a.gif)
+
+## What's next?
+
+Once you configured your Spaces, it's time to set up
+your [War Rooms](https://github.com/netdata/netdata/blob/master/docs/cloud/war-rooms.md).
diff --git a/docs/cloud/visualize/dashboards.md b/docs/cloud/visualize/dashboards.md
new file mode 100644
index 00000000..3c6d7ffd
--- /dev/null
+++ b/docs/cloud/visualize/dashboards.md
@@ -0,0 +1,122 @@
+---
+title: "Build new dashboards"
+description: >-
+ "Design new dashboards that target your infrastructure's unique needs and share them with your team for
+ targeted visual anomaly detection or incident response."
+custom_edit_url: "https://github.com/netdata/netdata/blob/master/docs/cloud/visualize/dashboards.md"
+sidebar_label: "Build new dashboards"
+learn_status: "Published"
+learn_topic_type: "Tasks"
+learn_rel_path: "Operations/Visualizations"
+---
+
+With Netdata Cloud, you can build new dashboards that target your infrastructure's unique needs. Put key metrics from
+any number of distributed systems in one place for a bird's eye view of your infrastructure.
+
+Click on the **Dashboards** tab in any War Room to get started.
+
+## Create your first dashboard
+
+From the Dashboards tab, click on the **+** button.
+
+![Add or manage
+dashboards](https://user-images.githubusercontent.com/1153921/108529360-a2145d00-7291-11eb-814b-2ea3303beb64.png)
+
+In the modal, give your new dashboard a name, and click **+ Add**.
+
+Click the **Add Chart** button to add your first chart card. From the dropdown, select either *All Nodes** or a specific
+node. If you select **All Nodes**, you will add a [composite chart](/docs/cloud/visualize/overview#composite-charts) to
+your new dashboard. Next, select the context. You'll see a preview of the chart before you finish adding it.
+
+The **Add Text** button creates a new card with user-defined text, which you can use to describe or document a
+particular dashboard's meaning and purpose.
+
+Be sure to click the **Save** button any time you make changes to your dashboard.
+
+![An example multi-node dashboard for system CPU
+metrics](https://user-images.githubusercontent.com/1153921/108526381-4f857180-728e-11eb-9d65-1613e60891a5.png)
+
+## Using your dashboard
+
+Dashboards are designed to be interactive and flexible so you can design them to your exact needs. Dashboards are made
+of any number of **cards**, which can contain charts or text.
+
+### Chart cards
+
+Click the **Add Chart** button to add your first chart card. From the dropdown, select either *All Nodes** or a specific
+node. If you select **All Nodes**, you will add a [composite chart](/docs/cloud/visualize/overview#composite-charts) to
+your new dashboard. Next, select the context. You'll see a preview of the chart before you finish adding it.
+
+The charts you add to any dashboard are fully interactive, just like the charts in an Agent dashboard or a single node's
+dashboard in Cloud. Zoom in and out, highlight timeframes, and more. See our
+[Agent dashboard docs](https://learn.netdata.cloud/docs/agent/web#using-charts) for all the shortcuts.
+
+Charts also synchronize as you interact with them, even across contexts _or_ nodes.
+
+### Text cards
+
+The **Add Text** button creates a new card with user-defined text. When you create a new text card or edit an existing
+one, select/highlight characters or words to open a modal to make them **bold**, _italic_, or <ins>underlined</ins>. You
+can also create a link.
+
+### Move cards
+
+To move any card, click and hold on the top of the card, then drag it to a new location. A red placeholder indicates the
+new location. Once you release your mouse, other charts re-sort to the grid system automatically.
+
+### Resize cards
+
+To resize any card on a dashboard, click on the bottom-right corner and drag to the card's new size. Other cards re-sort
+to the grid system automatically.
+
+## Jump to single-node dashboards
+
+Quickly jump to any node's dashboard by clicking the 3-dot icon in the corner of any card to open a menu. Hit the **Go
+to Chart** item.
+
+You'll land directly on that chart of interest, but you can now scroll up and down to correlate your findings with other
+charts. Of course, you can continue to zoom, highlight, and pan through time just as you're used to with Agent
+dashboards.
+
+## Pin dashboards
+
+Click on the **Pin** button in any dashboard to put those charts into a separate panel at the bottom of the screen. You
+can now navigate through Netdata Cloud freely, individual Cloud dashboards, the Nodes view, different War Rooms, or even
+different Spaces, and have those valuable metrics follow you.
+
+Pinning dashboards helps you correlate potentially related charts across your infrastructure, no matter how you
+organized your Spaces and War Rooms, and helps you discover root causes faster.
+
+## Manage your dashboards
+
+To see dashboards associated with the current War Room, click **Dashboards** tab in any War Room. You can select
+dashboards and delete them using the 🗑️ icon.
+
+### Update/save a dashboard
+
+If you've made changes to a dashboard, such as adding or moving cards, the **Save** button is enabled. Click it to save
+your most recent changes. Any other members of the War Room will be able to see these changes the next time they load
+this dashboard.
+
+If multiple users attempt to make concurrent changes to the same dashboard, the second user who hits Save will be
+prompted to either overwrite the dashboard or reload to see the most recent changes.
+
+### Remove an individual card
+
+Click on the 3-dot icon in the corner of any card to open a menu. Click the **Remove Card** item to remove the card.
+
+### Delete a dashboard
+
+Delete any dashboard by navigating to it and clicking the **Delete** button. This will remove this entry from the
+dropdown for every member of this War Room.
+
+### Minimum browser viewport
+
+Because of the visual complexity of individual charts, dashboards require a minimum browser viewport of 800px.
+
+## What's next?
+
+Once you've designed a dashboard or two, make sure
+to [invite your team](https://github.com/netdata/netdata/blob/master/docs/cloud/manage/invite-your-team.md) if
+you haven't already. You can add these new users to the same War Room to let them see the same dashboards without any
+effort.
diff --git a/docs/cloud/visualize/interact-new-charts.md b/docs/cloud/visualize/interact-new-charts.md
new file mode 100644
index 00000000..4b33fe85
--- /dev/null
+++ b/docs/cloud/visualize/interact-new-charts.md
@@ -0,0 +1,222 @@
+---
+title: "Interact with charts"
+description: >-
+ "Learn how to get the most out of Netdata's charts. These charts will help you make sense of all the
+ metrics at your disposal, helping you troubleshoot with real-time, per-second metric data"
+type: "how-to"
+custom_edit_url: "https://github.com/netdata/netdata/blob/master/docs/cloud/visualize/interact-new-charts.md"
+sidebar_label: "Interact with charts"
+learn_status: "Published"
+learn_topic_type: "Concepts"
+learn_rel_path: "Operations/Visualizations"
+---
+
+> ⚠️ This new version of charts is currently **only** available on Netdata Cloud. We didn't want to keep this valuable
+> feature from you, so after we get this into your hands on the Cloud, we will collect and implement your feedback.
+> Together, we will be able to provide the best possible version of charts on the Netdata Agent dashboard, as quickly as
+> possible.
+
+Netdata excels in collecting, storing, and organizing metrics in out-of-the-box dashboards.
+To make sense of all the metrics, Netdata offers an enhanced version of charts that update every second.
+
+These charts provide a lot of useful information, so that you can:
+
+- Enjoy the high-resolution, granular metrics collected by Netdata
+- Explore visualization with more options such as _line_, _stacked_ and _area_ types (other types like _bar_, _pie_ and
+ _gauges_ are to be added shortly)
+- Examine all the metrics by hovering over them with your cursor
+- Use intuitive tooling and shortcuts to pan, zoom or highlight your charts
+- On highlight, ease access
+ to [Metric Correlations](https://github.com/netdata/netdata/blob/master/docs/cloud/insights/metric-correlations.md) to
+ see other metrics with similar patterns
+- Have the dimensions sorted based on name or value
+- View information about the chart, its plugin, context, and type
+- Get the chart status and possible errors. On top, reload functionality
+
+These charts will available
+on [Overview tab](https://github.com/netdata/netdata/blob/master/docs/cloud/visualize/overview.md), Single Node view and
+on your [Custom Dashboards](https://github.com/netdata/netdata/blob/master/docs/cloud/visualize/dashboards.md).
+
+## Overview
+
+Have a look at the can see the overall look and feel of the charts for both with a composite chart from
+the [Overview tab](https://github.com/netdata/netdata/blob/master/docs/cloud/visualize/overview.md) and a simple chart
+from the single node view:
+
+![NRve6zr325.gif](https://images.zenhubusercontent.com/60b4ebb03f4163193ec31819/5ecaf5ec-1229-480e-b122-62f63e9df227)
+
+With a quick glance you have immediate information available at your disposal:
+
+- Chart title and units
+- Action bars
+- Chart area
+- Legend with dimensions
+
+## Play, Pause and Reset
+
+Your charts are controlled using the
+available [Time controls](https://github.com/netdata/netdata/blob/master/docs/dashboard/visualization-date-and-time-controls.mdx#time-controls).
+Besides these, when interacting with the chart you can also activate these controls by:
+
+- hovering over any chart to temporarily pause it - this momentarily switches time control to Pause, so that you can
+ hover over a specific timeframe. When moving out of the chart time control will go back to Play (if it was it's
+ previous state)
+- clicking on the chart to lock it - this enables the Pause option on the time controls, to the current timeframe. This
+ is if you want to jump to a different chart to look for possible correlations.
+- double clicking to release a previously locked chart - move the time control back to Play
+
+ ![23CHKCPnnJ.gif](https://images.zenhubusercontent.com/60b4ebb03f4163193ec31819/0b1e111e-df44-4d92-b2e3-be5cfd9db8df)
+
+| Interaction | Keyboard/mouse | Touchpad/touchscreen | Time control |
+|:------------------|:---------------|:---------------------|:----------------------|
+| **Pause** a chart | `hover` | `n/a` | Temporarily **Pause** |
+| **Stop** a chart | `click` | `tap` | **Pause** |
+| **Reset** a chart | `double click` | `n/a` | **Play** |
+
+Note: These interactions are available when the default "Pan" action is used. Other actions are accessible via
+the [Exploration action bar](#exploration-action-bar).
+
+## Title and chart action bar
+
+When you start interacting with a chart, you'll notice valuable information on the top bar. You will see information
+from the chart title to a chart action bar.
+
+The elements that you can find on this top bar are:
+
+- Netdata icon: this indicates that data is continuously being updated, this happens
+ if [Time controls](https://github.com/netdata/netdata/blob/master/docs/dashboard/visualization-date-and-time-controls.mdx#time-controls)
+ are in Play or Force Play mode
+- Chart status icon: indicates the status of the chart. Possible values are: Loading, Timeout, Error or No data
+- Chart title: on the chart title you can see the title together with the metric being displayed, as well as the unit of
+ measurement
+- Chart action bar: here you'll have access to chart info, change chart types, enables fullscreen mode, and the ability
+ to add the chart to a custom dashboard
+
+![image.png](https://images.zenhubusercontent.com/60b4ebb03f4163193ec31819/c8f5f0bd-5f84-4812-970b-0e4340f4773b)
+
+### Chart action bar
+
+On this bar you have access to immediate actions over the chart, the available actions are:
+
+- Chart info: you will be able to get more information relevant to the chart you are interacting with
+- Chart type: change the chart type from _line_, _stacked_ or _area_
+- Enter fullscreen mode: allows you expand the current chart to the full size of your screen
+- Add chart to dashboard: This allows you to add the chart to an existing custom dashboard or directly create a new one
+ that includes the chart.
+
+<img src="https://images.zenhubusercontent.com/60b4ebb03f4163193ec31819/65ac4fc8-3d8d-4617-8234-dbb9b31b4264" width="40%" height="40%" />
+
+## Exploration action bar
+
+When exploring the chart you will see a second action bar. This action bar is there to support you on this task. The
+available actions that you can see are:
+
+- Pan
+- Highlight
+- Horizontal and Vertical zooms
+- In-context zoom in and out
+
+<img src="https://images.zenhubusercontent.com/60b4ebb03f4163193ec31819/0417ad66-fcf6-42d5-9a24-e9392ec51f87" width="40%" height="40%" />
+
+### Pan
+
+Drag your mouse/finger to the right to pan backward through time, or drag to the left to pan forward in time. Think of
+it like pushing the current timeframe off the screen to see what came before or after.
+
+| Interaction | Keyboard | Mouse | Touchpad/touchscreen |
+|:------------|:---------|:---------------|:---------------------|
+| **Pan** | `n/a` | `click + drag` | `touch drag` |
+
+### Highlight
+
+Selecting timeframes is useful when you see an interesting spike or change in a chart and want to investigate further,
+from looking at the same period of time on other charts/sections or triggering actions to help you troubleshoot with an
+in-context action bar to help you troubleshoot (currently only available on
+Single Node view). The available actions:
+
+-
+
+run [Metric Correlations](https://github.com/netdata/netdata/blob/master/docs/cloud/insights/metric-correlations.md)
+
+- zoom in on the selected timeframe
+
+[Metric Correlations](https://github.com/netdata/netdata/blob/master/docs/cloud/insights/metric-correlations.md)
+will only be available if you respect the timeframe selection limitations. The selected duration pill together with the
+button state helps visualize this.
+
+<img src="https://images.zenhubusercontent.com/60b4ebb03f4163193ec31819/2ffc157d-0f0f-402e-80bb-5ffa8a2091d5" width="50%" height="50%" />
+
+<p/>
+
+| Interaction | Keyboard/mouse | Touchpad/touchscreen |
+|:-----------------------------------|:---------------------------------------------------------|:---------------------|
+| **Highlight** a specific timeframe | `Alt + mouse selection` or `⌘ + mouse selection` (macOS) | `n/a` |
+
+### Zoom
+
+Zooming in helps you see metrics with maximum granularity, which is useful when you're trying to diagnose the root cause
+of an anomaly or outage. Zooming out lets you see metrics within the larger context, such as the last hour, day, or
+week, which is useful in understanding what "normal" looks like, or to identify long-term trends, like a slow creep in
+memory usage.
+
+The actions above are _normal_ vertical zoom actions. We also provide an horizontal zoom action that helps you focus on
+a
+specific Y-axis area to further investigate a spike or dive on your charts.
+
+![Y5IESOjD3s.gif](https://images.zenhubusercontent.com/60b4ebb03f4163193ec31819/f8722ee8-e69b-426c-8bcb-6cb79897c177)
+
+| Interaction | Keyboard/mouse | Touchpad/touchscreen |
+|:-------------------------------------------|:-------------------------------------|:-----------------------------------------------------|
+| **Zoom** in or out | `Shift + mouse scrollwheel` | `two-finger pinch` <br />`Shift + two-finger scroll` |
+| **Zoom** to a specific timeframe | `Shift + mouse vertical selection` | `n/a` |
+| **Horizontal Zoom** a specific Y-axis area | `Shift + mouse horizontal selection` | `n/a` |
+
+You also have two direct action buttons on the exploration action bar for in-context `Zoom in` and `Zoom out`.
+
+## Other interactions
+
+### Order dimensions legend
+
+The bottom legend of the chart where you can see the dimensions of the chart can now be ordered by:
+
+- Dimension name (Ascending or Descending)
+- Dimension value (Ascending or Descending)
+
+<img src="https://images.zenhubusercontent.com/60b4ebb03f4163193ec31819/d3031c35-37bc-46c1-bcf9-be29dea0b476" width="50%" height="50%" />
+
+### Show and hide dimensions
+
+Hiding dimensions simplifies the chart and can help you better discover exactly which aspect of your system might be
+behaving strangely.
+
+| Interaction | Keyboard/mouse | Touchpad/touchscreen |
+|:---------------------------------------|:----------------|:---------------------|
+| **Show one** dimension and hide others | `click` | `tap` |
+| **Toggle (show/hide)** one dimension | `Shift + click` | `n/a` |
+
+### Resize
+
+To resize the chart, click-and-drag the icon on the bottom-right corner of any chart. To restore the chart to its
+original height,
+double-click the same icon.
+
+![AjqnkIHB9H.gif](https://images.zenhubusercontent.com/60b4ebb03f4163193ec31819/1bcc6a0a-a58e-457b-8a0c-e5d361a3083c)
+
+## What's next?
+
+We recommend you read up on the differences
+between [chart dimensions, contexts, and families](https://github.com/netdata/netdata/blob/master/docs/dashboard/dimensions-contexts-families.mdx)
+to strengthen your understanding of how Netdata organizes its dashboards. Another valuable way to interact with charts
+is to use
+the [date and time controls](https://github.com/netdata/netdata/blob/master/docs/dashboard/visualization-date-and-time-controls.mdx),
+which helps you visualize specific moments of historical metrics.
+
+### Further reading & related information
+
+- Dashboard
+ - [How the dashboard works](https://github.com/netdata/netdata/blob/master/docs/dashboard/how-dashboard-works.mdx)
+ - [Chart dimensions, contexts, and families](https://github.com/netdata/netdata/blob/master/docs/dashboard/dimensions-contexts-families.mdx)
+ - [Date and Time controls](https://github.com/netdata/netdata/blob/master/docs/dashboard/visualization-date-and-time-controls.mdx)
+ - [Customize the standard dashboard](https://github.com/netdata/netdata/blob/master/docs/dashboard/customize.mdx)
+ - [Metric Correlations](https://github.com/netdata/netdata/blob/master/docs/cloud/insights/metric-correlations.md)
+ - [Netdata Agent - Interact with charts](https://github.com/netdata/netdata/blob/master/docs/dashboard/interact-charts.mdx)
diff --git a/docs/cloud/visualize/kubernetes.md b/docs/cloud/visualize/kubernetes.md
new file mode 100644
index 00000000..0ff83970
--- /dev/null
+++ b/docs/cloud/visualize/kubernetes.md
@@ -0,0 +1,154 @@
+---
+title: "Kubernetes visualizations"
+description: "Netdata Cloud features rich, zero-configuration Kubernetes monitoring for the resource utilization and application metrics of Kubernetes (k8s) clusters."
+custom_edit_url: "https://github.com/netdata/netdata/blob/master/docs/cloud/visualize/kubernetes.md"
+sidebar_label: "Kubernetes visualizations"
+learn_status: "Published"
+learn_topic_type: "Concepts"
+learn_rel_path: "Operations/Visualizations"
+---
+
+Netdata Cloud features enhanced visualizations for the resource utilization of Kubernetes (k8s) clusters, embedded in
+the default [Overview](/docs/cloud/visualize/overview/) dashboard.
+
+These visualizations include a health map for viewing the status of k8s pods/containers, in addition to composite charts
+for viewing per-second CPU, memory, disk, and networking metrics from k8s nodes.
+
+## Before you begin
+
+In order to use the Kubernetes visualizations in Netdata Cloud, you need:
+
+- A Kubernetes cluster running Kubernetes v1.9 or newer.
+- A Netdata deployment using the latest version of the [Helm chart](https://github.com/netdata/helmchart), which
+ installs [v1.29.2](https://github.com/netdata/netdata/releases) or newer of the Netdata Agent.
+- To connect your Kubernetes cluster to Netdata Cloud.
+- To enable the feature flag described below.
+
+See our [Kubernetes deployment instructions](/docs/agent/packaging/installer/methods/kubernetes/) for details on
+installation and connecting to Netdata Cloud.
+
+## Available Kubernetes metrics
+
+Netdata Cloud organizes and visualizes the following metrics from your Kubernetes cluster from every container:
+
+- `cpu_limit`: CPU utilization as a percentage of the limit defined by the [pod specification
+ `spec.containers[].resources.limits.cpu`](https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/#resource-requests-and-limits-of-pod-and-container)
+ or a [`LimitRange`
+ object](https://kubernetes.io/docs/tasks/administer-cluster/manage-resources/cpu-default-namespace/#create-a-limitrange-and-a-pod).
+- `cpu`: CPU utilization of the pod/container. 100% usage equals 1 fully-utilized core, 200% equals 2 fully-utilized
+ cores, and so on.
+- `cpu_per_core`: CPU utilization averaged across available cores.
+- `mem_usage_limit`: Memory utilization, without cache, as a percentage of the limit defined by the [pod specification
+ `spec.containers[].resources.limits.memory`](https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/#resource-requests-and-limits-of-pod-and-container)
+ or a [`LimitRange`
+ object](https://kubernetes.io/docs/tasks/administer-cluster/manage-resources/cpu-default-namespace/#create-a-limitrange-and-a-pod).
+- `mem_usage`: Used memory, without cache.
+- `mem`: The sum of `cache` and `rss` (resident set size) memory usage.
+- `writeback`: The size of `dirty` and `writeback` cache.
+- `mem_activity`: Sum of `in` and `out` bandwidth.
+- `pgfaults`: Sum of page fault bandwidth, which are raised when the Kubernetes cluster tries accessing a memory page
+ that is mapped into the virtual address space, but not actually loaded into main memory.
+- `throttle_io`: Sum of `read` and `write` per second across all PVs/PVCs attached to the container.
+- `throttle_serviced_ops`: Sum of the `read` and `write` operations per second across all PVs/PVCs attached to the
+ container.
+- `net.net`: Sum of `received` and `sent` bandwidth per second.
+- `net.packets`: Sum of `multicast`, `received`, and `sent` packets.
+
+When viewing the [health map](#health-map), Netdata Cloud shows the above metrics per container, or aggregated based on
+their associated pods.
+
+When viewing the [composite charts](#composite-charts), Netdata Cloud aggregates metrics from multiple nodes, pods, or
+containers, depending on the grouping chosen. For example, if you group the `cpu_limit` composite chart by
+`k8s_namespace`, the metrics shown will be the average of `cpu_limit` metrics from all nodes/pods/containers that are
+part of that namespace.
+
+## Health map
+
+The health map places each container or pod as a single box, then varies the intensity of its color to visualize the
+resource utilization of specific k8s pods/containers.
+
+![The Kubernetes health map in Netdata
+Cloud](https://user-images.githubusercontent.com/1153921/106964367-39f54100-66ff-11eb-888c-5a04f8abb3d0.png)
+
+Change the health map's coloring, grouping, and displayed nodes to customize your experience and learn more about the
+status of your k8s cluster.
+
+### Color by
+
+Color the health map by choosing an aggregate function to apply to an [available Kubernetes
+metric](#available-kubernetes-metrics), then whether you to display boxes for individual pods or containers.
+
+The default is the _average, of CPU within the configured limit, organized by container_.
+
+### Group by
+
+Group the health map by the `k8s_cluster_id`, `k8s_controller_kind`, `k8s_controller_name`, `k8s_kind`, `k8s_namespace`,
+and `k8s_node_name`. The default is `k8s_controller_name`.
+
+### Filtering
+
+Filtering behaves identically to the [node filter in War Rooms](/docs/cloud/war-rooms#node-filter), with the ability to
+filter pods/containers by `container_id` and `namespace`.
+
+### Detailed information
+
+Hover over any of the pods/containers in the map to display a modal window, which contains contextual information
+and real-time metrics from that resource.
+
+![The modal containing additional information about a k8s
+resource](https://user-images.githubusercontent.com/1153921/106964369-3a8dd780-66ff-11eb-8a8a-a5c8f0d5711f.png)
+
+The **context** tab provides the following details about a container or pod:
+
+- Cluster ID
+- Node
+- Controller Kind
+- Controller Name
+- Pod Name
+- Container
+- Kind
+- Pod UID
+
+This information helps orient you as to where the container/pod operates inside your cluster.
+
+The **Metrics** tab contains charts visualizing the last 15 minutes of the same metrics available in the [color by
+option](#color-by). Use these metrics along with the context, to identify which containers or pods are experiencing
+problematic behavior to investigate further, troubleshoot, and remediate with `kubectl` or another tool.
+
+## Composite charts
+
+The Kubernetes composite charts show real-time and historical resource utilization metrics from nodes, pods, or
+containers within your Kubernetes deployment.
+
+See the [Overview](/docs/cloud/visualize/overview#definition-bar) doc for details on how composite charts work. These
+work similarly, but in addition to visualizing _by dimension_ and _by node_, Kubernetes composite charts can also be
+grouped by the following labels:
+
+- `k8s_cluster_id`
+- `k8s_container_id`
+- `k8s_container_name`
+- `k8s_controller_kind`
+- `k8s_kind`
+- `k8s_namespace`
+- `k8s_node_name`
+- `k8s_pod_name`
+- `k8s_pod_uid`
+
+![Composite charts of Kubernetes metrics in Netdata
+Cloud](https://user-images.githubusercontent.com/1153921/106964370-3a8dd780-66ff-11eb-8858-05b2253b25c6.png)
+
+In addition, when you hover over a composite chart, the colors in the heat map changes as well, so you can see how
+certain pod/container-level metrics change over time.
+
+## Caveats
+
+There are some caveats and known issues with Kubernetes monitoring with Netdata Cloud.
+
+- **No way to remove any nodes** you might have
+ [drained](https://kubernetes.io/docs/tasks/administer-cluster/safely-drain-node/) from your Kubernetes cluster. These
+ drained nodes will be marked "unreachable" and will show up in War Room management screens/dropdowns. The same applies
+ for any ephemeral nodes created and destroyed during horizontal scaling.
+
+## What's next?
+
+For more information about monitoring a k8s cluster with Netdata, see our guide: [_Kubernetes monitoring with Netdata: Overview and visualizations_](/guides/monitor/kubernetes-k8s-netdata/).
diff --git a/docs/cloud/visualize/nodes.md b/docs/cloud/visualize/nodes.md
new file mode 100644
index 00000000..9878b6b1
--- /dev/null
+++ b/docs/cloud/visualize/nodes.md
@@ -0,0 +1,53 @@
+---
+title: "Nodes view"
+description: "See charts from all your nodes in one pane of glass, then dive in to embedded dashboards for granular troubleshooting of ongoing issues."
+custom_edit_url: "https://github.com/netdata/netdata/blob/master/docs/cloud/visualize/nodes.md"
+sidebar_label: "Nodes view"
+learn_status: "Published"
+learn_topic_type: "Concepts"
+learn_rel_path: "Operations/Visualizations"
+---
+
+The Nodes view lets you see and customize key metrics from any number of Agent-monitored nodes and seamlessly navigate
+to any node's dashboard for troubleshooting performance issues or anomalies using Netdata's highly-granular metrics.
+
+![The Nodes view in Netdata
+Cloud](https://user-images.githubusercontent.com/1153921/119035218-2eebb700-b964-11eb-8b74-4ec2df0e457c.png)
+
+Each War Room's Nodes view is populated based on the nodes you added to that specific War Room. Each node occupies a
+single row, first featuring that node's alarm status (yellow for warnings, red for critical alarms) and operating
+system, some essential information about the node, followed by columns of user-defined key metrics represented in
+real-time charts.
+
+Use the [Overview](https://github.com/netdata/netdata/blob/master/docs/cloud/visualize/overview.md) for monitoring an infrastructure in real time using
+composite charts and Netdata's familiar dashboard UI.
+
+Check the [War Room docs](https://github.com/netdata/netdata/blob/master/docs/cloud/war-rooms.md) for details on the utility bar, which contains the [node
+filter](https://github.com/netdata/netdata/blob/master/docs/cloud/war-rooms.md#node-filter) and the [timeframe
+selector](https://github.com/netdata/netdata/blob/master/docs/cloud/war-rooms.md#play-pause-force-play-and-timeframe-selector).
+
+## Add and customize metrics columns
+
+Add more metrics columns by clicking the gear icon. Choose the context you'd like to add, give it a relevant name, and
+select whether you want to see all dimensions (the default), or only the specific dimensions your team is interested in.
+
+Click the gear icon and hover over any existing charts, then click the pencil icon. This opens a panel to
+edit that chart. Edit the context, its title, add or remove dimensions, or delete the chart altogether.
+
+These customizations appear for anyone else with access to that War Room.
+
+## See more metrics in Netdata Cloud
+
+If you want to add more metrics to your War Rooms and they don't show up when you add new metrics to Nodes, you likely
+need to configure those nodes to collect from additional data sources. See our [collectors doc](https://github.com/netdata/netdata/blob/master/docs/collect/enable-configure.md)
+to learn how to use dozens of pre-installed collectors that can instantly collect from your favorite services and applications.
+
+If you want to see up to 30 days of historical metrics in Cloud (and more on individual node dashboards), read our guide
+on [long-term storage of historical metrics](https://github.com/netdata/netdata/blob/master/docs/guides/longer-metrics-storage.md). Also, see our
+[calculator](/docs/store/change-metrics-storage#calculate-the-system-resources-RAM-disk-space-needed-to-store-metrics)
+for finding the disk and RAM you need to store metrics for a certain period of time.
+
+## What's next?
+
+Now that you know how to view your nodes at a glance, learn how to [track active
+alarms](https://github.com/netdata/netdata/blob/master/docs/cloud/alerts-notifications/view-active-alerts.mdx) with the Alerts Smartboard.
diff --git a/docs/cloud/visualize/overview.md b/docs/cloud/visualize/overview.md
new file mode 100644
index 00000000..35c07656
--- /dev/null
+++ b/docs/cloud/visualize/overview.md
@@ -0,0 +1,250 @@
+---
+title: "Home, Overview and Single Node view"
+description: >-
+ "The Home tab automatically presents relevant information of your War Room, the Overview uses composite
+ charts from all the nodes in a given War Room and Single Node view provides a look at a specific Node"
+custom_edit_url: "https://github.com/netdata/netdata/blob/master/docs/cloud/visualize/overview.md"
+sidebar_label: "Home, Overview and Single Node view"
+learn_status: "Published"
+learn_topic_type: "Concepts"
+learn_rel_path: "Operations/Visualizations"
+---
+
+## Home
+
+The Home tab provides a predefined dashboard of relevant information about entities in the War Room.
+
+This tab will
+automatically present summarized information in an easily digestible display. You can see information about your
+nodes, data collection and retention stats, alerts, users and dashboards.
+
+## Overview
+
+The Overview tab is another great way to monitor infrastructure using Netdata Cloud. While the interface might look
+similar to local
+dashboards served by an Agent Overview uses **composite charts**.
+These charts display real-time aggregated metrics from all the nodes (or a filtered selection) in a given War Room.
+
+With Overview's composite charts, you can see your infrastructure from a single pane of glass, discover trends or
+anomalies, then drill down by grouping metrics by node and jumping to single-node dashboards for root cause analysis.
+
+## Single Node view
+
+The Single Node view dashboard engine is the same as the Overview, meaning that it also uses **composite charts**, and
+displays real-time aggregated metrics from a specific node.
+
+As mentioned above, the interface is similar to local dashboards served by an Agent but this dashboard also uses *
+*composite charts** which, in the case of a single node, will aggregate
+multiple chart _instances_ belonging to a context into a single chart. For example, on `disk.io` context it will get
+into a single chart an aggregated view of each disk the node has.
+
+Further tools provided in composite chart [definiton bar](/docs/cloud/visualize/overview#definition-bar) will allow you
+to explore in more detail what is happening on each _instance_.
+
+## Before you get started
+
+Only nodes with v1.25.0-127 or later of the the [open-source Netdata](https://github.com/netdata/netdata) monitoring
+agent can contribute to composite charts. If your node(s) use an earlier version of Netdata, you will see them marked as
+**needs upgrade** in various dropdowns.
+
+See our [update docs](https://github.com/netdata/netdata/blob/master/packaging/installer/UPDATE.md) for the preferred
+update method based on how you installed
+Netdata.
+
+## Composite charts
+
+The Overview uses composite charts, which aggregate metrics from all the nodes (or a filtered selection) in a given War
+Room.
+
+## Definition bar
+
+Each composite chart has a definition bar to provide information about the following:
+
+* Grouping option
+* Aggregate function to be applied in case multiple data sources exist
+* Instances
+* Nodes
+* Dimensions, and
+* Aggregate function over time to be applied if one point in the chart consists of multiple data points aggregated
+
+### Group by dimension, node, or chart
+
+Click on the **dimension** dropdown to change how a composite chart groups metrics.
+
+The default option is by _dimension_, so that each line/area in the visualization is the aggregation of a single
+dimension.
+This provides a per dimension view of the data from all the nodes in the War Room, taking into account filtering
+criteria if defined.
+
+A composite chart grouped by _node_ visualizes a single metric across contributing nodes. If the composite chart has
+five
+contributing nodes, there will be five lines/areas. This is typically an absolute value of the sum of the dimensions
+over each node but there
+are some opinionated-but-valuable exceptions where a specific dimension is selected.
+Grouping by nodes allows you to quickly understand which nodes in your infrastructure are experiencing anomalous
+behavior.
+
+A composite chart grouped by _instance_ visualizes each instance of one software or hardware on a node and displays
+these as a separate dimension. By grouping the
+`disk.io` chart by _instance_, you can visualize the activity of each disk on each node that contributes to the
+composite
+chart.
+
+Another very pertinent example is composite charts over contexts related to cgroups (VMs and containers). You have the
+means to change the default group by or apply filtering to
+get a better view into what data your are trying to analyze. For example, if you change the group by to _instance_ you
+get a view with the data of all the instances (cgroups) that
+contribute to that chart. Then you can use further filtering tools to focus the data that is important to you and even
+save the result to your own dashboards.
+
+![image](https://user-images.githubusercontent.com/82235632/201902017-04b76701-0ff9-4498-aa9b-6d507b567bea.png)
+
+### Aggregate functions over data sources
+
+Each chart uses an opinionated-but-valuable default aggregate function over the data sources. For example,
+the `system.cpu` chart shows the
+average for each dimension from every contributing chart, while the `net.net` chart shows the sum for each dimension
+from every contributing chart, which can also come from multiple networking interfaces.
+
+The following aggregate functions are available for each selected dimension:
+
+- **Average**: Displays the average value from contributing nodes. If a composite chart has 5 nodes with the following
+ values for the `out` dimension&mdash;`-2.1`, `-5.5`, `-10.2`, `-15`, `-0.1`&mdash;the composite chart displays a
+ value of `−6.58`.
+- **Sum**: Displays the sum of contributed values. Using the same nodes, dimension, and values as above, the composite
+ chart displays a metric value of `-32.9`.
+- **Min**: Displays a minimum value. For dimensions with positive values, the min is the value closest to zero. For
+ charts with negative values, the min is the value with the largest magnitude.
+- **Max**: Displays a maximum value. For dimensions with positive values, the max is the value with the largest
+ magnitude. For charts with negative values, the max is the value closet to zero.
+
+### Dimensions
+
+Select which dimensions to display on the composite chart. You can choose **All dimensions**, a single dimension, or any
+number of dimensions available on that context.
+
+### Instances
+
+Click on **X Instances** to display a dropdown of instances and nodes contributing to that composite chart. Each line in
+the
+dropdown displays an instance name and the associated node's hostname.
+
+### Nodes
+
+Click on **X Nodes** to display a dropdown of nodes contributing to that composite chart. Each line displays a hostname
+to help you identify which nodes contribute to a chart. You can also use this component to filter nodes directly on the
+chart.
+
+If one or more nodes can't contribute to a given chart, the definition bar shows a warning symbol plus the number of
+affected nodes, then lists them in the dropdown along with the associated error. Nodes might return errors because of
+networking issues, a stopped `netdata` service, or because that node does not have any metrics for that context.
+
+### Aggregate functions over time
+
+When the granularity of the data collected is higher than the plotted points on the chart an aggregation function over
+time
+is applied. By default the aggregation applied is _average_ but the user can choose different options from the
+following:
+
+* Min
+* Max
+* Average
+* Sum
+* Incremental sum (Delta)
+* Standard deviation
+* Median
+* Single exponential smoothing
+* Double exponential smoothing
+* Coefficient variation
+* Trimmed Median `*`
+* Trimmed Mean `*`
+* Percentile `**`
+
+:::info
+
+- `*` For **Trimmed Median and Mean** you can choose the percentage of data tha you want to focus on: 1%, 2%, 3%, 5%,
+ 10%, 15%, 20% and 25%.
+- `**` For **Percentile** you can specify the percentile you want to focus on: 25th, 50th, 75th, 80th, 90th, 95th, 97th,
+ 98th and 99th.
+
+:::
+
+For more details on each, you can refer to our Agent's HTTP API details
+on [Data Queries - Data Grouping](/docs/agent/web/api/queries#data-grouping).
+
+### Reset to defaults
+
+Click on the 3-dot icon (**⋮**) on any chart, then **Reset to Defaults**, to reset the definition bar to its initial
+state.
+
+## Jump to single-node dashboards
+
+Click on **X Charts**/**X Nodes** to display one of the two dropdowns that list the charts and nodes contributing to a
+given composite chart. For example, the nodes dropdown.
+
+![The nodes dropdown in a composite
+chart](https://user-images.githubusercontent.com/1153921/99305049-7c019b80-2810-11eb-942a-8ebfcf236b7f.png)
+
+To jump to a single-node dashboard, click on the link icon <img class="img__inline img__inline--link"
+src="https://user-images.githubusercontent.com/1153921/95762109-1d219300-0c62-11eb-8daa-9ba509a8e71c.png" /> next to the
+node you're interested in.
+
+The single-node dashboard opens in a new tab. From there, you can continue to troubleshoot or run [Metric
+Correlations](https://github.com/netdata/netdata/blob/master/docs/cloud/insights/metric-correlations.md) for faster root
+cause analysis.
+
+## Add composite charts to a dashboard
+
+Click on the 3-dot icon (**⋮**) on any chart, then click on **Add to Dashboard**. Click the **+** button for any
+dashboard you'd like to add this composite chart to, or create a new dashboard an initiate it with your chosen chart by
+entering the name and clicking **New Dashboard**.
+
+## Interacting with composite charts: pan, zoom, and resize
+
+You can interact with composite charts as you would with other Netdata charts. You can use the controls beneath each
+chart to pan, zoom, or resize the chart, or use various combinations of the keyboard and mouse. See
+the [chart interaction doc](https://github.com/netdata/netdata/blob/master/docs/dashboard/interact-charts.mdx) for
+details.
+
+## Menu
+
+The Overview uses a similar menu to local Agent dashboards and single-node dashboards in Netdata Cloud, with sections
+and sub-menus aggregated from every contributing node. For example, even if only two nodes actively collect from and
+monitor an Apache web server, the **Apache** section still appears and displays composite charts from those two nodes.
+
+![A menu in the Overview
+screen](https://user-images.githubusercontent.com/1153921/95785094-fa0ad980-0c89-11eb-8328-2ff11ac630b4.png)
+
+One difference between the Overview's menu and those found in single-node dashboards or local Agent dashboards is that
+the Overview condenses multiple services, families, or instances into single sections, sub-menus, and associated charts.
+
+For services, let's say you have two concurrent jobs with the [web_log
+collector](https://github.com/netdata/go.d.plugin/blob/master/modules/weblog/README.md), one for Apache and another for
+Nginx. A single-node or
+local dashboard shows two section, **web_log apache** and **web_log nginx**, whereas the Overview condenses these into a
+single **web_log** section containing composite charts from both jobs.
+
+The Overview also consdenses multiple families or multiple instances into a single **all** sub-menu and associated
+charts. For example, if Node A has 5 disks, and Node B has 3, each disk contributes to a single `disk.io` composite
+chart. The utility bar should show that there are 8 charts from 2 nodes contributing to that chart.
+
+This action applies to disks, network devices, and other metric types that involve multiple instances of a piece of
+hardware or software. The Overview currently does not display metrics from filesystems. Read more about [families and
+instances](https://github.com/netdata/netdata/blob/master/docs/dashboard/dimensions-contexts-families.mdx)
+
+## Persistence of composite chart settings
+
+When you change a composite chart via its definition bar, Netdata Cloud persists these settings in a query string
+attached to the URL in your browser. You can "save" these settings by bookmarking this particular URL, or share it with
+colleagues by having them copy-paste it into their browser.
+
+## What's next?
+
+For another way to view an infrastructure from a high level, see
+the [Nodes view](https://github.com/netdata/netdata/blob/master/docs/cloud/visualize/nodes.md).
+
+If you need a refresher on how Netdata's charts work, see our doc
+on [interacting with charts](https://github.com/netdata/netdata/blob/master/docs/dashboard/interact-charts.mdx).
+
+Or, get more granular with configuring how you monitor your infrastructure
+by [building new dashboards](https://github.com/netdata/netdata/blob/master/docs/cloud/visualize/dashboards.md).
diff --git a/docs/cloud/war-rooms.md b/docs/cloud/war-rooms.md
new file mode 100644
index 00000000..99f9e368
--- /dev/null
+++ b/docs/cloud/war-rooms.md
@@ -0,0 +1,162 @@
+---
+title: "War Rooms"
+description: >-
+ "Netdata Cloud uses War Rooms to group related nodes and create insightful compositedashboards based on
+ their aggregate health and performance."
+custom_edit_url: "https://github.com/netdata/netdata/blob/master/docs/cloud/war-rooms.md"
+sidebar_label: "War Rooms"
+learn_status: "Published"
+learn_topic_type: "Tasks"
+learn_rel_path: "Operations"
+---
+
+War Rooms organize your connected nodes and provide infrastructure-wide dashboards using real-time metrics and
+visualizations.
+
+Once you add nodes to a Space, all of your nodes will be visible in the _All nodes_ War Room. This is a special War Room
+which gives you an overview of all of your nodes in this particular space. Then you can create functional separations of
+your nodes into more War Rooms. Every War Room has its own dashboards, navigation, indicators, and management tools.
+
+![An example War Room](/img/cloud/main-page.png)
+
+## Navigation
+
+### Switching between views - static tabs
+
+Every War Rooms provides multiple views. Each view focus on a particular area/subject of the nodes which you monitor in
+this War Rooms. Let's explore what view you have available:
+
+- The default view for any War Room is
+ the [Home tab](https://github.com/netdata/netdata/blob/master/docs/cloud/visualize/overview.md#home), which give you
+ an overview
+ of this space. Here you can see the number of Nodes claimed, data retention statics, user particate, alerts and more
+
+- The second and most important view is
+ the [Overview tab](https://github.com/netdata/netdata/blob/master/docs/cloud/visualize/overview.md#overview) which
+ uses composite
+ charts to display real-time metrics from every available node in a given War Room.
+
+- The [Nodes tab](https://github.com/netdata/netdata/blob/master/docs/cloud/visualize/nodes.md) gives you the ability to
+ see the status (offline or online), host details
+ , alarm status and also a short overview of some key metrics from all your nodes at a glance.
+
+- [Kubernetes tab](https://github.com/netdata/netdata/blob/master/docs/cloud/visualize/kubernetes.md) is a logical
+ grouping of charts regards to your Kubernetes clusters.
+ It contains a subset of the charts available in the _Overview tab_
+
+-
+
+The [Dashboards tab](https://github.com/netdata/netdata/blob/master/docs/cloud/visualize/dashboards.md)
+gives you the ability to have tailored made views of
+specific/targeted interfaces for your infrastructure using any number of charts from any number of nodes.
+
+- The **Alerts tab** provides you with an overview for all the active alerts you receive for the nodes in this War Room,
+ you can also see alla the alerts that are configured to be triggered in any given moment.
+
+- The **Anomalies tab** is dedicated to
+ the [Anomaly Advisor](https://github.com/netdata/netdata/blob/master/docs/cloud/insights/anomaly-advisor.mdx) tool
+
+### Non static tabs
+
+If you open
+a [new dashboard](https://github.com/netdata/netdata/blob/master/docs/cloud/visualize/dashboards.md),
+jump to a single-node dashboard, or navigate to a dedicated alert page they will open in a new War Room tab.
+
+Tabs can be rearranged with drag-and-drop or closed with the **X** button. Open tabs persist between sessions, so you
+can always come right back to your preferred setup.
+
+### Play, pause, force play, and timeframe selector
+
+A War Room has three different states: playing, paused, and force playing. The default playing state refreshes charts
+every second as long as the browser tab is in
+focus. [Interacting with a chart](https://github.com/netdata/netdata/blob/master/docs/dashboard/interact-charts.mdx)
+pauses
+the War Room. Once the tab loses focus, charts pause automatically.
+
+The top navigation bar features a play/pause button to quickly change the state, and a dropdown to select **Force Play**
+, which keeps charts refreshing, potentially at the expense of system performance.
+
+Next to the play/pause button is the timeframe selector, which helps you select a precise window of metrics data to
+visualize. By default, all visualizations in Netdata Cloud show the last 15 minutes of metrics data.
+
+Use the **Quick Selector** to visualize metrics from predefined timeframes, or use the input field below to enter a
+number and an appropriate unit of time. The calendar allows you to select multiple days of metrics data.
+
+Click **Apply** to re-render all visualizations with new metrics data streamed to your browser from each distributed
+node. Click **Clear** to remove any changes and apply the default 15-minute timeframe.
+
+The fields beneath the calendar display the beginning and ending timestamps your selected timeframe.
+
+### Node filter
+
+The node filter allows you to quickly filter the nodes visualized in a War Room's views. It appears on all views, but
+not on single-node dashboards.
+
+![The node filter](https://user-images.githubusercontent.com/12612986/172674440-df224058-2b2c-41da-bb45-f4eb82e342e5.png)
+
+## War Room organization
+
+We recommend a few strategies for organizing your War Rooms.
+
+**Service, purpose, location, etc.**: You can group War Rooms by a service (think Nginx, MySQL, Pulsar, and so on),
+their purpose (webserver, database, application), their physical location, whether they're baremetal or a Docker
+container, the PaaS/cloud provider it runs on, and much more. This allows you to see entire slices of your
+infrastructure by moving from one War Room to another.
+
+**End-to-end apps/services**: If you have a user-facing SaaS product, or an internal service that said product relies
+on, you may want to monitor that entire stack in a single War Room. This might include Kubernetes clusters, Docker
+containers, proxies, databases, web servers, brokers, and more. End-to-end War Rooms are valuable tools for ensuring the
+health and performance of your organization's essential services.
+
+**Incident response**: You can also create new War Rooms as one of the first steps in your incident response process.
+For example, you have a user-facing web app that relies on Apache Pulsar for a message queue, and one of your nodes
+using the [Pulsar collector](https://github.com/netdata/go.d.plugin/blob/master/modules/pulsar/README.md) begins
+reporting a suspiciously low messages rate. You can create a War Room called `$year-$month-$day-pulsar-rate`, add all
+your Pulsar nodes in addition to nodes they connect to, and begin diagnosing the root cause in a War Room optimized for
+getting to resolution as fast as possible.
+
+## Add War Rooms
+
+To add new War Rooms to any Space, click on the green plus icon **+** next the **War Rooms** heading. on the left (
+space's) sidebar.
+
+In the panel, give the War Room a name and description, and choose whether it's public or private. Anyone in your Space
+can join public War Rooms, but can only join private War Rooms with an invitation.
+
+## Manage War Rooms
+
+All the users and nodes involved in a particular space can potential be part of a War Room.
+
+Any user can change simple settings of a War room, like the name or the users participating in it. Click on the gear
+icon of the War Room's name in the top of the page to do that. A sidebar will open with options for this War Room:
+
+1. To _change a War Room's name, description, or public/private status_, click on **War Room** tab of the sidebar.
+
+2. To _include an existing node_ to a War Room or _connect a new node*_ click on **Nodes** tab of the sidebar. Choose
+ any
+ connected node you want to add to this War Room by clicking on the checkbox next to its hostname, then click **+ Add
+ **
+ at the top of the panel.
+
+3. To _add existing users to a War Room_, click on **Add Users**. See
+ our [invite doc](https://github.com/netdata/netdata/blob/master/docs/cloud/manage/invite-your-team.md)
+ for details on inviting new users to your Space in Netdata Cloud.
+
+:::note
+\* This action requires admin rights for this space
+:::
+
+### More actions
+
+To _view or remove nodes_ in a War Room, click on **Nodes view**. To remove a node from the current War Room, click on
+the **🗑** icon.
+
+:::info
+Removing a node from a War Room does not remove it from your Space.
+:::
+
+## What's next?
+
+Once you've figured out an organizational structure that works for your team, learn more about how you can use Netdata
+Cloud to monitor distributed nodes
+using [real-time composite charts](https://github.com/netdata/netdata/blob/master/docs/cloud/visualize/overview.md).
diff --git a/docs/collect/application-metrics.md b/docs/collect/application-metrics.md
index c9bc4e2c..454ed95a 100644
--- a/docs/collect/application-metrics.md
+++ b/docs/collect/application-metrics.md
@@ -2,7 +2,10 @@
title: "Collect application metrics with Netdata"
sidebar_label: "Application metrics"
description: "Monitor and troubleshoot every application on your infrastructure with per-second metrics, zero configuration, and meaningful charts."
-custom_edit_url: https://github.com/netdata/netdata/edit/master/docs/collect/application-metrics.md
+custom_edit_url: "https://github.com/netdata/netdata/edit/master/docs/collect/application-metrics.md"
+learn_status: "Published"
+learn_topic_type: "Concepts"
+learn_rel_path: "Concepts"
-->
# Collect application metrics with Netdata
@@ -12,7 +15,7 @@ web servers, databases, message brokers, email servers, search platforms, and mu
pre-installed with every Netdata Agent and usually require zero configuration. Netdata also collects and visualizes
resource utilization per application on Linux systems using `apps.plugin`.
-[**apps.plugin**](/collectors/apps.plugin/README.md) looks at the Linux process tree every second, much like `top` or
+[**apps.plugin**](https://github.com/netdata/netdata/blob/master/collectors/apps.plugin/README.md) looks at the Linux process tree every second, much like `top` or
`ps fax`, and collects resource utilization information on every running process. By reading the process tree, Netdata
shows CPU, disk, networking, processes, and eBPF for every application or Linux user. Unlike `top` or `ps fax`, Netdata
adds a layer of meaningful visualization on top of the process tree metrics, such as grouping applications into useful
@@ -21,43 +24,43 @@ charts under **Users**, and per-user group charts under **User Groups**.
Our most popular application collectors:
-- [Prometheus endpoints](https://learn.netdata.cloud/docs/agent/collectors/go.d.plugin/modules/prometheus): Gathers
+- [Prometheus endpoints](https://github.com/netdata/go.d.plugin/blob/master/modules/prometheus/README.md): Gathers
metrics from one or more Prometheus endpoints that use the OpenMetrics exposition format. Auto-detects more than 600
endpoints.
-- [Web server logs (Apache, NGINX)](https://learn.netdata.cloud/docs/agent/collectors/go.d.plugin/modules/weblog/):
+- [Web server logs (Apache, NGINX)](https://github.com/netdata/go.d.plugin/blob/master/modules/weblog/README.md):
Tail access logs and provide very detailed web server performance statistics. This module is able to parse 200k+
rows in less than half a second.
-- [MySQL](https://learn.netdata.cloud/docs/agent/collectors/go.d.plugin/modules/mysql/): Collect database global,
+- [MySQL](https://github.com/netdata/go.d.plugin/blob/master/modules/mysql/README.md): Collect database global,
replication, and per-user statistics.
-- [Redis](https://learn.netdata.cloud/docs/agent/collectors/go.d.plugin/modules/redis): Monitor database status by
+- [Redis](https://github.com/netdata/go.d.plugin/blob/master/modules/redis/README.md): Monitor database status by
reading the server's response to the `INFO` command.
-- [Apache](https://learn.netdata.cloud/docs/agent/collectors/go.d.plugin/modules/apache/): Collect Apache web server
+- [Apache](https://github.com/netdata/go.d.plugin/blob/master/modules/apache/README.md): Collect Apache web server
performance metrics via the `server-status?auto` endpoint.
-- [Nginx](https://learn.netdata.cloud/docs/agent/collectors/go.d.plugin/modules/nginx/): Monitor web server status
+- [Nginx](https://github.com/netdata/go.d.plugin/blob/master/modules/nginx/README.md): Monitor web server status
information by gathering metrics via `ngx_http_stub_status_module`.
-- [Postgres](https://learn.netdata.cloud/docs/agent/collectors/go.d.plugin/modules/postgres): Collect database health
+- [Postgres](https://github.com/netdata/go.d.plugin/blob/master/modules/postgres/README.md): Collect database health
and performance metrics.
-- [ElasticSearch](https://learn.netdata.cloud/docs/agent/collectors/go.d.plugin/modules/elasticsearch): Collect search
+- [ElasticSearch](https://github.com/netdata/go.d.plugin/blob/master/modules/elasticsearch/README.md): Collect search
engine performance and health statistics. Optionally collects per-index metrics.
-- [PHP-FPM](https://learn.netdata.cloud/docs/agent/collectors/go.d.plugin/modules/phpfpm/): Collect application summary
+- [PHP-FPM](https://github.com/netdata/go.d.plugin/blob/master/modules/phpfpm/README.md): Collect application summary
and processes health metrics by scraping the status page (`/status?full`).
-Our [supported collectors list](/collectors/COLLECTORS.md#service-and-application-collectors) shows all Netdata's
+Our [supported collectors list](https://github.com/netdata/netdata/blob/master/collectors/COLLECTORS.md#service-and-application-collectors) shows all Netdata's
application metrics collectors, including those for containers/k8s clusters.
## Collect metrics from applications running on Windows
Netdata is fully capable of collecting and visualizing metrics from applications running on Windows systems. The only
-caveat is that you must [install Netdata](/docs/get-started.mdx) on a separate system or a compatible VM because there
+caveat is that you must [install Netdata](https://github.com/netdata/netdata/blob/master/docs/get-started.mdx) on a separate system or a compatible VM because there
is no native Windows version of the Netdata Agent.
Once you have Netdata running on that separate system, you can follow the [enable and configure
-doc](/docs/collect/enable-configure.md) to tell the collector to look for exposed metrics on the Windows system's IP
+doc](https://github.com/netdata/netdata/blob/master/docs/collect/enable-configure.md) to tell the collector to look for exposed metrics on the Windows system's IP
address or hostname, plus the applicable port.
For example, you have a MySQL database with a root password of `my-secret-pw` running on a Windows system with the IP
address 203.0.113.0. you can configure the [MySQL
-collector](https://learn.netdata.cloud/docs/agent/collectors/go.d.plugin/modules/mysql) to look at `203.0.113.0:3306`:
+collector](https://github.com/netdata/go.d.plugin/blob/master/modules/mysql/README.md) to look at `203.0.113.0:3306`:
```yml
jobs:
@@ -66,16 +69,16 @@ jobs:
```
This same logic applies to any application in our [supported collectors
-list](/collectors/COLLECTORS.md#service-and-application-collectors) that can run on Windows.
+list](https://github.com/netdata/netdata/blob/master/collectors/COLLECTORS.md#service-and-application-collectors) that can run on Windows.
## What's next?
-If you haven't yet seen the [supported collectors list](/collectors/COLLECTORS.md) give it a once-over for any
+If you haven't yet seen the [supported collectors list](https://github.com/netdata/netdata/blob/master/collectors/COLLECTORS.md) give it a once-over for any
additional applications you may want to monitor using Netdata's native collectors, or the [generic Prometheus
-collector](https://learn.netdata.cloud/docs/agent/collectors/go.d.plugin/modules/prometheus).
+collector](https://github.com/netdata/go.d.plugin/blob/master/modules/prometheus/README.md).
Collecting all the available metrics on your nodes, and across your entire infrastructure, is just one piece of the
puzzle. Next, learn more about Netdata's famous real-time visualizations by [seeing an overview of your
-infrastructure](/docs/visualize/overview-infrastructure.md) using Netdata Cloud.
+infrastructure](https://github.com/netdata/netdata/blob/master/docs/visualize/overview-infrastructure.md) using Netdata Cloud.
diff --git a/docs/collect/container-metrics.md b/docs/collect/container-metrics.md
index 5d145362..b6b6a432 100644
--- a/docs/collect/container-metrics.md
+++ b/docs/collect/container-metrics.md
@@ -2,7 +2,10 @@
title: "Collect container metrics with Netdata"
sidebar_label: "Container metrics"
description: "Use Netdata to collect per-second utilization and application-level metrics from Linux/Docker containers and Kubernetes clusters."
-custom_edit_url: https://github.com/netdata/netdata/edit/master/docs/collect/container-metrics.md
+custom_edit_url: "https://github.com/netdata/netdata/edit/master/docs/collect/container-metrics.md"
+learn_status: "Published"
+learn_topic_type: "Concepts"
+learn_rel_path: "Concepts"
-->
# Collect container metrics with Netdata
@@ -10,35 +13,35 @@ custom_edit_url: https://github.com/netdata/netdata/edit/master/docs/collect/con
Thanks to close integration with Linux cgroups and the virtual files it maintains under `/sys/fs/cgroup`, Netdata can
monitor the health, status, and resource utilization of many different types of Linux containers.
-Netdata uses [cgroups.plugin](/collectors/cgroups.plugin/README.md) to poll `/sys/fs/cgroup` and convert the raw data
+Netdata uses [cgroups.plugin](https://github.com/netdata/netdata/blob/master/collectors/cgroups.plugin/README.md) to poll `/sys/fs/cgroup` and convert the raw data
into human-readable metrics and meaningful visualizations. Through cgroups, Netdata is compatible with **all Linux
containers**, such as Docker, LXC, LXD, Libvirt, systemd-nspawn, and more. Read more about [Docker-specific
monitoring](#collect-docker-metrics) below.
Netdata also has robust **Kubernetes monitoring** support thanks to a
-[Helmchart](/packaging/installer/methods/kubernetes.md) to automate deployment, collectors for k8s agent services, and
+[Helmchart](https://github.com/netdata/netdata/blob/master/packaging/installer/methods/kubernetes.md) to automate deployment, collectors for k8s agent services, and
robust [service discovery](https://github.com/netdata/agent-service-discovery/#service-discovery) to monitor the
services running inside of pods in your k8s cluster. Read more about [Kubernetes
monitoring](#collect-kubernetes-metrics) below.
A handful of additional collectors gather metrics from container-related services, such as
-[dockerd](/collectors/python.d.plugin/dockerd/README.md) or [Docker
-Engine](https://learn.netdata.cloud/docs/agent/collectors/go.d.plugin/modules/docker_engine/). You can find all
+[dockerd](https://github.com/netdata/go.d.plugin/blob/master/modules/docker/README.md) or [Docker
+Engine](https://github.com/netdata/go.d.plugin/blob/master/modules/docker_engine/README.md). You can find all
container collectors in our supported collectors list under the
-[containers/VMs](/collectors/COLLECTORS.md#containers-and-vms) and
-[Kubernetes](/collectors/COLLECTORS.md#containers-and-vms) headings.
+[containers/VMs](https://github.com/netdata/netdata/blob/master/collectors/COLLECTORS.md#containers-and-vms) and
+[Kubernetes](https://github.com/netdata/netdata/blob/master/collectors/COLLECTORS.md#containers-and-vms) headings.
## Collect Docker metrics
Netdata has robust Docker monitoring thanks to the aforementioned
-[cgroups.plugin](/collectors/cgroups.plugin/README.md). By polling cgroups every second, Netdata can produce meaningful
+[cgroups.plugin](https://github.com/netdata/netdata/blob/master/collectors/cgroups.plugin/README.md). By polling cgroups every second, Netdata can produce meaningful
visualizations about the CPU, memory, disk, and network utilization of all running containers on the host system with
zero configuration.
Netdata also collects metrics from applications running inside of Docker containers. For example, if you create a MySQL
database container using `docker run --name some-mysql -e MYSQL_ROOT_PASSWORD=my-secret-pw -d mysql:tag`, it exposes
metrics on port 3306. You can configure the [MySQL
-collector](https://learn.netdata.cloud/docs/agent/collectors/go.d.plugin/modules/mysql) to look at `127.0.0.0:3306` for
+collector](https://github.com/netdata/go.d.plugin/blob/master/modules/mysql/README.md) to look at `127.0.0.0:3306` for
MySQL metrics:
```yml
@@ -48,18 +51,18 @@ jobs:
```
Netdata then collects metrics from the container itself, but also dozens [MySQL-specific
-metrics](https://learn.netdata.cloud/docs/agent/collectors/go.d.plugin/modules/mysql#charts) as well.
+metrics](https://github.com/netdata/go.d.plugin/blob/master/modules/mysql/README.md#charts) as well.
### Collect metrics from applications running in Docker containers
You could use this technique to monitor an entire infrastructure of Docker containers. The same [enable and
-configure](/docs/collect/enable-configure.md) procedures apply whether an application runs on the host system or inside
+configure](https://github.com/netdata/netdata/blob/master/docs/collect/enable-configure.md) procedures apply whether an application runs on the host system or inside
a container. You may need to configure the target endpoint if it's not the application's default.
-Netdata can even [run in a Docker container](/packaging/docker/README.md) itself, and then collect metrics about the
+Netdata can even [run in a Docker container](https://github.com/netdata/netdata/blob/master/packaging/docker/README.md) itself, and then collect metrics about the
host system, its own container with cgroups, and any applications you want to monitor.
-See our [application metrics doc](/docs/collect/application-metrics.md) for details about Netdata's application metrics
+See our [application metrics doc](https://github.com/netdata/netdata/blob/master/docs/collect/application-metrics.md) for details about Netdata's application metrics
collection capabilities.
## Collect Kubernetes metrics
@@ -74,26 +77,26 @@ your k8s infrastructure.
configuration files for [compatible
applications](https://github.com/netdata/helmchart#service-discovery-and-supported-services) and any endpoints
covered by our [generic Prometheus
- collector](https://learn.netdata.cloud/docs/agent/collectors/go.d.plugin/modules/prometheus). With these
+ collector](https://github.com/netdata/go.d.plugin/blob/master/modules/prometheus/README.md). With these
configuration files, Netdata collects metrics from any compatible applications as they run _inside_ of a pod.
Service discovery happens without manual intervention as pods are created, destroyed, or moved between nodes.
-- A [Kubelet collector](https://learn.netdata.cloud/docs/agent/collectors/go.d.plugin/modules/k8s_kubelet), which runs
+- A [Kubelet collector](https://github.com/netdata/go.d.plugin/blob/master/modules/k8s_kubelet/README.md), which runs
on each node in a k8s cluster to monitor the number of pods/containers, the volume of operations on each container,
and more.
-- A [kube-proxy collector](https://learn.netdata.cloud/docs/agent/collectors/go.d.plugin/modules/k8s_kubeproxy), which
+- A [kube-proxy collector](https://github.com/netdata/go.d.plugin/blob/master/modules/k8s_kubeproxy/README.md), which
also runs on each node and monitors latency and the volume of HTTP requests to the proxy.
-- A [cgroups collector](/collectors/cgroups.plugin/README.md), which collects CPU, memory, and bandwidth metrics for
+- A [cgroups collector](https://github.com/netdata/netdata/blob/master/collectors/cgroups.plugin/README.md), which collects CPU, memory, and bandwidth metrics for
each container running on your k8s cluster.
For a holistic view of Netdata's Kubernetes monitoring capabilities, see our guide: [_Monitor a Kubernetes (k8s) cluster
-with Netdata_](https://learn.netdata.cloud/guides/monitor/kubernetes-k8s-netdata).
+with Netdata_](https://github.com/netdata/netdata/blob/master/docs/guides/monitor/kubernetes-k8s-netdata.md).
## What's next?
Netdata is capable of collecting metrics from hundreds of applications, such as web servers, databases, messaging
-brokers, and more. See more in the [application metrics doc](/docs/collect/application-metrics.md).
+brokers, and more. See more in the [application metrics doc](https://github.com/netdata/netdata/blob/master/docs/collect/application-metrics.md).
If you already have all the information you need about collecting metrics, move into Netdata's meaningful visualizations
-with [seeing an overview of your infrastructure](/docs/visualize/overview-infrastructure.md) using Netdata Cloud.
+with [seeing an overview of your infrastructure](https://github.com/netdata/netdata/blob/master/docs/visualize/overview-infrastructure.md) using Netdata Cloud.
diff --git a/docs/collect/enable-configure.md b/docs/collect/enable-configure.md
index 19e680c2..cd8960ac 100644
--- a/docs/collect/enable-configure.md
+++ b/docs/collect/enable-configure.md
@@ -1,14 +1,18 @@
<!--
title: "Enable or configure a collector"
description: "Every collector is highly configurable, allowing them to collect metrics from any node and any infrastructure."
-custom_edit_url: https://github.com/netdata/netdata/edit/master/docs/collect/enable-configure.md
+custom_edit_url: "https://github.com/netdata/netdata/edit/master/docs/collect/enable-configure.md"
+sidebar_label: "Enable or configure a collector"
+learn_status: "Published"
+learn_topic_type: "Tasks"
+learn_rel_path: "Setup"
-->
# Enable or configure a collector
When Netdata starts up, each collector searches for exposed metrics on the default endpoint established by that service
or application's standard installation procedure. For example, the [Nginx
-collector](https://learn.netdata.cloud/docs/agent/collectors/go.d.plugin/modules/nginx) searches at
+collector](https://github.com/netdata/go.d.plugin/blob/master/modules/nginx/README.md) searches at
`http://127.0.0.1/stub_status` for exposed metrics in the correct format. If an Nginx web server is running and exposes
metrics on that endpoint, the collector begins gathering them.
@@ -20,7 +24,7 @@ enable or configure a collector to gather all available metrics from your system
You can enable/disable collectors individually, or enable/disable entire orchestrators, using their configuration files.
For example, you can change the behavior of the Go orchestrator, or any of its collectors, by editing `go.d.conf`.
-Use `edit-config` from your [Netdata config directory](/docs/configure/nodes.md#the-netdata-config-directory) to open
+Use `edit-config` from your [Netdata config directory](https://github.com/netdata/netdata/blob/master/docs/configure/nodes.md#the-netdata-config-directory) to open
the orchestrator primary configuration file:
```bash
@@ -33,14 +37,14 @@ enable/disable it with `yes` and `no` settings. Uncomment any line you change to
start.
After you make your changes, restart the Agent with `sudo systemctl restart netdata`, or the [appropriate
-method](/docs/configure/start-stop-restart.md) for your system.
+method](https://github.com/netdata/netdata/blob/master/docs/configure/start-stop-restart.md) for your system.
## Configure a collector
-First, [find the collector](/collectors/COLLECTORS.md) you want to edit and open its documentation. Some software has
+First, [find the collector](https://github.com/netdata/netdata/blob/master/collectors/COLLECTORS.md) you want to edit and open its documentation. Some software has
collectors written in multiple languages. In these cases, you should always pick the collector written in Go.
-Use `edit-config` from your [Netdata config directory](/docs/configure/nodes.md#the-netdata-config-directory) to open a
+Use `edit-config` from your [Netdata config directory](https://github.com/netdata/netdata/blob/master/docs/configure/nodes.md#the-netdata-config-directory) to open a
collector's configuration file. For example, edit the Nginx collector with the following:
```bash
@@ -53,16 +57,16 @@ configure that collector. Uncomment any line you change to ensure the collector'
read it on start.
After you make your changes, restart the Agent with `sudo systemctl restart netdata`, or the [appropriate
-method](/docs/configure/start-stop-restart.md) for your system.
+method](https://github.com/netdata/netdata/blob/master/docs/configure/start-stop-restart.md) for your system.
## What's next?
-Read high-level overviews on how Netdata collects [system metrics](/docs/collect/system-metrics.md), [container
-metrics](/docs/collect/container-metrics.md), and [application metrics](/docs/collect/application-metrics.md).
+Read high-level overviews on how Netdata collects [system metrics](https://github.com/netdata/netdata/blob/master/docs/collect/system-metrics.md), [container
+metrics](https://github.com/netdata/netdata/blob/master/docs/collect/container-metrics.md), and [application metrics](https://github.com/netdata/netdata/blob/master/docs/collect/application-metrics.md).
If you're already collecting all metrics from your systems, containers, and applications, it's time to move into
-Netdata's visualization features. [See an overview of your infrastructure](/docs/visualize/overview-infrastructure.md)
+Netdata's visualization features. [See an overview of your infrastructure](https://github.com/netdata/netdata/blob/master/docs/visualize/overview-infrastructure.md)
using Netdata Cloud, or learn how to [interact with dashboards and
-charts](/docs/visualize/interact-dashboards-charts.md).
+charts](https://github.com/netdata/netdata/blob/master/docs/visualize/interact-dashboards-charts.md).
diff --git a/docs/collect/how-collectors-work.md b/docs/collect/how-collectors-work.md
index 07e34858..382d4ccc 100644
--- a/docs/collect/how-collectors-work.md
+++ b/docs/collect/how-collectors-work.md
@@ -1,7 +1,11 @@
<!--
title: "How Netdata's metrics collectors work"
description: "When Netdata starts, and with zero configuration, it auto-detects thousands of data sources and immediately collects per-second metrics."
-custom_edit_url: https://github.com/netdata/netdata/edit/master/docs/collect/how-collectors-work.md
+custom_edit_url: "https://github.com/netdata/netdata/edit/master/docs/collect/how-collectors-work.md"
+sidebar_label: "How Netdata's metrics collectors work"
+learn_status: "Published"
+learn_topic_type: "Concepts"
+learn_rel_path: "Concepts"
-->
# How Netdata's metrics collectors work
@@ -10,7 +14,7 @@ When Netdata starts, and with zero configuration, it auto-detects thousands of d
per-second metrics.
Netdata can immediately collect metrics from these endpoints thanks to 300+ **collectors**, which all come pre-installed
-when you [install Netdata](/docs/get-started.mdx).
+when you [install Netdata](https://github.com/netdata/netdata/blob/master/docs/get-started.mdx).
Every collector has two primary jobs:
@@ -19,15 +23,15 @@ Every collector has two primary jobs:
If the collector finds compatible metrics exposed on the configured endpoint, it begins a per-second collection job. The
Netdata Agent gathers these metrics, sends them to the [database engine for
-storage](/docs/store/change-metrics-storage.md), and immediately [visualizes them
-meaningfully](/docs/visualize/interact-dashboards-charts.md) on dashboards.
+storage](https://github.com/netdata/netdata/blob/master/docs/store/change-metrics-storage.md), and immediately [visualizes them
+meaningfully](https://github.com/netdata/netdata/blob/master/docs/visualize/interact-dashboards-charts.md) on dashboards.
Each collector comes with a pre-defined configuration that matches the default setup for that application. This endpoint
can be a URL and port, a socket, a file, a web page, and more.
-For example, the [Nginx collector](https://learn.netdata.cloud/docs/agent/collectors/go.d.plugin/modules/nginx) searches
+For example, the [Nginx collector](https://github.com/netdata/go.d.plugin/blob/master/modules/nginx/README.md) searches
at `http://127.0.0.1/stub_status`, which is the default endpoint for exposing Nginx metrics. The [web log collector for
-Nginx or Apache](https://learn.netdata.cloud/docs/agent/collectors/go.d.plugin/modules/weblog) searches at
+Nginx or Apache](https://github.com/netdata/go.d.plugin/blob/master/README.mdmodules/weblog) searches at
`/var/log/nginx/access.log` and `/var/log/apache2/access.log`, respectively, both of which are standard locations for
access log files on Linux systems.
@@ -35,15 +39,15 @@ The endpoint is user-configurable, as are many other specifics of what a given c
## What can Netdata collect?
-To quickly find your answer, see our [list of supported collectors](/collectors/COLLECTORS.md).
+To quickly find your answer, see our [list of supported collectors](https://github.com/netdata/netdata/blob/master/collectors/COLLECTORS.md).
Generally, Netdata's collectors can be grouped into three types:
-- [Systems](/docs/collect/system-metrics.md): Monitor CPU, memory, disk, networking, systemd, eBPF, and much more.
+- [Systems](https://github.com/netdata/netdata/blob/master/docs/collect/system-metrics.md): Monitor CPU, memory, disk, networking, systemd, eBPF, and much more.
Every metric exposed by `/proc`, `/sys`, and other Linux kernel sources.
-- [Containers](/docs/collect/container-metrics.md): Gather metrics from container agents, like `dockerd` or `kubectl`,
+- [Containers](https://github.com/netdata/netdata/blob/master/docs/collect/container-metrics.md): Gather metrics from container agents, like `dockerd` or `kubectl`,
along with the resource usage of containers and the applications they run.
-- [Applications](/docs/collect/application-metrics.md): Collect per-second metrics from web servers, databases, logs,
+- [Applications](https://github.com/netdata/netdata/blob/master/docs/collect/application-metrics.md): Collect per-second metrics from web servers, databases, logs,
message brokers, APM tools, email servers, and much more.
## Collector architecture and terminology
@@ -56,11 +60,11 @@ terms related to collecting metrics.
- **Modules** are a type of collector.
- **Orchestrators** are external plugins that run and manage one or more modules. They run as independent processes.
The Go orchestrator is in active development.
- - [go.d.plugin](https://learn.netdata.cloud/docs/agent/collectors/go.d.plugin/): An orchestrator for data
+ - [go.d.plugin](https://github.com/netdata/go.d.plugin/blob/master/README.md): An orchestrator for data
collection modules written in `go`.
- - [python.d.plugin](/collectors/python.d.plugin/README.md): An orchestrator for data collection modules written in
+ - [python.d.plugin](https://github.com/netdata/netdata/blob/master/collectors/python.d.plugin/README.md): An orchestrator for data collection modules written in
`python` v2/v3.
- - [charts.d.plugin](/collectors/charts.d.plugin/README.md): An orchestrator for data collection modules written in
+ - [charts.d.plugin](https://github.com/netdata/netdata/blob/master/collectors/charts.d.plugin/README.md): An orchestrator for data collection modules written in
`bash` v4+.
- **External plugins** gather metrics from external processes, such as a webserver or database, and run as independent
processes that communicate with the Netdata daemon via pipes.
@@ -69,10 +73,10 @@ terms related to collecting metrics.
## What's next?
-[Enable or configure a collector](/docs/collect/enable-configure.md) if the default settings are not compatible with
+[Enable or configure a collector](https://github.com/netdata/netdata/blob/master/docs/collect/enable-configure.md) if the default settings are not compatible with
your infrastructure.
-See our [collectors reference](/collectors/REFERENCE.md) for detailed information on Netdata's collector architecture,
+See our [collectors reference](https://github.com/netdata/netdata/blob/master/collectors/REFERENCE.md) for detailed information on Netdata's collector architecture,
troubleshooting a collector, developing a custom collector, and more.
diff --git a/docs/collect/system-metrics.md b/docs/collect/system-metrics.md
index ecd8dad7..442b1382 100644
--- a/docs/collect/system-metrics.md
+++ b/docs/collect/system-metrics.md
@@ -2,59 +2,62 @@
title: "Collect system metrics with Netdata"
sidebar_label: "System metrics"
description: "Netdata collects thousands of metrics from physical and virtual systems, IoT/edge devices, and containers with zero configuration."
-custom_edit_url: https://github.com/netdata/netdata/edit/master/docs/collect/system-metrics.md
+custom_edit_url: "https://github.com/netdata/netdata/edit/master/docs/collect/system-metrics.md"
+learn_status: "Published"
+learn_topic_type: "Concepts"
+learn_rel_path: "Concepts"
-->
# Collect system metrics with Netdata
Netdata collects thousands of metrics directly from the operating systems of physical and virtual systems, IoT/edge
-devices, and [containers](/docs/collect/container-metrics.md) with zero configuration.
+devices, and [containers](https://github.com/netdata/netdata/blob/master/docs/collect/container-metrics.md) with zero configuration.
To gather system metrics, Netdata uses roughly a dozen plugins, each of which has one or more collectors for very
specific metrics exposed by the host. The system metrics Netdata users interact with most for health monitoring and
performance troubleshooting are collected and visualized by `proc.plugin`, `cgroups.plugin`, and `ebpf.plugin`.
-[**proc.plugin**](/collectors/proc.plugin/README.md) gathers metrics from the `/proc` and `/sys` folders in Linux
+[**proc.plugin**](https://github.com/netdata/netdata/blob/master/collectors/proc.plugin/README.md) gathers metrics from the `/proc` and `/sys` folders in Linux
systems, along with a few other endpoints, and is responsible for the bulk of the system metrics collected and
visualized by Netdata. It collects CPU, memory, disks, load, networking, mount points, and more with zero configuration.
It even allows Netdata to monitor its own resource utilization!
-[**cgroups.plugin**](/collectors/cgroups.plugin/README.md) collects rich metrics about containers and virtual machines
+[**cgroups.plugin**](https://github.com/netdata/netdata/blob/master/collectors/cgroups.plugin/README.md) collects rich metrics about containers and virtual machines
using the virtual files under `/sys/fs/cgroup`. By reading cgroups, Netdata can instantly collect resource utilization
metrics for systemd services, all containers (Docker, LXC, LXD, Libvirt, systemd-nspawn), and more. Learn more in the
-[collecting container metrics](/docs/collect/container-metrics.md) doc.
+[collecting container metrics](https://github.com/netdata/netdata/blob/master/docs/collect/container-metrics.md) doc.
-[**ebpf.plugin**](/collectors/ebpf.plugin/README.md): Netdata's extended Berkeley Packet Filter (eBPF) collector
+[**ebpf.plugin**](https://github.com/netdata/netdata/blob/master/collectors/ebpf.plugin/README.md): Netdata's extended Berkeley Packet Filter (eBPF) collector
monitors Linux kernel-level metrics for file descriptors, virtual filesystem IO, and process management. You can use our
eBPF collector to analyze how and when a process accesses files, when it makes system calls, whether it leaks memory or
creating zombie processes, and more.
While the above plugins and associated collectors are the most important for system metrics, there are many others. You
-can find all system collectors in our [supported collectors list](/collectors/COLLECTORS.md#system-collectors).
+can find all system collectors in our [supported collectors list](https://github.com/netdata/netdata/blob/master/collectors/COLLECTORS.md#system-collectors).
## Collect Windows system metrics
Netdata is also capable of monitoring Windows systems. The [WMI
-collector](https://learn.netdata.cloud/docs/agent/collectors/go.d.plugin/modules/wmi) integrates with
+collector](https://github.com/netdata/go.d.plugin/blob/master/modules/wmi/README.md) integrates with
[windows_exporter](https://github.com/prometheus-community/windows_exporter), a small Go-based binary that you can run
on Windows systems. The WMI collector then gathers metrics from an endpoint created by windows_exporter, for more
-details see [the requirements](https://learn.netdata.cloud/docs/agent/collectors/go.d.plugin/modules/wmi#requirements).
+details see [the requirements](https://github.com/netdata/go.d.plugin/blob/master/modules/wmi/README.md#requirements).
Next, [configure the WMI
-collector](https://learn.netdata.cloud/docs/agent/collectors/go.d.plugin/modules/wmi#configuration) to point to the URL
+collector](https://github.com/netdata/go.d.plugin/blob/master/modules/wmi/README.md#configuration) to point to the URL
and port of your exposed endpoint. Restart Netdata with `sudo systemctl restart netdata`, or the [appropriate
-method](/docs/configure/start-stop-restart.md) for your system. You'll start seeing Windows system metrics, such as CPU
+method](https://github.com/netdata/netdata/blob/master/docs/configure/start-stop-restart.md) for your system. You'll start seeing Windows system metrics, such as CPU
utilization, memory, bandwidth per NIC, number of processes, and much more.
For information about collecting metrics from applications _running on Windows systems_, see the [application metrics
-doc](/docs/collect/application-metrics.md#collect-metrics-from-applications-running-on-windows).
+doc](https://github.com/netdata/netdata/blob/master/docs/collect/application-metrics.md#collect-metrics-from-applications-running-on-windows).
## What's next?
-Because there's some overlap between system metrics and [container metrics](/docs/collect/container-metrics.md), you
+Because there's some overlap between system metrics and [container metrics](https://github.com/netdata/netdata/blob/master/docs/collect/container-metrics.md), you
should investigate Netdata's container compatibility if you use them heavily in your infrastructure.
-If you don't use containers, skip ahead to collecting [application metrics](/docs/collect/application-metrics.md) with
+If you don't use containers, skip ahead to collecting [application metrics](https://github.com/netdata/netdata/blob/master/docs/collect/application-metrics.md) with
Netdata.
diff --git a/docs/configure/common-changes.md b/docs/configure/common-changes.md
index 93b12d22..e1dccfce 100644
--- a/docs/configure/common-changes.md
+++ b/docs/configure/common-changes.md
@@ -1,7 +1,11 @@
<!--
title: "Common configuration changes"
description: "See the most popular configuration changes to make to the Netdata Agent, including longer metrics retention, reduce sampling, and more."
-custom_edit_url: https://github.com/netdata/netdata/edit/master/docs/configure/common-changes.md
+custom_edit_url: "https://github.com/netdata/netdata/edit/master/docs/configure/common-changes.md"
+sidebar_label: "Common configuration changes"
+learn_status: "Published"
+learn_topic_type: "Tasks"
+learn_rel_path: "Setup"
-->
# Common configuration changes
@@ -10,19 +14,24 @@ The Netdata Agent requires no configuration upon installation to collect thousan
systems, containers, and applications, but there are hundreds of settings to tweak if you want to exercise more control
over your monitoring platform.
-This document assumes familiarity with using [`edit-config`](/docs/configure/nodes.md) from the Netdata config
+This document assumes familiarity with
+using [`edit-config`](https://github.com/netdata/netdata/blob/master/docs/configure/nodes.md) from the Netdata config
directory.
## Change dashboards and visualizations
-The Netdata Agent's [local dashboard](/web/gui/README.md), accessible at `http://NODE:19999` is highly configurable. If
-you use Netdata Cloud for [infrastructure monitoring](/docs/quickstart/infrastructure.md), you will see many of these
+The Netdata Agent's [local dashboard](https://github.com/netdata/netdata/blob/master/web/gui/README.md), accessible
+at `http://NODE:19999` is highly configurable. If
+you use Netdata Cloud
+for [infrastructure monitoring](https://github.com/netdata/netdata/blob/master/docs/quickstart/infrastructure.md), you
+will see many of these
changes reflected in those visualizations due to the way Netdata Cloud proxies metric data and metadata to your browser.
### Increase the long-term metrics retention period
-Increase the values for the `page cache size` and `dbengine multihost disk space` settings in the [`[global]`
-section](/daemon/config/README.md#global-section-options) of `netdata.conf`.
+Increase the values for the `page cache size` and `dbengine multihost disk space` settings in
+the [`[global]`section](https://github.com/netdata/netdata/blob/master/daemon/config/README.md#global-section-options)
+of `netdata.conf`.
```conf
[global]
@@ -30,13 +39,17 @@ section](/daemon/config/README.md#global-section-options) of `netdata.conf`.
dbengine multihost disk space = 4096 # 4GiB of disk space for metrics storage
```
-Read our doc on [increasing long-term metrics storage](/docs/store/change-metrics-storage.md) for details, including a
-[calculator](/docs/store/change-metrics-storage.md#calculate-the-system-resources-ram-disk-space-needed-to-store-metrics)
+Read our doc
+on [increasing long-term metrics storage](https://github.com/netdata/netdata/blob/master/docs/store/change-metrics-storage.md)
+for details, including a
+[calculator](https://github.com/netdata/netdata/blob/master/docs/store/change-metrics-storage.md#calculate-the-system-resources-ram-disk-space-needed-to-store-metrics)
to help you determine the exact settings for your desired retention period.
### Reduce the data collection frequency
-Change `update every` in the [`[global]` section](/daemon/config/README.md#global-section-options) of `netdata.conf` so
+Change `update every` in
+the [`[global]` section](https://github.com/netdata/netdata/blob/master/daemon/config/README.md#global-section-options)
+of `netdata.conf` so
that it is greater than `1`. An `update every` of `5` means the Netdata Agent enforces a _minimum_ collection frequency
of 5 seconds.
@@ -47,12 +60,15 @@ of 5 seconds.
Every collector and plugin has its own `update every` setting, which you can also change in the `go.d.conf`,
`python.d.conf` or `charts.d.conf` files, or in individual collector configuration files. If the `update
-every` for an individual collector is less than the global, the Netdata Agent uses the global setting. See the [enable
-or configure a collector](/docs/collect/enable-configure.md) doc for details.
+every` for an individual collector is less than the global, the Netdata Agent uses the global setting. See
+the [enable or configure a collector](https://github.com/netdata/netdata/blob/master/docs/collect/enable-configure.md)
+doc for details.
### Disable a collector or plugin
-Turn off entire plugins in the [`[plugins]` section](/daemon/config/README.md#plugins-section-options) of
+Turn off entire plugins in
+the [`[plugins]` section](https://github.com/netdata/netdata/blob/master/daemon/config/README.md#plugins-section-options)
+of
`netdata.conf`.
To disable specific collectors, open `go.d.conf`, `python.d.conf` or `charts.d.conf` and find the line
@@ -77,17 +93,20 @@ sudo ./edit-config health.d/example-alarm.conf
Or, append your new alarm to an existing file by editing a relevant existing file in the `health.d/` directory.
-Read more about [configuring alarms](/docs/monitor/configure-alarms.md) to get started, and see the [health monitoring
-reference](/health/REFERENCE.md) for a full listing of options available in health entities.
+Read more about [configuring alarms](https://github.com/netdata/netdata/blob/master/docs/monitor/configure-alarms.md) to
+get started, and see
+the [health monitoring reference](https://github.com/netdata/netdata/blob/master/health/REFERENCE.md) for a full listing
+of options available in health entities.
### Configure a specific alarm
Tweak existing alarms by editing files in the `health.d/` directory. For example, edit `health.d/cpu.conf` to change how
the Agent responds to anomalies related to CPU utilization.
-To see which configuration file you need to edit to configure a specific alarm, [view your active
-alarms](/docs/monitor/view-active-alarms.md) in Netdata Cloud or the local Agent dashboard and look for the **source**
-line. For example, it might read `source 4@/usr/lib/netdata/conf.d/health.d/cpu.conf`.
+To see which configuration file you need to edit to configure a specific
+alarm, [view your active alarms](https://github.com/netdata/netdata/blob/master/docs/monitor/view-active-alarms.md) in
+Netdata Cloud or the local Agent dashboard and look for the **source** line. For example, it might
+read `source 4@/usr/lib/netdata/conf.d/health.d/cpu.conf`.
Because the source path contains `health.d/cpu.conf`, run `sudo edit-config health.d/cpu.conf` to configure that alarm.
@@ -106,13 +125,16 @@ template: disk_fill_rate
### Turn of all alarms and notifications
-Set `enabled` to `no` in the [`[health]` section](/daemon/config/README.md#health-section-options) section of
+Set `enabled` to `no` in
+the [`[health]` section](https://github.com/netdata/netdata/blob/master/daemon/config/README.md#health-section-options)
+section of
`netdata.conf`.
### Enable alarm notifications
Open `health_alarm_notify.conf` for editing. First, read the [enabling
-notifications](/docs/monitor/enable-notifications.md#netdata-agent) doc for an example of the process using Slack, then
+notifications](https://github.com/netdata/netdata/blob/master/docs/monitor/enable-notifications.md#netdata-agent) doc
+for an example of the process using Slack, then
click on the link to your preferred notification method to find documentation for that specific endpoint.
## Improve node security
@@ -120,14 +142,17 @@ click on the link to your preferred notification method to find documentation fo
While the Netdata Agent is both [open and secure by design](https://www.netdata.cloud/blog/netdata-agent-dashboard/), we
recommend every user take some action to administer and secure their nodes.
-Learn more about a few of the following changes in the [node security doc](/docs/configure/secure-nodes.md).
+Learn more about a few of the following changes in
+the [node security doc](https://github.com/netdata/netdata/blob/master/docs/configure/secure-nodes.md).
### Disable the local Agent dashboard (`http://NODE:19999`)
If you use Netdata Cloud to visualize metrics, stream metrics to a parent node, or otherwise don't need the local Agent
dashboard, disabling it reduces the Agent's resource utilization and improves security.
-Change the `mode` setting to `none` in the [`[web]` section](/web/server/README.md#configuration) of `netdata.conf`.
+Change the `mode` setting to `none` in
+the [`[web]` section](https://github.com/netdata/netdata/blob/master/web/server/README.md#configuration)
+of `netdata.conf`.
```conf
[web]
@@ -136,11 +161,12 @@ Change the `mode` setting to `none` in the [`[web]` section](/web/server/README.
### Use access lists to restrict access to specific assets
-Allow access from only specific IP addresses, ranges of IP addresses, or hostnames using [access
-lists](/web/server/README.md#access-lists) and [simple patterns](/libnetdata/simple_pattern/README.md).
+Allow access from only specific IP addresses, ranges of IP addresses, or hostnames
+using [access lists](https://github.com/netdata/netdata/blob/master/web/server/README.md#access-lists)
+and [simple patterns](https://github.com/netdata/netdata/blob/master/libnetdata/simple_pattern/README.md).
See a quickstart to access lists in the [node security
-doc](/docs/configure/secure-nodes.md#restrict-access-to-the-local-dashboard).
+doc](https://github.com/netdata/netdata/blob/master/docs/configure/secure-nodes.md#restrict-access-to-the-local-dashboard).
### Stop sending anonymous statistics to Google Analytics
@@ -151,7 +177,8 @@ the statistics script.
sudo touch .opt-out-from-anonymous-statistics
```
-Learn more about [why we collect anonymous statistics](/docs/anonymous-statistics.md).
+Learn more
+about [why we collect anonymous statistics](https://github.com/netdata/netdata/blob/master/docs/anonymous-statistics.md).
### Change the IP address/port Netdata listens to
@@ -162,26 +189,30 @@ Change the `default port` setting in the `[web]` section to a port other than `1
default port = 39999
```
-Use the `bind to` setting to the ports other assets, such as the [running `netdata.conf`
-configuration](/docs/configure/nodes.md#see-an-agents-running-configuration), API, or streaming requests listen to.
+Use the `bind to` setting to the ports other assets, such as
+the [running `netdata.conf` configuration](https://github.com/netdata/netdata/blob/master/docs/configure/nodes.md#see-an-agents-running-configuration),
+API, or streaming requests listen to.
## Reduce resource usage
-Read our [performance optimization guide](/docs/guides/configure/performance.md) for a long list of specific changes
+Read
+our [performance optimization guide](https://github.com/netdata/netdata/blob/master/docs/guides/configure/performance.md)
+for a long list of specific changes
that can reduce the Netdata Agent's CPU/memory footprint and IO requirements.
## Organize nodes with host labels
Beginning with v1.20, Netdata accepts user-defined **host labels**. These labels are sent during streaming, exporting,
and as metadata to Netdata Cloud, and help you organize the metrics coming from complex infrastructure. Host labels are
-defined in the section `[host labels]`.
+defined in the section `[host labels]`.
-For a quick introduction, read the [host label guide](/docs/guides/using-host-labels.md).
+For a quick introduction, read
+the [host label guide](https://github.com/netdata/netdata/blob/master/docs/guides/using-host-labels.md).
-The following restrictions apply to host label names:
-
-- Names cannot start with `_`, but it can be present in other parts of the name.
-- Names only accept alphabet letters, numbers, dots, and dashes.
+The following restrictions apply to host label names:
+
+- Names cannot start with `_`, but it can be present in other parts of the name.
+- Names only accept alphabet letters, numbers, dots, and dashes.
The policy for values is more flexible, but you can not use exclamation marks (`!`), whitespaces (` `), single quotes
(`'`), double quotes (`"`), or asterisks (`*`), because they are used to compare label values in health alarms and
@@ -189,26 +220,33 @@ templates.
## What's next?
-If you haven't already, learn how to [secure your nodes](/docs/configure/secure-nodes.md).
+If you haven't already, learn how
+to [secure your nodes](https://github.com/netdata/netdata/blob/master/docs/configure/secure-nodes.md).
-As mentioned at the top, there are plenty of other
+As mentioned at the top, there are plenty of other
You can also take what you've learned about node configuration to tweak the Agent's behavior or enable new features:
-- [Enable new collectors](/docs/collect/enable-configure.md) or tweak their behavior.
-- [Configure existing health alarms](/docs/monitor/configure-alarms.md) or create new ones.
-- [Enable notifications](/docs/monitor/enable-notifications.md) to receive updates about the health of your
+- [Enable new collectors](https://github.com/netdata/netdata/blob/master/docs/collect/enable-configure.md) or tweak
+ their behavior.
+- [Configure existing health alarms](https://github.com/netdata/netdata/blob/master/docs/monitor/configure-alarms.md) or
+ create new ones.
+- [Enable notifications](https://github.com/netdata/netdata/blob/master/docs/monitor/enable-notifications.md) to receive
+ updates about the health of your
infrastructure.
-- Change [the long-term metrics retention period](/docs/store/change-metrics-storage.md) using the database engine.
+-
+
+Change [the long-term metrics retention period](https://github.com/netdata/netdata/blob/master/docs/store/change-metrics-storage.md)
+using the database engine.
### Related reference documentation
-- [Netdata Agent · Daemon](/health/README.md)
-- [Netdata Agent · Daemon configuration](/daemon/config/README.md)
-- [Netdata Agent · Web server](/web/server/README.md)
-- [Netdata Agent · Local Agent dashboard](/web/gui/README.md)
-- [Netdata Agent · Health monitoring](/health/REFERENCE.md)
-- [Netdata Agent · Notifications](/health/notifications/README.md)
-- [Netdata Agent · Simple patterns](/libnetdata/simple_pattern/README.md)
+- [Netdata Agent · Daemon](https://github.com/netdata/netdata/blob/master/health/README.md)
+- [Netdata Agent · Daemon configuration](https://github.com/netdata/netdata/blob/master/daemon/config/README.md)
+- [Netdata Agent · Web server](https://github.com/netdata/netdata/blob/master/web/server/README.md)
+- [Netdata Agent · Local Agent dashboard](https://github.com/netdata/netdata/blob/master/web/gui/README.md)
+- [Netdata Agent · Health monitoring](https://github.com/netdata/netdata/blob/master/health/REFERENCE.md)
+- [Netdata Agent · Notifications](https://github.com/netdata/netdata/blob/master/health/notifications/README.md)
+- [Netdata Agent · Simple patterns](https://github.com/netdata/netdata/blob/master/libnetdata/simple_pattern/README.md)
[![analytics](https://www.google-analytics.com/collect?v=1&aip=1&t=pageview&_s=1&ds=github&dr=https%3A%2F%2Fgithub.com%2Fnetdata%2Fnetdata&dl=https%3A%2F%2Fmy-netdata.io%2Fgithub%2Fdocs%2Fconfigure%2Fcommon-changes&_u=MAC~&cid=5792dfd7-8dc4-476b-af31-da2fdb9f93d2&tid=UA-64295674-3)](<>)
diff --git a/docs/configure/nodes.md b/docs/configure/nodes.md
index 841419a7..8f54b1bf 100644
--- a/docs/configure/nodes.md
+++ b/docs/configure/nodes.md
@@ -1,7 +1,11 @@
<!--
title: "Configure the Netdata Agent"
description: "Netdata is zero-configuration for most users, but complex infrastructures may require you to tweak some of the Agent's granular settings."
-custom_edit_url: https://github.com/netdata/netdata/edit/master/docs/configure/nodes.md
+custom_edit_url: "https://github.com/netdata/netdata/edit/master/docs/configure/nodes.md"
+sidebar_label: "Configure the Netdata Agent"
+learn_status: "Published"
+learn_topic_type: "Concepts"
+learn_rel_path: "Concepts"
-->
# Configure the Netdata Agent
@@ -19,7 +23,7 @@ anomaly, or change in infrastructure affects how their Agents should perform.
## The Netdata config directory
On most Linux systems, using our [recommended one-line
-installation](/docs/get-started.mdx#install-on-linux-with-one-line-installer), the **Netdata config
+installation](https://github.com/netdata/netdata/blob/master/docs/get-started.mdx#install-on-linux-with-one-line-installer), the **Netdata config
directory** is `/etc/netdata/`. The config directory contains several configuration files with the `.conf` extension, a
few directories, and a shell script named `edit-config`.
@@ -37,23 +41,23 @@ these files in your own Netdata config directory, as the next section describes
exist.
- `netdata.conf` is the main configuration file. This is where you'll find most configuration options. Read descriptions
- for each in the [daemon config](/daemon/config/README.md) doc.
+ for each in the [daemon config](https://github.com/netdata/netdata/blob/master/daemon/config/README.md) doc.
- `edit-config` is a shell script used for [editing configuration files](#use-edit-config-to-edit-configuration-files).
- Various configuration files ending in `.conf` for [configuring plugins or
- collectors](/docs/collect/enable-configure.md#enable-a-collector-or-its-orchestrator) behave. Examples: `go.d.conf`,
+ collectors](https://github.com/netdata/netdata/blob/master/docs/collect/enable-configure.md#enable-a-collector-or-its-orchestrator) behave. Examples: `go.d.conf`,
`python.d.conf`, and `ebpf.d.conf`.
- Various directories ending in `.d`, which contain other configuration files, each ending in `.conf`, for [configuring
- specific collectors](/docs/collect/enable-configure.md#configure-a-collector).
+ specific collectors](https://github.com/netdata/netdata/blob/master/docs/collect/enable-configure.md#configure-a-collector).
- `apps_groups.conf` is a configuration file for changing how applications/processes are grouped when viewing the
- **Application** charts from [`apps.plugin`](/collectors/apps.plugin/README.md) or
- [`ebpf.plugin`](/collectors/ebpf.plugin/README.md).
-- `health.d/` is a directory that contains [health configuration files](/docs/monitor/configure-alarms.md).
-- `health_alarm_notify.conf` enables and configures [alarm notifications](/docs/monitor/enable-notifications.md).
-- `statsd.d/` is a directory for configuring Netdata's [statsd collector](/collectors/statsd.plugin/README.md).
-- `stream.conf` configures [parent-child streaming](/streaming/README.md) between separate nodes running the Agent.
+ **Application** charts from [`apps.plugin`](https://github.com/netdata/netdata/blob/master/collectors/apps.plugin/README.md) or
+ [`ebpf.plugin`](https://github.com/netdata/netdata/blob/master/collectors/ebpf.plugin/README.md).
+- `health.d/` is a directory that contains [health configuration files](https://github.com/netdata/netdata/blob/master/docs/monitor/configure-alarms.md).
+- `health_alarm_notify.conf` enables and configures [alarm notifications](https://github.com/netdata/netdata/blob/master/docs/monitor/enable-notifications.md).
+- `statsd.d/` is a directory for configuring Netdata's [statsd collector](https://github.com/netdata/netdata/blob/master/collectors/statsd.plugin/README.md).
+- `stream.conf` configures [parent-child streaming](https://github.com/netdata/netdata/blob/master/streaming/README.md) between separate nodes running the Agent.
- `.environment` is a hidden file that describes the environment in which the Netdata Agent is installed, including the
- `PATH` and any installation options. Useful for [reinstalling](/packaging/installer/REINSTALL.md) or
- [uninstalling](/packaging/installer/UNINSTALL.md) the Agent.
+ `PATH` and any installation options. Useful for [reinstalling](https://github.com/netdata/netdata/blob/master/packaging/installer/REINSTALL.md) or
+ [uninstalling](https://github.com/netdata/netdata/blob/master/packaging/installer/UNINSTALL.md) the Agent.
The Netdata config directory also contains one symlink:
@@ -63,7 +67,7 @@ The Netdata config directory also contains one symlink:
## Configure a Netdata docker container
-See [configure agent containers](/packaging/docker/README.md#configure-agent-containers).
+See [configure agent containers](https://github.com/netdata/netdata/blob/master/packaging/docker/README.md#configure-agent-containers).
## Use `edit-config` to edit configuration files
@@ -103,7 +107,7 @@ method for `edit-config` to write into the config directory. Use your `$EDITOR`,
> defaulted to `vim` or `nano`. Use `export EDITOR=` to change this temporarily, or edit your shell configuration file
> to change to permanently.
-After you make your changes, you need to [restart the Agent](/docs/configure/start-stop-restart.md) with `sudo systemctl
+After you make your changes, you need to [restart the Agent](https://github.com/netdata/netdata/blob/master/docs/configure/start-stop-restart.md) with `sudo systemctl
restart netdata` or the appropriate method for your system.
Here's an example of editing the node's hostname, which appears in both the local dashboard and in Netdata Cloud.
@@ -145,26 +149,26 @@ curl -o /etc/netdata/netdata.conf http://NODE:19999/netdata.conf
## What's next?
-Learn more about [starting, stopping, or restarting](/docs/configure/start-stop-restart.md) the Netdata daemon to apply
+Learn more about [starting, stopping, or restarting](https://github.com/netdata/netdata/blob/master/docs/configure/start-stop-restart.md) the Netdata daemon to apply
configuration changes.
-Apply some [common configuration changes](/docs/configure/common-changes.md) to quickly tweak the Agent's behavior.
+Apply some [common configuration changes](https://github.com/netdata/netdata/blob/master/docs/configure/common-changes.md) to quickly tweak the Agent's behavior.
-[Add security to your node](/docs/configure/secure-nodes.md) with what you've learned about the Netdata config directory
+[Add security to your node](https://github.com/netdata/netdata/blob/master/docs/configure/secure-nodes.md) with what you've learned about the Netdata config directory
and `edit-config`. We put together a few security best practices based on how you use the Netdata.
You can also take what you've learned about node configuration to enable or enhance features:
-- [Enable new collectors](/docs/collect/enable-configure.md) or tweak their behavior.
-- [Configure existing health alarms](/docs/monitor/configure-alarms.md) or create new ones.
-- [Enable notifications](/docs/monitor/enable-notifications.md) to receive updates about the health of your
+- [Enable new collectors](https://github.com/netdata/netdata/blob/master/docs/collect/enable-configure.md) or tweak their behavior.
+- [Configure existing health alarms](https://github.com/netdata/netdata/blob/master/docs/monitor/configure-alarms.md) or create new ones.
+- [Enable notifications](https://github.com/netdata/netdata/blob/master/docs/monitor/enable-notifications.md) to receive updates about the health of your
infrastructure.
-- Change [the long-term metrics retention period](/docs/store/change-metrics-storage.md) using the database engine.
+- Change [the long-term metrics retention period](https://github.com/netdata/netdata/blob/master/docs/store/change-metrics-storage.md) using the database engine.
### Related reference documentation
-- [Netdata Agent · Daemon](/daemon/README.md)
-- [Netdata Agent · Health monitoring](/health/README.md)
-- [Netdata Agent · Notifications](/health/notifications/README.md)
+- [Netdata Agent · Daemon](https://github.com/netdata/netdata/blob/master/daemon/README.md)
+- [Netdata Agent · Health monitoring](https://github.com/netdata/netdata/blob/master/health/README.md)
+- [Netdata Agent · Notifications](https://github.com/netdata/netdata/blob/master/health/notifications/README.md)
[![analytics](https://www.google-analytics.com/collect?v=1&aip=1&t=pageview&_s=1&ds=github&dr=https%3A%2F%2Fgithub.com%2Fnetdata%2Fnetdata&dl=https%3A%2F%2Fmy-netdata.io%2Fgithub%2Fdocs%2Fconfigure%2Fnodes&_u=MAC~&cid=5792dfd7-8dc4-476b-af31-da2fdb9f93d2&tid=UA-64295674-3)](<>)
diff --git a/docs/configure/secure-nodes.md b/docs/configure/secure-nodes.md
index 02057ab9..75bf6fd3 100644
--- a/docs/configure/secure-nodes.md
+++ b/docs/configure/secure-nodes.md
@@ -1,7 +1,11 @@
<!--
title: "Secure your nodes"
description: "Your data and systems are safe with Netdata, but we recommend a few easy ways to improve the security of your infrastructure."
-custom_edit_url: https://github.com/netdata/netdata/edit/master/docs/configure/secure-nodes.md
+custom_edit_url: "https://github.com/netdata/netdata/edit/master/docs/configure/secure-nodes.md"
+sidebar_label: "Secure your nodes"
+learn_status: "Published"
+learn_topic_type: "Tasks"
+learn_rel_path: "Setup"
-->
# Secure your nodes
@@ -11,13 +15,13 @@ internet at large, anyone can access the dashboard and your node's metrics at `h
so that the local dashboard was immediately accessible to users, and so that we don't dictate how professionals set up
and secure their infrastructures.
-Despite this design decision, your [data](/docs/netdata-security.md#your-data-is-safe-with-netdata) and your
-[systems](/docs/netdata-security.md#your-systems-are-safe-with-netdata) are safe with Netdata. Netdata is read-only,
+Despite this design decision, your [data](https://github.com/netdata/netdata/blob/master/docs/netdata-security.md#your-data-is-safe-with-netdata) and your
+[systems](https://github.com/netdata/netdata/blob/master/docs/netdata-security.md#your-systems-are-safe-with-netdata) are safe with Netdata. Netdata is read-only,
cannot do anything other than present metrics, and runs without special/`sudo` privileges. Also, the local dashboard
only exposes chart metadata and metric values, not raw data.
While Netdata is secure by design, we believe you should [protect your
-nodes](/docs/netdata-security.md#why-netdata-should-be-protected). If left accessible to the internet at large, the
+nodes](https://github.com/netdata/netdata/blob/master/docs/netdata-security.md#why-netdata-should-be-protected). If left accessible to the internet at large, the
local dashboard could reveal sensitive information about your infrastructure. For example, an attacker can view which
applications you run (databases, webservers, and so on), or see every user account on a node.
@@ -37,7 +41,7 @@ that align with your goals and your organization's standards.
This is the _recommended method for those who have connected their nodes to Netdata Cloud_ and prefer viewing real-time
metrics using the War Room Overview, Nodes view, and Cloud dashboards.
-You can disable the local dashboard (and API) but retain the encrypted Agent-Cloud link ([ACLK](/aclk/README.md)) that
+You can disable the local dashboard (and API) but retain the encrypted Agent-Cloud link ([ACLK](https://github.com/netdata/netdata/blob/master/aclk/README.md)) that
allows you to stream metrics on demand from your nodes via the Netdata Cloud interface. This change mitigates all
concerns about revealing metrics and system design to the internet at large, while keeping all the functionality you
need to view metrics and troubleshoot issues with Netdata Cloud.
@@ -50,17 +54,17 @@ static-threaded` setting, and change it to `none`.
mode = none
```
-Save and close the editor, then [restart your Agent](/docs/configure/start-stop-restart.md) using `sudo systemctl
+Save and close the editor, then [restart your Agent](https://github.com/netdata/netdata/blob/master/docs/configure/start-stop-restart.md) using `sudo systemctl
restart netdata`. If you try to visit the local dashboard to `http://NODE:19999` again, the connection will fail because
that node no longer serves its local dashboard.
-> See the [configuration basics doc](/docs/configure/nodes.md) for details on how to find `netdata.conf` and use
+> See the [configuration basics doc](https://github.com/netdata/netdata/blob/master/docs/configure/nodes.md) for details on how to find `netdata.conf` and use
> `edit-config`.
## Restrict access to the local dashboard
If you want to keep using the local dashboard, but don't want it exposed to the internet, you can restrict access with
-[access lists](/web/server/README.md#access-lists). This method also fully retains the ability to stream metrics
+[access lists](https://github.com/netdata/netdata/blob/master/web/server/README.md#access-lists). This method also fully retains the ability to stream metrics
on-demand through Netdata Cloud.
The `allow connections from` setting helps you allow only certain IP addresses or FQDN/hostnames, such as a trusted
@@ -68,7 +72,7 @@ static IP, only `localhost`, or connections from behind a management LAN.
By default, this setting is `localhost *`. This setting allows connections from `localhost` in addition to _all_
connections, using the `*` wildcard. You can change this setting using Netdata's [simple
-patterns](/libnetdata/simple_pattern/README.md).
+patterns](https://github.com/netdata/netdata/blob/master/libnetdata/simple_pattern/README.md).
```conf
[web]
@@ -95,8 +99,8 @@ The `allow connections from` setting is global and restricts access to the dashb
allow management from = localhost
```
-See the [web server](/web/server/README.md#access-lists) docs for additional details about access lists. You can take
-access lists one step further by [enabling SSL](/web/server/README.md#enabling-tls-support) to encrypt data from local
+See the [web server](https://github.com/netdata/netdata/blob/master/web/server/README.md#access-lists) docs for additional details about access lists. You can take
+access lists one step further by [enabling SSL](https://github.com/netdata/netdata/blob/master/web/server/README.md#enabling-tls-support) to encrypt data from local
dashboard in transit. The connection to Netdata Cloud is always secured with TLS.
## Use a reverse proxy
@@ -106,18 +110,18 @@ local dashboard and Netdata Cloud dashboards. You can use a reverse proxy to pas
enable HTTPS to encrypt metadata and metric values in transit.
We recommend Nginx, as it's what we use for our [demo server](https://london.my-netdata.io/), and we have a guide
-dedicated to [running Netdata behind Nginx](/docs/Running-behind-nginx.md).
+dedicated to [running Netdata behind Nginx](https://github.com/netdata/netdata/blob/master/docs/Running-behind-nginx.md).
-We also have guides for [Apache](/docs/Running-behind-apache.md), [Lighttpd](/docs/Running-behind-lighttpd.md),
-[HAProxy](/docs/Running-behind-haproxy.md), and [Caddy](/docs/Running-behind-caddy.md).
+We also have guides for [Apache](https://github.com/netdata/netdata/blob/master/docs/Running-behind-apache.md), [Lighttpd](https://github.com/netdata/netdata/blob/master/docs/Running-behind-lighttpd.md),
+[HAProxy](https://github.com/netdata/netdata/blob/master/docs/Running-behind-haproxy.md), and [Caddy](https://github.com/netdata/netdata/blob/master/docs/Running-behind-caddy.md).
## What's next?
-Read about [Netdata's security design](/docs/netdata-security.md) and our [blog
+Read about [Netdata's security design](https://github.com/netdata/netdata/blob/master/docs/netdata-security.md) and our [blog
post](https://www.netdata.cloud/blog/netdata-agent-dashboard/) about why the local Agent dashboard is both open and
secure by design.
-Next up, learn about [collectors](/docs/collect/how-collectors-work.md) to ensure you're gathering every essential
+Next up, learn about [collectors](https://github.com/netdata/netdata/blob/master/docs/collect/how-collectors-work.md) to ensure you're gathering every essential
metric about your node, its applications, and your infrastructure at large.
[![analytics](https://www.google-analytics.com/collect?v=1&aip=1&t=pageview&_s=1&ds=github&dr=https%3A%2F%2Fgithub.com%2Fnetdata%2Fnetdata&dl=https%3A%2F%2Fmy-netdata.io%2Fgithub%2Fdocs%2Fconfigure%2Fsecure-nodesa&_u=MAC~&cid=5792dfd7-8dc4-476b-af31-da2fdb9f93d2&tid=UA-64295674-3)](<>)
diff --git a/docs/configure/start-stop-restart.md b/docs/configure/start-stop-restart.md
index 4967fff0..3c04777d 100644
--- a/docs/configure/start-stop-restart.md
+++ b/docs/configure/start-stop-restart.md
@@ -1,12 +1,16 @@
<!--
title: "Start, stop, or restart the Netdata Agent"
description: "Manage the Netdata Agent daemon, load configuration changes, and troubleshoot stuck processes on systemd and non-systemd nodes."
-custom_edit_url: https://github.com/netdata/netdata/edit/master/docs/configure/start-stop-restart.md
+custom_edit_url: "https://github.com/netdata/netdata/edit/master/docs/configure/start-stop-restart.md"
+sidebar_label: "Start, stop, or restart the Netdata Agent"
+learn_status: "Published"
+learn_topic_type: "Tasks"
+learn_rel_path: "Operations"
-->
# Start, stop, or restart the Netdata Agent
-When you install the Netdata Agent, the [daemon](/daemon/README.md) is configured to start at boot and stop and
+When you install the Netdata Agent, the [daemon](https://github.com/netdata/netdata/blob/master/daemon/README.md) is configured to start at boot and stop and
restart/shutdown.
You will most often need to _restart_ the Agent to load new or editing configuration files. [Health
@@ -40,7 +44,7 @@ If you start the daemon this way, close it with `sudo killall netdata`.
## Using `netdatacli`
-The Netdata Agent also comes with a [CLI tool](/cli/README.md) capable of performing shutdowns. Start the Agent back up
+The Netdata Agent also comes with a [CLI tool](https://github.com/netdata/netdata/blob/master/cli/README.md) capable of performing shutdowns. Start the Agent back up
using your preferred method listed above.
```bash
@@ -80,19 +84,19 @@ again with `service netdata start`, or the appropriate method for your system.
## What's next?
-Learn more about [securing the Netdata Agent](/docs/configure/secure-nodes.md).
+Learn more about [securing the Netdata Agent](https://github.com/netdata/netdata/blob/master/docs/configure/secure-nodes.md).
You can also use the restart/reload methods described above to enable new features:
-- [Enable new collectors](/docs/collect/enable-configure.md) or tweak their behavior.
-- [Configure existing health alarms](/docs/monitor/configure-alarms.md) or create new ones.
-- [Enable notifications](/docs/monitor/enable-notifications.md) to receive updates about the health of your
+- [Enable new collectors](https://github.com/netdata/netdata/blob/master/docs/collect/enable-configure.md) or tweak their behavior.
+- [Configure existing health alarms](https://github.com/netdata/netdata/blob/master/docs/monitor/configure-alarms.md) or create new ones.
+- [Enable notifications](https://github.com/netdata/netdata/blob/master/docs/monitor/enable-notifications.md) to receive updates about the health of your
infrastructure.
-- Change [the long-term metrics retention period](/docs/store/change-metrics-storage.md) using the database engine.
+- Change [the long-term metrics retention period](https://github.com/netdata/netdata/blob/master/docs/store/change-metrics-storage.md) using the database engine.
### Related reference documentation
-- [Netdata Agent · Daemon](/daemon/README.md)
-- [Netdata Agent · Netdata CLI](/cli/README.md)
+- [Netdata Agent · Daemon](https://github.com/netdata/netdata/blob/master/daemon/README.md)
+- [Netdata Agent · Netdata CLI](https://github.com/netdata/netdata/blob/master/cli/README.md)
[![analytics](https://www.google-analytics.com/collect?v=1&aip=1&t=pageview&_s=1&ds=github&dr=https%3A%2F%2Fgithub.com%2Fnetdata%2Fnetdata&dl=https%3A%2F%2Fmy-netdata.io%2Fgithub%2Fdocs%2Fconfigure%2Fstart-stop-restart&_u=MAC~&cid=5792dfd7-8dc4-476b-af31-da2fdb9f93d2&tid=UA-64295674-3)](<>)
diff --git a/docs/contributing/contributing-documentation.md b/docs/contributing/contributing-documentation.md
index 68b861d4..da28272b 100644
--- a/docs/contributing/contributing-documentation.md
+++ b/docs/contributing/contributing-documentation.md
@@ -18,7 +18,7 @@ The Netdata team aggregates and publishes all documentation at [learn.netdata.cl
## Before you get started
Anyone interested in contributing to documentation should first read the [Netdata style
-guide](/docs/contributing/style-guide.md) and the [Netdata Community Code of Conduct](https://learn.netdata.cloud/contribute/code-of-conduct).
+guide](https://github.com/netdata/netdata/blob/master/docs/contributing/style-guide.md) and the [Netdata Community Code of Conduct](https://github.com/netdata/.github/blob/main/CODE_OF_CONDUCT.md).
Netdata's documentation uses Markdown syntax. If you're not familiar with Markdown, read the [Mastering
Markdown](https://guides.github.com/features/mastering-markdown/) guide from GitHub for the basics on creating
@@ -40,7 +40,7 @@ Netdata's documentation is separated into four sections.
- Published under the **Reference** section in the Netdata Learn sidebar.
- **Netdata Cloud reference**: Reference documentation for the closed-source Netdata Cloud web application.
- Stored in a private GitHub repository and not editable by the community.
- - Published at [`https://learn.netdata.cloud/docs/cloud`](https://learn.netdata.cloud/docs/cloud).
+ - Published at [`https://github.com/netdata/netdata/blob/master/docs/cloud/cloud.mdx`](https://github.com/netdata/netdata/blob/master/docs/cloud/cloud.mdx).
- **Guides**: Solutions-based articles for users who want instructions on completing a specific complex task using the
Netdata Agent and/or Netdata Cloud.
- Stored in the [`/docs/guides` folder](https://github.com/netdata/netdata/tree/master/docs/guides) within the
@@ -59,7 +59,7 @@ fixes to a single document, such as fixing a typo or clarifying a confusing sent
Click on the **Edit this page** button on any published document on [Netdata Learn](https://learn.netdata.cloud). Each
page has two of these buttons: One beneath the table of contents, and another at the end of the document, which take you
-to GitHub's code editor. Make your suggested changes, keeping [Netdata style guide](/docs/contributing/style-guide.md)
+to GitHub's code editor. Make your suggested changes, keeping [Netdata style guide](https://github.com/netdata/netdata/blob/master/docs/contributing/style-guide.md)
in mind, and use *Preview changes** button to ensure your Markdown syntax works as expected.
Under the **Commit changes** header, write descriptive title for your requested change. Click the **Commit changes**
@@ -86,7 +86,7 @@ git clone https://github.com/YOUR-GITHUB-USERNAME/netdata.git
```
Create a new branch using `git checkout -b BRANCH-NAME`. Use your favorite text editor to make your changes, keeping the
-[Netdata style guide](/docs/contributing/style-guide.md) in mind. Add, commit, and push changes to your fork. When
+[Netdata style guide](https://github.com/netdata/netdata/blob/master/docs/contributing/style-guide.md) in mind. Add, commit, and push changes to your fork. When
you're finished, visit the [Netdata Agent Pull requests](https://github.com/netdata/netdata/pulls) to create a new pull
request based on the changes you made in the new branch of your fork.
diff --git a/docs/contributing/style-guide.md b/docs/contributing/style-guide.md
index 5ff61164..7d1b8647 100644
--- a/docs/contributing/style-guide.md
+++ b/docs/contributing/style-guide.md
@@ -67,8 +67,8 @@ Netdata is a global company in every sense, with employees, contributors, and us
communicate in a way that is clear and easily understood by everyone.
Here are some guidelines, pointers, and questions to be aware of as you write to ensure your writing is universal. Some
-of these are expanded into individual sections in the [language, grammar, and
-mechanics](#language-grammar-and-mechanics) section below.
+of these are expanded into individual sections in
+the [language, grammar, and mechanics](#language-grammar-and-mechanics) section below.
- Would this language make sense to someone who doesn't work here?
- Could someone quickly scan this document and understand the material?
@@ -97,8 +97,8 @@ mechanics](#language-grammar-and-mechanics) section below.
To ensure Netdata's writing is clear, concise, and universal, we have established standards for language, grammar, and
certain writing mechanics. However, if you're writing about Netdata for an external publication, such as a guest blog
-post, follow that publication's style guide or standards, while keeping the [preferred spelling of Netdata
-terms](#netdata-specific-terms) in mind.
+post, follow that publication's style guide or standards, while keeping
+the [preferred spelling of Netdata terms](#netdata-specific-terms) in mind.
### Active voice
@@ -106,31 +106,32 @@ Active voice is more concise and easier to understand compared to passive voice.
the sentence is action. In passive voice, the subject is acted upon. A famous example of passive voice is the phrase
"mistakes were made."
-| | |
-|-----------------|---------------------------------------------------------------------------------------------|
-| Not recommended | When an alarm is triggered by a metric, a notification is sent by Netdata. |
-| **Recommended** | When a metric triggers an alarm, Netdata sends a notification to your preferred endpoint. |
+| | |
+|-----------------|-------------------------------------------------------------------------------------------|
+| Not recommended | When an alarm is triggered by a metric, a notification is sent by Netdata. |
+| **Recommended** | When a metric triggers an alarm, Netdata sends a notification to your preferred endpoint. |
### Second person
-Use the second person ("you") to give instructions or "talk" directly to users.
+Use the second person ("you") to give instructions or "talk" directly to users.
In these situations, avoid "we," "I," "let's," and "us," particularly in documentation. The "you" pronoun can also be
-implied, depending on your sentence structure.
+implied, depending on your sentence structure.
One valid exception is when a member of the Netdata team or community wants to write about said team or community.
-| | |
-|--------------------------------|-------------------------------------------------------------------------------------------|
-| Not recommended | To install Netdata, we should try the one-line installer... |
-| **Recommended** | To install Netdata, you should try the one-line installer... |
-| **Recommended**, implied "you" | To install Netdata, try the one-line installer... |
+| | |
+|--------------------------------|--------------------------------------------------------------|
+| Not recommended | To install Netdata, we should try the one-line installer... |
+| **Recommended** | To install Netdata, you should try the one-line installer... |
+| **Recommended**, implied "you" | To install Netdata, try the one-line installer... |
### "Easy" or "simple"
-Using words that imply the complexity of a task or feature goes against our policy of [universal
-communication](#universal-communication). If you claim that a task is easy and the reader struggles to complete it, you
-may inadvertently discourage them.
+Using words that imply the complexity of a task or feature goes against our policy
+of [universal communication](#universal-communication). If you claim that a task is easy and the reader struggles to
+complete it, you
+may inadvertently discourage them.
However, if you give users two options and want to relay that one option is genuinely less complex than another, be
specific about how and why.
@@ -163,11 +164,11 @@ See the [word list](#word-list) for spellings of specific words.
Follow the general [English standards](https://owl.purdue.edu/owl/general_writing/mechanics/help_with_capitals.html) for
capitalization. In summary:
-- Capitalize the first word of every new sentence.
-- Don't use uppercase for emphasis. (Netdata is the BEST!)
-- Capitalize the names of brands, software, products, and companies according to their official guidelines. (Netdata,
- Docker, Apache, NGINX)
-- Avoid camel case (NetData) or all caps (NETDATA).
+- Capitalize the first word of every new sentence.
+- Don't use uppercase for emphasis. (Netdata is the BEST!)
+- Capitalize the names of brands, software, products, and companies according to their official guidelines. (Netdata,
+ Docker, Apache, NGINX)
+- Avoid camel case (NetData) or all caps (NETDATA).
Whenever you refer to the company Netdata, Inc., or the open-source monitoring agent the company develops, capitalize
**Netdata**.
@@ -244,10 +245,10 @@ must reflect the _current state of [production](https://app.netdata.cloud).
Every link should clearly state its destination. Don't use words like "here" to describe where a link will take your
reader.
-| | |
-|-----------------|-------------------------------------------------------------------------------------------|
-| Not recommended | To install Netdata, click [here](/packaging/installer/README.md). |
-| **Recommended** | To install Netdata, read the [installation instructions](/packaging/installer/README.md). |
+| | |
+|-----------------|-----------------------------------------------------------------------------------------------------------------------------------------|
+| Not recommended | To install Netdata, click [here](https://github.com/netdata/netdata/blob/master/packaging/installer/README.md). |
+| **Recommended** | To install Netdata, read the [installation instructions](https://github.com/netdata/netdata/blob/master/packaging/installer/README.md). |
Use links as often as required to provide necessary context. Blog posts and guides require less hyperlinks than
documentation. See the section on [linking between documentation](#linking-between-documentation) for guidance on the
@@ -268,7 +269,7 @@ and desired audience.
## Technical/Linux standards
Configuration or maintenance of the Netdata Agent requires some system administration skills, such as navigating
-directories, editing files, or starting/stopping/restarting services. Certain processes
+directories, editing files, or starting/stopping/restarting services. Certain processes
### Switching Linux users
@@ -302,16 +303,17 @@ Netdata Agent installation will have commands under the same paths. When applica
path, providing a recommendation or instructions on how to view the running configuration, which includes the correct
paths.
-For example, the [configuration](/docs/configure/nodes.md) doc first teaches users how to find the Netdata config
+For example, the [configuration](https://github.com/netdata/netdata/blob/master/docs/configure/nodes.md) doc first
+teaches users how to find the Netdata config
directory and navigate to it, then runs commands from the `/etc/netdata` path so that the instructions are more
universal.
Don't include full paths, beginning from the system's root (`/`), as these might not work on certain systems.
-| | |
-|-----------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
-| Not recommended | Use `edit-config` to edit Netdata's configuration: `sudo /etc/netdata/edit-config netdata.conf`. |
-| **Recommended** | Use `edit-config` to edit Netdata's configuration by first navigating to your [Netdata config directory](/docs/configure/nodes.md#the-netdata-config-directory), which is typically at `/etc/netdata`, then running `sudo edit-config netdata.conf`. |
+| | |
+|-----------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| Not recommended | Use `edit-config` to edit Netdata's configuration: `sudo /etc/netdata/edit-config netdata.conf`. |
+| **Recommended** | Use `edit-config` to edit Netdata's configuration by first navigating to your [Netdata config directory](https://github.com/netdata/netdata/blob/master/docs/configure/nodes.md#the-netdata-config-directory), which is typically at `/etc/netdata`, then running `sudo edit-config netdata.conf`. |
### `sudo`
@@ -371,8 +373,8 @@ Some documents, like the Ansible guide and others in the `/docs/guides` folder,
this case, replace `/docs` with `/img/seo`, and then rebuild the remainder of the path to the document in question. End
the path with `.png`. A member of the Netdata team will assist in creating the image when publishing the content.
-For example, here is the frontmatter for the guide about [deploying the Netdata Agent with
-Ansible](https://learn.netdata.cloud/guides/deploy/ansible).
+For example, here is the frontmatter for the guide
+about [deploying the Netdata Agent with Ansible](https://github.com/netdata/netdata/blob/master/docs/guides/deploy/ansible.md).
```markdown
<!--
@@ -387,8 +389,8 @@ custom_edit_url: https://github.com/netdata/netdata/edit/master/docs/guides/depl
...
```
-Questions about frontmatter in documentation? [Ask on our community
-forum](https://community.netdata.cloud/c/blog-posts-and-articles/6).
+Questions about frontmatter in
+documentation? [Ask on our community forum](https://community.netdata.cloud/c/blog-posts-and-articles/6).
### Linking between documentation
@@ -397,7 +399,8 @@ Documentation should link to relevant pages whenever it's relevant and provides
Links should always reference the full path to the document, beginning at the root of the Netdata Agent repository
(`/`), and ending with the `.md` file extension. Avoid relative links or traversing up directories using `../`.
-For example, if you want to link to our node configuration document, link to `/docs/configure/nodes.md`. To reference
+For example, if you want to link to our node configuration document, link
+to `https://github.com/netdata/netdata/blob/master/docs/configure/nodes.md`. To reference
the guide for deploying the Netdata Agent with Ansible, link to `/docs/guides/deploy/ansible.md`.
### References to UI elements
@@ -421,7 +424,7 @@ In Markdown, use the standard image syntax, `![]()`, and place the alt text betw
using our logo:
```markdown
-![The Netdata logo](/web/gui/static/img/netdata-logomark.svg)
+![The Netdata logo](https://github.com/netdata/netdata/blob/master/web/gui/static/img/netdata-logomark.svg)
```
Reference in-product text, code samples, and terminal output with actual text content, not screen captures or other
@@ -458,8 +461,8 @@ inline char *health_stock_config_dir(void) {
}
```
-Prism also supports titles and line highlighting. See the [Docusaurus
-documentation](https://v2.docusaurus.io/docs/markdown-features#code-blocks) for more information.
+Prism also supports titles and line highlighting. See
+the [Docusaurus documentation](https://v2.docusaurus.io/docs/markdown-features#code-blocks) for more information.
## Word list
@@ -469,24 +472,24 @@ The following tables describe the standard spelling, capitalization, and usage o
| Term | Definition |
|-----------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
-| **claimed node** | A node that you've proved ownership of by completing the [connecting to Cloud process](/claim/README.md). The claimed node will then appear in your Space and any War Rooms you added it to. |
+| **claimed node** | A node that you've proved ownership of by completing the [connecting to Cloud process](https://github.com/netdata/netdata/blob/master/claim/README.md). The claimed node will then appear in your Space and any War Rooms you added it to. |
| **Netdata** | The company behind the open-source Netdata Agent and the Netdata Cloud web application. Never use _netdata_ or _NetData_. <br /><br />In general, focus on the user's goals, actions, and solutions rather than what the company provides. For example, write _Learn more about enabling alarm notifications on your preferred platforms_ instead of _Netdata sends alarm notifications to your preferred platforms_. |
| **Netdata Agent** | The free and open source [monitoring agent](https://github.com/netdata/netdata) that you can install on all of your distributed systems, whether they're physical, virtual, containerized, ephemeral, and more. The Agent monitors systems running Linux, Docker, Kubernetes, macOS, FreeBSD, and more, and collects metrics from hundreds of popular services and applications. |
| **Netdata Cloud** | The web application hosted at [https://app.netdata.cloud](https://app.netdata.cloud) that helps you monitor an entire infrastructure of distributed systems in real time. <br /><br />Never use _Cloud_ without the preceding _Netdata_ to avoid ambiguity. |
-| **Netdata community** | Contributors to any of Netdata's [open-source projects](https://learn.netdata.cloud/contribute/projects), members of the [community forum](https://community.netdata.cloud/). |
+| **Netdata community** | Contributors to any of Netdata's [open-source projects](https://github.com/netdata/learn/blob/master/contribute/projects.mdx), members of the [community forum](https://community.netdata.cloud/). |
| **Netdata community forum** | The Discourse-powered forum for feature requests, Netdata Cloud technical support, and conversations about Netdata's monitoring and troubleshooting products. |
| **node** | A system on which the Netdata Agent is installed. The system can be physical, virtual, in a Docker container, and more. Depending on your infrastructure, you may have one, dozens, or hundreds of nodes. Some nodes are _ephemeral_, in that they're created/destroyed automatically by an orchestrator service. |
| **Space** | The highest level container within Netdata Cloud for a user to organize their team members and nodes within their infrastructure. A Space likely represents an entire organization or a large team. <br /><br />_Space_ is always capitalized. |
-| **unreachable node** | A connected node with a disrupted [Agent-Cloud link](/aclk/README.md). Unreachable could mean the node no longer exists or is experiencing network connectivity issues with Cloud. |
+| **unreachable node** | A connected node with a disrupted [Agent-Cloud link](https://github.com/netdata/netdata/blob/master/aclk/README.md). Unreachable could mean the node no longer exists or is experiencing network connectivity issues with Cloud. |
| **visited node** | A node which has had its Agent dashboard directly visited by a user. A list of these is maintained on a per-user basis. |
| **War Room** | A smaller grouping of nodes where users can view key metrics in real-time and monitor the health of many nodes with their alarm status. War Rooms can be used to organize nodes in any way that makes sense for your infrastructure, such as by a service, purpose, physical location, and more. <br /><br />_War Room_ is always capitalized. |
### Other technical terms
-| Term | Definition |
-|-----------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
-| **filesystem** | Use instead of _file system_. |
-| **preconfigured** | The concept that many of Netdata's features come with sane defaults that users don't need to configure to find [immediate value](/docs/overview/why-netdata.md#simple-to-deploy). |
-| **real time**/**real-time** | Use _real time_ as a noun phrase, most often with _in_: _Netdata collects metrics in real time_. Use _real-time_ as an adjective: _Netdata collects real-time metrics from hundreds of supported applications and services. |
+| Term | Definition |
+|-----------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| **filesystem** | Use instead of _file system_. |
+| **preconfigured** | The concept that many of Netdata's features come with sane defaults that users don't need to configure to find [immediate value](https://github.com/netdata/netdata/blob/master/docs/overview/why-netdata.md#simple-to-deploy). |
+| **real time**/**real-time** | Use _real time_ as a noun phrase, most often with _in_: _Netdata collects metrics in real time_. Use _real-time_ as an adjective: _Netdata collects real-time metrics from hundreds of supported applications and services. |
diff --git a/docs/dashboard/customize.mdx b/docs/dashboard/customize.mdx
index 8f0b222f..3c30ee23 100644
--- a/docs/dashboard/customize.mdx
+++ b/docs/dashboard/customize.mdx
@@ -1,13 +1,19 @@
---
title: "Customize the standard dashboard"
-description: "Netdata's preconfigured dashboard offers many customization options, such as choosing when charts are updated, your preferred theme, and custom text to document processes, and more."
-type: how-to
-custom_edit_url: https://github.com/netdata/netdata/edit/master/docs/dashboard/customize.mdx
+description: >-
+ "Netdata's preconfigured dashboard offers many customization options, such as choosing when
+ charts are updated, your preferred theme, and custom text to document processes, and more."
+type: "how-to"
+custom_edit_url: "https://github.com/netdata/netdata/blob/master/docs/dashboard/customize.mdx"
+sidebar_label: "Customize the standard dashboard"
+learn_status: "Published"
+learn_topic_type: "Tasks"
+learn_rel_path: "Operations"
---
# Customize the standard dashboard
-While the [Netdata dashboard](/docs/dashboard/how-dashboard-works.mdx) comes preconfigured with hundreds of charts and
+While the [Netdata dashboard](https://github.com/netdata/netdata/blob/master/docs/dashboard/how-dashboard-works.mdx) comes preconfigured with hundreds of charts and
thousands of metrics, you may want to alter your experience based on a particular use case or preferences.
## Dashboard settings
@@ -24,7 +30,7 @@ Here are a few popular settings:
### Change chart legend position
-Find this setting under the **Visual** tab. By default, Netdata places the [legend of dimensions](/docs/dashboard/dimensions-contexts-families.mdx#dimension) _below_ charts.
+Find this setting under the **Visual** tab. By default, Netdata places the [legend of dimensions](https://github.com/netdata/netdata/blob/master/docs/dashboard/dimensions-contexts-families.mdx#dimension) _below_ charts.
Click this toggle to move the legend to the _right_ of charts.
@@ -67,7 +73,7 @@ Edit the file with customizations to the `title`, `icon`, and `info` fields. Rep
icon from [Font Awesome](https://fontawesome.com/cheatsheet) to customize the icons that appear throughout the
dashboard.
-Save the file, then navigate to your [Netdata config directory](/docs/configure/nodes.md) to edit `netdata.conf`. Add
+Save the file, then navigate to your [Netdata config directory](https://github.com/netdata/netdata/blob/master/docs/configure/nodes.md) to edit `netdata.conf`. Add
the following line to the `[web]` section to tell Netdata where to find your custom configuration.
```conf
@@ -80,14 +86,14 @@ Reload your browser tab to see your custom configuration.
## What's next?
If you're keen on continuing to customize your Netdata experience, check out our docs on [building new custom
-dashboards](/web/gui/custom/README.md) with HTML, CSS, and JavaScript.
+dashboards](https://github.com/netdata/netdata/blob/master/web/gui/custom/README.md) with HTML, CSS, and JavaScript.
### Further reading & related information
- Dashboard
- - [How the dashboard works](/docs/dashboard/how-dashboard-works.mdx)
- - [Interact with charts](/docs/dashboard/interact-charts.mdx)
- - [Chart dimensions, contexts, and families](/docs/dashboard/dimensions-contexts-families.mdx)
- - [Select timeframes to visualize](/docs/dashboard/visualization-date-and-time-controls.mdx)
- - [Import, export, and print a snapshot](/docs/dashboard/import-export-print-snapshot.mdx)
- - **[Customize the standard dashboard](/docs/dashboard/customize.mdx)**
+ - [How the dashboard works](https://github.com/netdata/netdata/blob/master/docs/dashboard/how-dashboard-works.mdx)
+ - [Interact with charts](https://github.com/netdata/netdata/blob/master/docs/dashboard/interact-charts.mdx)
+ - [Chart dimensions, contexts, and families](https://github.com/netdata/netdata/blob/master/docs/dashboard/dimensions-contexts-families.mdx)
+ - [Select timeframes to visualize](https://github.com/netdata/netdata/blob/master/docs/dashboard/visualization-date-and-time-controls.mdx)
+ - [Import, export, and print a snapshot](https://github.com/netdata/netdata/blob/master/docs/dashboard/import-export-print-snapshot.mdx)
+ - **[Customize the standard dashboard](https://github.com/netdata/netdata/blob/master/docs/dashboard/customize.mdx)**
diff --git a/docs/dashboard/dimensions-contexts-families.mdx b/docs/dashboard/dimensions-contexts-families.mdx
index 0c6dd2ad..ee9636d1 100644
--- a/docs/dashboard/dimensions-contexts-families.mdx
+++ b/docs/dashboard/dimensions-contexts-families.mdx
@@ -1,20 +1,26 @@
---
title: "Chart dimensions, contexts, and families"
-description: "Netdata organizes charts into dimensions, contexts, and families to automatically and meaningfully organize thousands of metrics into interactive charts."
-type: explanation
-custom_edit_url: https://github.com/netdata/netdata/edit/master/docs/dashboard/dimensions-contexts-families.mdx
+description: >-
+ "Netdata organizes charts into dimensions, contexts, and families to automatically
+ and meaningfully organize thousands of metrics into interactive charts."
+type: "explanation"
+custom_edit_url: "https://github.com/netdata/netdata/blob/master/docs/dashboard/dimensions-contexts-families.mdx"
+sidebar_label: "Chart dimensions, contexts, and families"
+learn_status: "Published"
+learn_topic_type: "Concepts"
+learn_rel_path: "Concepts"
---
# Chart dimensions, contexts, and families
-While Netdata's charts require no configuration and are [easy to interact with](/docs/dashboard/interact-charts.mdx),
+While Netdata's charts require no configuration and are [easy to interact with](https://github.com/netdata/netdata/blob/master/docs/dashboard/interact-charts.mdx),
they have a lot of underlying complexity. To meaningfully organize charts out of the box based on what's happening in
your nodes, Netdata uses the concepts of **dimensions**, **contexts**, and **families**.
Understanding how these work will help you more easily navigate the dashboard, [write new
-alarms](/docs/monitor/configure-alarms.md), or play around with the [API](/web/api/README.md).
+alarms](https://github.com/netdata/netdata/blob/master/docs/monitor/configure-alarms.md), or play around with the [API](https://github.com/netdata/netdata/blob/master/web/api/README.md).
-For a refresher on the anatomy of a chart, see [dashboards and charts](/docs/dashboard/how-dashboard-works.mdx).
+For a refresher on the anatomy of a chart, see [dashboards and charts](https://github.com/netdata/netdata/blob/master/docs/dashboard/how-dashboard-works.mdx).
## Dimension
@@ -36,7 +42,7 @@ dimensions](https://user-images.githubusercontent.com/1153921/114207816-a5cb7400
The chart shows 13 unique dimensions, such as `httpd` for the CPU utilization for web servers, `kernel` for anything
related to the Linux kernel, and so on. In your dashboard, these specific dimensions will almost certainly be different.
-Dimensions can be [hidden](/docs/dashboard/interact-charts.mdx#show-and-hide-dimensions) to help you focus your
+Dimensions can be [hidden](https://github.com/netdata/netdata/blob/master/docs/dashboard/interact-charts.mdx#show-and-hide-dimensions) to help you focus your
attention.
## Context
@@ -50,8 +56,8 @@ whereas anything after the `.` is specified either by the chart's developer or b
By default, a chart's type affects where it fits in the menu, while its family creates submenus.
-Netdata also relies on contexts for [alarm configuration](/docs/monitor/configure-alarms.md) (the [`on`
-line](/health/REFERENCE.md#alarm-line-on)).
+Netdata also relies on contexts for [alarm configuration](https://github.com/netdata/netdata/blob/master/docs/monitor/configure-alarms.md) (the [`on`
+line](https://github.com/netdata/netdata/blob/master/health/REFERENCE.md#alarm-line-on)).
## Family
@@ -79,18 +85,18 @@ names:
## What's next?
With an understanding of a chart's dimensions, context, and family, you're now ready to dig even deeper into Netdata's
-dashboard. We recommend looking into [using the timeframe selector](/docs/dashboard/visualization-date-and-time-controls.mdx).
+dashboard. We recommend looking into [using the timeframe selector](https://github.com/netdata/netdata/blob/master/docs/dashboard/visualization-date-and-time-controls.mdx).
-If you feel comfortable with the [dashboard](/docs/dashboard/how-dashboard-works.mdx) and interacting with charts, we
-recommend learning about [configuration](/docs/configure/nodes.md). While Netdata doesn't _require_ a complicated setup
+If you feel comfortable with the [dashboard](https://github.com/netdata/netdata/blob/master/docs/dashboard/how-dashboard-works.mdx) and interacting with charts, we
+recommend learning about [configuration](https://github.com/netdata/netdata/blob/master/docs/configure/nodes.md). While Netdata doesn't _require_ a complicated setup
process or a query language to create charts, there are a lot of ways to tweak the experience to match your needs.
### Further reading & related information
- Dashboard
- - [How the dashboard works](/docs/dashboard/how-dashboard-works.mdx)
- - [Interact with charts](/docs/dashboard/interact-charts.mdx)
- - **[Chart dimensions, contexts, and families](/docs/dashboard/dimensions-contexts-families.mdx)**
- - [Select timeframes to visualize](/docs/dashboard/visualization-date-and-time-controls.mdx)
- - [Import, export, and print a snapshot](/docs/dashboard/import-export-print-snapshot.mdx)
- - [Customize the standard dashboard](/docs/dashboard/customize.mdx)
+ - [How the dashboard works](https://github.com/netdata/netdata/blob/master/docs/dashboard/how-dashboard-works.mdx)
+ - [Interact with charts](https://github.com/netdata/netdata/blob/master/docs/dashboard/interact-charts.mdx)
+ - **[Chart dimensions, contexts, and families](https://github.com/netdata/netdata/blob/master/docs/dashboard/dimensions-contexts-families.mdx)**
+ - [Select timeframes to visualize](https://github.com/netdata/netdata/blob/master/docs/dashboard/visualization-date-and-time-controls.mdx)
+ - [Import, export, and print a snapshot](https://github.com/netdata/netdata/blob/master/docs/dashboard/import-export-print-snapshot.mdx)
+ - [Customize the standard dashboard](https://github.com/netdata/netdata/blob/master/docs/dashboard/customize.mdx)
diff --git a/docs/dashboard/how-dashboard-works.mdx b/docs/dashboard/how-dashboard-works.mdx
index 315614d6..f1440270 100644
--- a/docs/dashboard/how-dashboard-works.mdx
+++ b/docs/dashboard/how-dashboard-works.mdx
@@ -1,8 +1,14 @@
---
title: "How the dashboard works"
-description: "Learn how to navigate Netdata's preconfigured dashboard to get started exploring, visualizing, and troubleshooting in real time."
-type: explanation
-custom_edit_url: https://github.com/netdata/netdata/edit/master/docs/dashboard/how-dashboard-works.mdx
+description: >-
+ "Learn how to navigate Netdata's preconfigured dashboard to get started
+ exploring, visualizing, and troubleshooting in real time."
+type: "explanation"
+custom_edit_url: "https://github.com/netdata/netdata/blob/master/docs/dashboard/how-dashboard-works.mdx"
+sidebar_label: "How the dashboard works"
+learn_status: "Published"
+learn_topic_type: "Concepts"
+learn_rel_path: "Concepts"
---
# How the dashboard works
@@ -10,7 +16,7 @@ custom_edit_url: https://github.com/netdata/netdata/edit/master/docs/dashboard/h
Because Netdata is a monitoring and _troubleshooting_ platform, a dashboard with real-time, meaningful, and
context-aware charts is essential.
-As soon as you [install Netdata](/docs/get-started.mdx), it autodetects hardware, OS, containers, services, and
+As soon as you [install Netdata](https://github.com/netdata/netdata/blob/master/docs/get-started.mdx), it autodetects hardware, OS, containers, services, and
applications running on your node and builds a dashboard on a single, scrollable webpage. This page features hundreds of
charts, which are preconfigured to save you time from learning a query language, all stacked on top of one another. This
vertical rhythm is designed to encourage exploration and help you visually identify connections between the metrics
@@ -27,7 +33,7 @@ Access Netdata's dashboard by navigating to `http://NODE:19999` in your browser,
![Animated GIF of navigating to the
dashboard](https://user-images.githubusercontent.com/1153921/80825153-abaec600-8b94-11ea-8b17-1b770a2abaa9.gif)
-Many features of the internal web server that serves the dashboard are [configurable](/web/server/README.md), including
+Many features of the internal web server that serves the dashboard are [configurable](https://github.com/netdata/netdata/blob/master/web/server/README.md), including
the listen port, enforced TLS, and even disabling the dashboard altogether.
## Sections and menus
@@ -48,12 +54,12 @@ submenus](https://user-images.githubusercontent.com/1153921/80832425-7c528600-8b
## Charts
-Every **chart** in the Netdata dashboard is [fully interactive](/docs/dashboard/interact-charts.mdx). Netdata
+Every **chart** in the Netdata dashboard is [fully interactive](https://github.com/netdata/netdata/blob/master/docs/dashboard/interact-charts.mdx). Netdata
synchronizes your interactions to help you understand exactly how a node behaved in any timeframe, whether that's
seconds or days.
A chart is an individual, interactive, always-updating graphic displaying one or more collected/calculated metrics,
-which are generated by [collectors](/docs/collect/how-collectors-work.md).
+which are generated by [collectors](https://github.com/netdata/netdata/blob/master/docs/collect/how-collectors-work.md).
![Animated GIF of the standard Netdata dashboard being manipulated and synchronizing
charts](https://user-images.githubusercontent.com/1153921/80839230-b034a800-8baf-11ea-9cb2-99c1e10f0f85.gif)
@@ -70,7 +76,7 @@ like pushing the current timeframe off the screen to see what came before or aft
To zoom, press and hold `Shift`, then use your mouse's scroll wheel, or a two-finger pinch if you're using a touchpad.
-See [interact with charts](/docs/dashboard/interact-charts.mdx) for all the possible ways to interact with the charts on
+See [interact with charts](https://github.com/netdata/netdata/blob/master/docs/dashboard/interact-charts.mdx) for all the possible ways to interact with the charts on
your dashboard.
## Alarms
@@ -78,7 +84,7 @@ your dashboard.
Many of the preconfigured charts on the Netdata dashboard also come with preconfigured alarms. Netdata sends three
primary alarm states via alarms: `CLEAR`, `WARNING`, and `CRITICAL`. If an alarm moves from a `CLEAR` state to either
`WARNING` or `CRITICAL`, Netdata creates a notification to let you know exactly what's going on. There are [other alarm
-states](/health/REFERENCE.md#alarm-statuses) as well.
+states](https://github.com/netdata/netdata/blob/master/health/REFERENCE.md#alarm-statuses) as well.
The easiest way to see alarms is by clicking on the alarm icon ![Alarms
icon](https://raw.githubusercontent.com/netdata/netdata-ui/98e31799c1ec0983f433537ff16d2ac2b0d994aa/src/components/icon/assets/alarm.svg)
@@ -88,25 +94,25 @@ active alarm, and the **Log** tab shows a historical record of exactly when alar
![Animated GIF of looking at raised alarms and the alarm
log](https://user-images.githubusercontent.com/1153921/80842482-8c289500-8bb6-11ea-9791-600cfdbe82ce.gif)
-Learn more about [viewing active alarms](/docs/monitor/view-active-alarms.md), [configuring
-alarms](/docs/monitor/configure-alarms.md), or [enabling a new notification
-method](/docs/monitor/enable-notifications.md).
+Learn more about [viewing active alarms](https://github.com/netdata/netdata/blob/master/docs/monitor/view-active-alarms.md), [configuring
+alarms](https://github.com/netdata/netdata/blob/master/docs/monitor/configure-alarms.md), or [enabling a new notification
+method](https://github.com/netdata/netdata/blob/master/docs/monitor/enable-notifications.md).
## What's next?
-Learn more about [interacting with charts](/docs/dashboard/interact-charts.mdx) to quickly pan through time, zoom, and
+Learn more about [interacting with charts](https://github.com/netdata/netdata/blob/master/docs/dashboard/interact-charts.mdx) to quickly pan through time, zoom, and
show/hide dimensions to best understand the state of your node in any timeframe. A complete understanding of [chart
-dimensions, contexts, and families](/docs/dashboard/dimensions-contexts-families.mdx) will also help with how Netdata
-organizes its dashboard and operates [alarms](/docs/monitor/configure-alarms.md).
+dimensions, contexts, and families](https://github.com/netdata/netdata/blob/master/docs/dashboard/dimensions-contexts-families.mdx) will also help with how Netdata
+organizes its dashboard and operates [alarms](https://github.com/netdata/netdata/blob/master/docs/monitor/configure-alarms.md).
### Further reading & related information
- Dashboard
- - **[How the dashboard works](/docs/dashboard/how-dashboard-works.mdx)**
- - [Interact with charts](/docs/dashboard/interact-charts.mdx)
- - [Chart dimensions, contexts, and families](/docs/dashboard/dimensions-contexts-families.mdx)
- - [Select timeframes to visualize](/docs/dashboard/visualization-date-and-time-controls.mdx)
- - [Import, export, and print a snapshot](/docs/dashboard/import-export-print-snapshot.mdx)
- - [Customize the standard dashboard](/docs/dashboard/customize.mdx)
-- [HTTP API](/web/api/README.md)
-- [Custom dashboards](/web/gui/custom/README.md)
+ - **[How the dashboard works](https://github.com/netdata/netdata/blob/master/docs/dashboard/how-dashboard-works.mdx)**
+ - [Interact with charts](https://github.com/netdata/netdata/blob/master/docs/dashboard/interact-charts.mdx)
+ - [Chart dimensions, contexts, and families](https://github.com/netdata/netdata/blob/master/docs/dashboard/dimensions-contexts-families.mdx)
+ - [Select timeframes to visualize](https://github.com/netdata/netdata/blob/master/docs/dashboard/visualization-date-and-time-controls.mdx)
+ - [Import, export, and print a snapshot](https://github.com/netdata/netdata/blob/master/docs/dashboard/import-export-print-snapshot.mdx)
+ - [Customize the standard dashboard](https://github.com/netdata/netdata/blob/master/docs/dashboard/customize.mdx)
+- [HTTP API](https://github.com/netdata/netdata/blob/master/web/api/README.md)
+- [Custom dashboards](https://github.com/netdata/netdata/blob/master/web/gui/custom/README.md)
diff --git a/docs/dashboard/import-export-print-snapshot.mdx b/docs/dashboard/import-export-print-snapshot.mdx
index df43feb0..23430a56 100644
--- a/docs/dashboard/import-export-print-snapshot.mdx
+++ b/docs/dashboard/import-export-print-snapshot.mdx
@@ -1,8 +1,15 @@
---
title: "Import, export, and print a snapshot"
-description: "Snapshots can be incredibly useful for diagnosing anomalies after they've already happened, and are interoperable with any other node running Netdata."
-type: how-to
-custom_edit_url: https://github.com/netdata/netdata/edit/master/docs/dashboard/import-export-print-snapshot.mdx
+description: >-
+ "Snapshots can be incredibly useful for diagnosing anomalies after
+ they've already happened, and are interoperable with any other node
+ running Netdata."
+type: "how-to"
+custom_edit_url: "https://github.com/netdata/netdata/blob/master/docs/dashboard/import-export-print-snapshot.mdx"
+sidebar_label: "Import, export, and print a snapshot"
+learn_status: "Published"
+learn_topic_type: "Tasks"
+learn_rel_path: "Operations"
---
# Import, export, and print snapshots
@@ -12,7 +19,7 @@ node running Netdata. Or, you can create a print-ready version of your dashboard
paper.
Snapshots can be incredibly useful for diagnosing anomalies after they've already happened. Let's say Netdata triggered a warning alarm while you were asleep. In the morning, you can [select the
-timeframe](/docs/dashboard/visualization-date-and-time-controls.mdx) when the alarm triggered, export a snapshot, and send it to a
+timeframe](https://github.com/netdata/netdata/blob/master/docs/dashboard/visualization-date-and-time-controls.mdx) when the alarm triggered, export a snapshot, and send it to a
colleague for further analysis.
@@ -69,15 +76,15 @@ Netdata opens a browser print dialog for you to save to PDF or print.
## What's next?
Now that you understand snapshots, now is a good time to delve deeper into some of the dashboard's lesser-known
-features, such as [customization](/docs/dashboard/customize.mdx) or [building new, custom
-dashboards](/web/gui/custom/README.md).
+features, such as [customization](https://github.com/netdata/netdata/blob/master/docs/dashboard/customize.mdx) or [building new, custom
+dashboards](https://github.com/netdata/netdata/blob/master/web/gui/custom/README.md).
### Further reading & related information
- Dashboard
- - [How the dashboard works](/docs/dashboard/how-dashboard-works.mdx)
- - [Interact with charts](/docs/dashboard/interact-charts.mdx)
- - [Chart dimensions, contexts, and families](/docs/dashboard/dimensions-contexts-families.mdx)
- - [Select timeframes to visualize](/docs/dashboard/visualization-date-and-time-controls.mdx)
- - **[Import, export, and print a snapshot](/docs/dashboard/import-export-print-snapshot.mdx)**
- - [Customize the standard dashboard](/docs/dashboard/customize.mdx) \ No newline at end of file
+ - [How the dashboard works](https://github.com/netdata/netdata/blob/master/docs/dashboard/how-dashboard-works.mdx)
+ - [Interact with charts](https://github.com/netdata/netdata/blob/master/docs/dashboard/interact-charts.mdx)
+ - [Chart dimensions, contexts, and families](https://github.com/netdata/netdata/blob/master/docs/dashboard/dimensions-contexts-families.mdx)
+ - [Select timeframes to visualize](https://github.com/netdata/netdata/blob/master/docs/dashboard/visualization-date-and-time-controls.mdx)
+ - **[Import, export, and print a snapshot](https://github.com/netdata/netdata/blob/master/docs/dashboard/import-export-print-snapshot.mdx)**
+ - [Customize the standard dashboard](https://github.com/netdata/netdata/blob/master/docs/dashboard/customize.mdx) \ No newline at end of file
diff --git a/docs/dashboard/interact-charts.mdx b/docs/dashboard/interact-charts.mdx
index fe0f05e3..a733bc9e 100644
--- a/docs/dashboard/interact-charts.mdx
+++ b/docs/dashboard/interact-charts.mdx
@@ -1,13 +1,17 @@
---
title: "Interact with charts"
description: "Learn how to pan, zoom, select, and customize Netdata's preconfigured charts to help you troubleshooting with real-time, per-second metrics data."
-type: how-to
-custom_edit_url: https://github.com/netdata/netdata/edit/master/docs/dashboard/interact-charts.mdx
+type: "how-to"
+custom_edit_url: "https://github.com/netdata/netdata/edit/master/docs/dashboard/interact-charts.mdx"
+sidebar_label: "Interact with charts"
+learn_status: "Published"
+learn_topic_type: "Tasks"
+learn_rel_path: "Operations"
---
# Interact with charts
-> ⚠️ There is a new version of charts that is currently **only** available on [Netdata Cloud](https://learn.netdata.cloud/docs/cloud/visualize/interact-new-charts). We didn't
+> ⚠️ There is a new version of charts that is currently **only** available on [Netdata Cloud](https://github.com/netdata/netdata/blob/master/docs/cloud/visualize/interact-new-charts.md). We didn't
> want to keep this valuable feature from you, so after we get this into your hands on the Cloud, we will collect and implement your feedback to make sure we are providing the best possible version of the feature on the Netdata Agent dashboard as quickly as possible.
While charts that update every second with new metrics are helpful for understanding the immediate state of a node, deep
@@ -172,26 +176,26 @@ jobs:
### Future Work
-We already have [configurable host labels](https://learn.netdata.cloud/guides/using-host-labels) as well, which currently can’t be used to filter or group your metrics. We intend to provide the same capabilities described here with host labels, among other capabilities on other areas of the app as well
+We already have [configurable host labels](https://github.com/netdata/netdata/blob/master/docs/guides/using-host-labels.md) as well, which currently can’t be used to filter or group your metrics. We intend to provide the same capabilities described here with host labels, among other capabilities on other areas of the app as well
## What's next?
We recommend you read up on the differences between [chart dimensions, contexts, and
-families](/docs/dashboard/dimensions-contexts-families.mdx) to complete your understanding of how Netdata organizes its
+families](https://github.com/netdata/netdata/blob/master/docs/dashboard/dimensions-contexts-families.mdx) to complete your understanding of how Netdata organizes its
dashboards. Another valuable way to interact with charts is to use the [timeframe
-selector](/docs/dashboard/visualization-date-and-time-controls.mdx), which helps you visualize specific moments of historical metrics.
+selector](https://github.com/netdata/netdata/blob/master/docs/dashboard/visualization-date-and-time-controls.mdx), which helps you visualize specific moments of historical metrics.
-If you feel comfortable with the [dashboard](/docs/dashboard/how-dashboard-works.mdx) and interacting with charts, we
-recommend moving on to learning about [configuration](/docs/configure/nodes.md). While Netdata doesn't _require_ a
+If you feel comfortable with the [dashboard](https://github.com/netdata/netdata/blob/master/docs/dashboard/how-dashboard-works.mdx) and interacting with charts, we
+recommend moving on to learning about [configuration](https://github.com/netdata/netdata/blob/master/docs/configure/nodes.md). While Netdata doesn't _require_ a
complicated setup process or a query language to create charts, there are a lot of ways to tweak the experience to match
your needs.
### Further reading & related information
- Dashboard
- - [How the dashboard works](/docs/dashboard/how-dashboard-works.mdx)
- - [Netdata Cloud · Interact with new charts](https://learn.netdata.cloud/docs/cloud/visualize/interact-new-charts)
- - [Chart dimensions, contexts, and families](/docs/dashboard/dimensions-contexts-families.mdx)
- - [Select timeframes to visualize](/docs/dashboard/visualization-date-and-time-controls.mdx)
- - [Import, export, and print a snapshot](/docs/dashboard/import-export-print-snapshot.mdx)
- - [Customize the standard dashboard](/docs/dashboard/customize.mdx)
+ - [How the dashboard works](https://github.com/netdata/netdata/blob/master/docs/dashboard/how-dashboard-works.mdx)
+ - [Netdata Cloud · Interact with new charts](https://github.com/netdata/netdata/blob/master/docs/cloud/visualize/interact-new-charts.md)
+ - [Chart dimensions, contexts, and families](https://github.com/netdata/netdata/blob/master/docs/dashboard/dimensions-contexts-families.mdx)
+ - [Select timeframes to visualize](https://github.com/netdata/netdata/blob/master/docs/dashboard/visualization-date-and-time-controls.mdx)
+ - [Import, export, and print a snapshot](https://github.com/netdata/netdata/blob/master/docs/dashboard/import-export-print-snapshot.mdx)
+ - [Customize the standard dashboard](https://github.com/netdata/netdata/blob/master/docs/dashboard/customize.mdx)
diff --git a/docs/dashboard/reference-web-server.mdx b/docs/dashboard/reference-web-server.mdx
index 55e761a2..f90e6f87 100644
--- a/docs/dashboard/reference-web-server.mdx
+++ b/docs/dashboard/reference-web-server.mdx
@@ -16,7 +16,7 @@ This web server respects the `keep-alive` HTTP header to serve multiple HTTP req
## Configuration
-From within your Netdata config directory (typically `/etc/netdata`), [use `edit-config`](/docs/configure/nodes.md) to
+From within your Netdata config directory (typically `/etc/netdata`), [use `edit-config`](https://github.com/netdata/netdata/blob/master/docs/configure/nodes.md) to
open `netdata.conf`.
```
@@ -33,8 +33,8 @@ Scroll down to the `[web]` section to find the following settings.
| `ssl certificate` | `/etc/netdata/ssl/cert.pem` | Declare the location of an SSL certificate to [enable HTTPS](#enable-httpstls-support). |
| `tls version` | `1.3` | Choose which TLS version to use. While all versions are allowed (`1` or `1.0`, `1.1`, `1.2` and `1.3`), we recommend `1.3` for the most secure encryption. If left blank, Netdata uses the highest available protocol version on your system. |
| `tls ciphers` | `none` | Choose which TLS cipher to use. Options include `TLS_AES_256_GCM_SHA384`, `TLS_CHACHA20_POLY1305_SHA256`, and `TLS_AES_128_GCM_SHA256`. If left blank, Netdata uses the default cipher list for that protocol provided by your TLS implementation. |
-| `ses max window` | `15` | See [single exponential smoothing](/web/api/queries/ses/README.md). |
-| `des max window` | `15` | See [double exponential smoothing](/web/api/queries/des/README.md). |
+| `ses max window` | `15` | See [single exponential smoothing](https://github.com/netdata/netdata/blob/master/web/api/queries/ses/README.md). |
+| `des max window` | `15` | See [double exponential smoothing](https://github.com/netdata/netdata/blob/master/web/api/queries/des/README.md). |
| `mode` | `static-threaded` | Turns on (`static-threaded` or off (`none`) the static-threaded web server. See the [example](#disable-the-web-server) to turn off the web server and disable the dashboard. |
| `listen backlog` | `4096` | The port backlog. Check `man 2 listen`. |
| `default port` | `19999` | The listen port for the static web server. |
@@ -42,10 +42,10 @@ Scroll down to the `[web]` section to find the following settings.
| `web files group` | `netdata` | If this is set, Netdata will check if the file is owned by this group and refuse to serve the file if it's not. |
| `disconnect idle clients after seconds` | `60` | The time in seconds to disconnect web clients after being totally idle. |
| `timeout for first request` | `60` | How long to wait for a client to send a request before closing the socket. Prevents slow request attacks. |
-| `accept a streaming request every seconds` | `0` | Can be used to set a limit on how often a parent node will accept streaming requests from child nodes in a [streaming and replication setup](/streaming/README.md). |
-| `respect do not track policy` | `no` | If set to `yes`, Netdata will respect the user's browser preferences for [Do Not Track](https://www.eff.org/issues/do-not-track) (DNT) and storing cookies. If DNT is _enabled_ in the browser, and this option is set to `yes`, users will not be able to sign in to Netdata Cloud via their local Agent dashboard, and their node will not connect to any [registry](/registry/README.md). For certain browsers, users must disable DNT and change this option to `yes` for full functionality. |
+| `accept a streaming request every seconds` | `0` | Can be used to set a limit on how often a parent node will accept streaming requests from child nodes in a [streaming and replication setup](https://github.com/netdata/netdata/blob/master/streaming/README.md). |
+| `respect do not track policy` | `no` | If set to `yes`, Netdata will respect the user's browser preferences for [Do Not Track](https://www.eff.org/issues/do-not-track) (DNT) and storing cookies. If DNT is _enabled_ in the browser, and this option is set to `yes`, users will not be able to sign in to Netdata Cloud via their local Agent dashboard, and their node will not connect to any [registry](https://github.com/netdata/netdata/blob/master/registry/README.md). For certain browsers, users must disable DNT and change this option to `yes` for full functionality. |
| `x-frame-options response header` | ` ` | Avoid [clickjacking attacks](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Frame-Options), by ensuring that the content is not embedded into other sites. |
-| `allow connections from` | `localhost *` | Declare which IP addresses or full-qualified domain names (FQDNs) are allowed to connect to the web server, including the [dashboard](/docs/dashboard/interact-charts.mdx) or [HTTP API](/web/api/README.md). This is a global setting with higher priority to any of the ones below. |
+| `allow connections from` | `localhost *` | Declare which IP addresses or full-qualified domain names (FQDNs) are allowed to connect to the web server, including the [dashboard](https://github.com/netdata/netdata/blob/master/docs/dashboard/interact-charts.mdx) or [HTTP API](https://github.com/netdata/netdata/blob/master/web/api/README.md). This is a global setting with higher priority to any of the ones below. |
| `allow connections by dns` | `heuristic` | See the [access list examples](#access-lists) for details on using `allow` settings. |
| `allow dashboard from` | `localhost *` | |
| `allow dashboard by dns` | `heuristic` | |
@@ -62,7 +62,7 @@ Scroll down to the `[web]` section to find the following settings.
| `gzip compression level` | `3` | Valid settings are 1 (fastest) to 9 (best ratio). |
| `web server threads` | ` ` | How many processor threads the web server is allowed. The default is system-specific, the minimum of `6` or the number of CPU cores. |
| `web server max sockets` | ` ` | Available sockets. The default is system-specific, automatically adjusted to 50% of the max number of open files Netdata is allowed to use (via `/etc/security/limits.conf` or systemd), to allow enough file descriptors to be available for data collection. |
-| `custom dashboard_info.js` | ` ` | Specifies the location of a custom `dashboard.js` file. See [customizing the standard dashboard](/docs/dashboard/customize.mdx#customize-the-standard-dashboard) for details. |
+| `custom dashboard_info.js` | ` ` | Specifies the location of a custom `dashboard.js` file. See [customizing the standard dashboard](https://github.com/netdata/netdata/blob/master/docs/dashboard/customize.mdx#customize-the-standard-dashboard) for details. |
## Examples
@@ -102,7 +102,7 @@ Using the above, Netdata will bind to:
- IPv4 127.0.0.1 at port 19999 (port was used from `default port`). Only the UI (dashboard) and the read API will be accessible on this port. Both HTTP and HTTPS requests will be accepted.
- IPv4 10.1.1.1 at port 19998. The management API and `netdata.conf` will be accessible on this port.
- All the IPs `hostname` resolves to (both IPv4 and IPv6 depending on the resolved IPs) at port 19997. Only badges will be accessible on this port.
-- All IPv6 IPs at port 19996. Only metric streaming requests from other Netdata agents will be accepted on this port. Only encrypted streams will be allowed (i.e. child nodes also need to be [configured for TLS](/streaming/README.md).
+- All IPv6 IPs at port 19996. Only metric streaming requests from other Netdata agents will be accepted on this port. Only encrypted streams will be allowed (i.e. child nodes also need to be [configured for TLS](https://github.com/netdata/netdata/blob/master/streaming/README.md).
- All the IPs `localhost` resolves to (both IPv4 and IPv6 depending the resolved IPs) at port 19996. This port will only accept registry API requests.
- All IPv4 and IPv6 IPs at port `http` as set in `/etc/services`. Only the UI (dashboard) and the read API will be accessible on this port.
- Unix domain socket `/run/netdata/netdata.sock`. All requests are serviceable on this socket. Note that in some OSs like Fedora, every service sees a different `/tmp`, so don't create a Unix socket under `/tmp`. `/run` or `/var/run` is suggested.
@@ -189,7 +189,7 @@ Example:
bind to = *=dashboard|registry|badges|management|streaming|netdata.conf^SSL=force
```
-For information how to configure the child to use TLS, check [securing the communication](/streaming/README.md#securing-streaming-communications) in the streaming documentation. There you will find additional details on the expected behavior for client and server nodes, when their respective TLS options are enabled.
+For information how to configure the child to use TLS, check [securing the communication](https://github.com/netdata/netdata/blob/master/streaming/README.md#securing-streaming-communications) in the streaming documentation. There you will find additional details on the expected behavior for client and server nodes, when their respective TLS options are enabled.
When we define the use of SSL in a Netdata agent for different ports, Netdata will apply the behavior specified on each port. For example, using the configuration line below:
@@ -245,7 +245,7 @@ Netdata supports access lists in `netdata.conf`:
- `allow netdata.conf from` checks the IP to allow `http://netdata.host:19999/netdata.conf`.
The IPs listed are all the private IPv4 addresses, including link local IPv6 addresses. Keep in mind that connections to Netdata API ports are filtered by `allow connections from`. So, IPs allowed by `allow netdata.conf from` should also be allowed by `allow connections from`.
-- `allow management from` checks the IPs to allow API management calls. Management via the API is currently supported for [health](/web/api/health/README.md#health-management-api)
+- `allow management from` checks the IPs to allow API management calls. Management via the API is currently supported for [health](https://github.com/netdata/netdata/blob/master/web/api/health/README.md#health-management-api)
In order to check the FQDN of the connection without opening the Netdata agent to DNS-spoofing, a reverse-dns record
must be setup for the connecting host. At connection time the reverse-dns of the peer IP address is resolved, and
diff --git a/docs/dashboard/visualization-date-and-time-controls.mdx b/docs/dashboard/visualization-date-and-time-controls.mdx
index fe3e5109..a59a1f06 100644
--- a/docs/dashboard/visualization-date-and-time-controls.mdx
+++ b/docs/dashboard/visualization-date-and-time-controls.mdx
@@ -1,9 +1,13 @@
----
+<!--
title: "Visualization date and time controls"
description: "Netdata's dashboard features powerful date visualization controls that include a time control (play, pause, force play), a timezone selector and a rich date and timeframe selector, with useful defaults and rich customization, to help you narrow your focus when troubleshooting issues or anomalies."
-type: how-to
-custom_edit_url: https://github.com/netdata/netdata/edit/master/docs/dashboard/visualization-date-and-time-controls.mdx
----
+type: "how-to"
+custom_edit_url: "https://github.com/netdata/netdata/edit/master/docs/dashboard/visualization-date-and-time-controls.mdx"
+sidebar_label: "Visualization date and time controls"
+learn_status: "Published"
+learn_topic_type: "Concepts"
+learn_rel_path: "Concepts"
+-->
# Visualization date and time controls
@@ -11,7 +15,7 @@ custom_edit_url: https://github.com/netdata/netdata/edit/master/docs/dashboard/v
### Pick timeframes to visualize
-While [panning through time and zooming in/out](/docs/dashboard/interact-charts.mdx) from charts it is helpful when
+While [panning through time and zooming in/out](https://github.com/netdata/netdata/blob/master/docs/dashboard/interact-charts.mdx) from charts it is helpful when
you're looking a recent history, or want to do granular troubleshooting, what if you want to see metrics from 6 hours
ago? Or 6 days?
@@ -80,7 +84,7 @@ distributed in different timezones and they need to collaborate.
Our goal is to make it easier for you and your teams to troubleshoot based on your timezone preference and communicate easily
with varying timezones and timeframes without the need to be concerned about their specificity.
-![Timezon selector](https://user-images.githubusercontent.com/82235632/129209528-bc1d572d-4582-4142-aace-918287849499.png)
+<img width="437" alt="Untitled1" src="https://user-images.githubusercontent.com/43294513/216628390-c3bd1cd2-349d-4523-b8d3-c7e68395f670.png">
When you change the timezone all the date and time fields will be updated to be displayed according to the specified timezone, this goes from
charts to alerts information and across the Netdata Cloud.
@@ -99,23 +103,23 @@ beyond stored historical metrics, you'll see this message:
![Screenshot of reaching the end of historical metrics
storage](https://user-images.githubusercontent.com/1153921/114207597-63a23280-9911-11eb-863d-4d2f75b030b4.png)
-At any time, [configure the internal TSDB's storage capacity](/docs/store/change-metrics-storage.md) to expand your
+At any time, [configure the internal TSDB's storage capacity](https://github.com/netdata/netdata/blob/master/docs/store/change-metrics-storage.md) to expand your
depth of historical metrics.
## What's next?
One useful next step after selecting a timeframe is [exporting the
-metrics](/docs/dashboard/import-export-print-snapshot.mdx) into a snapshot file, which can then be shared and imported
+metrics](https://github.com/netdata/netdata/blob/master/docs/dashboard/import-export-print-snapshot.mdx) into a snapshot file, which can then be shared and imported
into any other Netdata dashboard.
-There are also many ways to [customize](/docs/dashboard/customize.mdx) the standard dashboard experience, from changing
+There are also many ways to [customize](https://github.com/netdata/netdata/blob/master/docs/dashboard/customize.mdx) the standard dashboard experience, from changing
the theme to editing the text that accompanies every section of charts.
## Further reading & related information
- Dashboard
- - [How the dashboard works](/docs/dashboard/how-dashboard-works.mdx)
- - [Interact with charts](/docs/dashboard/interact-charts.mdx)
- - [Chart dimensions, contexts, and families](/docs/dashboard/dimensions-contexts-families.mdx)
- - [Import, export, and print a snapshot](/docs/dashboard/import-export-print-snapshot.mdx)
- - [Customize the standard dashboard](/docs/dashboard/customize.mdx)
+ - [How the dashboard works](https://github.com/netdata/netdata/blob/master/docs/dashboard/how-dashboard-works.mdx)
+ - [Interact with charts](https://github.com/netdata/netdata/blob/master/docs/dashboard/interact-charts.mdx)
+ - [Chart dimensions, contexts, and families](https://github.com/netdata/netdata/blob/master/docs/dashboard/dimensions-contexts-families.mdx)
+ - [Import, export, and print a snapshot](https://github.com/netdata/netdata/blob/master/docs/dashboard/import-export-print-snapshot.mdx)
+ - [Customize the standard dashboard](https://github.com/netdata/netdata/blob/master/docs/dashboard/customize.mdx)
diff --git a/docs/export/enable-connector.md b/docs/export/enable-connector.md
index a914a114..28208e2f 100644
--- a/docs/export/enable-connector.md
+++ b/docs/export/enable-connector.md
@@ -1,25 +1,31 @@
<!--
title: "Enable an exporting connector"
description: "Learn how to enable and configure any connector using examples to start exporting metrics to external time-series databases in minutes."
-custom_edit_url: https://github.com/netdata/netdata/edit/master/docs/export/enable-connector.md
+custom_edit_url: "https://github.com/netdata/netdata/edit/master/docs/export/enable-connector.md"
+sidebar_label: "Enable an exporting connector"
+learn_status: "Published"
+learn_topic_type: "Tasks"
+learn_rel_path: "Setup"
-->
# Enable an exporting connector
Now that you found the right connector for your [external time-series
-database](/docs/export/external-databases.md#supported-databases), you can now enable the exporting engine and the
+database](https://github.com/netdata/netdata/blob/master/docs/export/external-databases.md#supported-databases), you can now enable the exporting engine and the
connector itself. We'll walk through the process of enabling the exporting engine itself, followed by two examples using
the OpenTSDB and Graphite connectors.
> When you enable the exporting engine and a connector, the Netdata Agent exports metrics _beginning from the time you
-> restart its process_, not the entire [database of long-term metrics](/docs/store/change-metrics-storage.md).
+> restart its process_, not the entire
+> [database of long-term metrics](https://github.com/netdata/netdata/blob/master/docs/store/change-metrics-storage.md).
Once you understand the process of enabling a connector, you can translate that knowledge to any other connector.
## Enable the exporting engine
-Use `edit-config` from your [Netdata config directory](/docs/configure/nodes.md#the-netdata-config-directory) to open
-`exporting.conf`:
+Use `edit-config` from your
+[Netdata config directory](https://github.com/netdata/netdata/blob/master/docs/configure/nodes.md#the-netdata-config-directory)
+to open `exporting.conf`:
```bash
sudo ./edit-config exporting.conf
@@ -47,14 +53,16 @@ Use the following configuration as a starting point. Copy and paste it into `exp
Replace `my_opentsdb_http_instance` with an instance name of your choice, and change the `destination` setting to the IP
address or hostname of your OpenTSDB database.
-Restart your Agent with `sudo systemctl restart netdata`, or the [appropriate
-method](/docs/configure/start-stop-restart.md) for your system, to begin exporting to your OpenTSDB database. The
+Restart your Agent with `sudo systemctl restart netdata`, or
+the [appropriate method](https://github.com/netdata/netdata/blob/master/docs/configure/start-stop-restart.md) for your system, to begin exporting to your OpenTSDB
+database. The
Netdata Agent exports metrics _beginning from the time the process starts_, and because it exports as metrics are
collected, you should start seeing data in your external database after only a few seconds.
Any further configuration is optional, based on your needs and the configuration of your OpenTSDB database. See the
-[OpenTSDB connector doc](/exporting/opentsdb/README.md) and [exporting engine
-reference](/exporting/README.md#configuration) for details.
+[OpenTSDB connector doc](https://github.com/netdata/netdata/blob/master/exporting/opentsdb/README.md)
+and [exporting engine reference](https://github.com/netdata/netdata/blob/master/exporting/README.md#configuration) for
+details.
## Example: Enable the Graphite connector
@@ -69,27 +77,29 @@ Use the following configuration as a starting point. Copy and paste it into `exp
Replace `my_graphite_instance` with an instance name of your choice, and change the `destination` setting to the IP
address or hostname of your Graphite-supported database.
-Restart your Agent with `sudo systemctl restart netdata`, or the [appropriate
-method](/docs/configure/start-stop-restart.md) for your system, to begin exporting to your Graphite-supported database.
+Restart your Agent with `sudo systemctl restart netdata`, or
+the [appropriate method](https://github.com/netdata/netdata/blob/master/docs/configure/start-stop-restart.md) for your system, to begin exporting to your
+Graphite-supported database.
Because the Agent exports metrics as they're collected, you should start seeing data in your external database after
only a few seconds.
Any further configuration is optional, based on your needs and the configuration of your Graphite-supported database.
-See [exporting engine reference](/exporting/README.md#configuration) for details.
+See [exporting engine reference](https://github.com/netdata/netdata/blob/master/exporting/README.md#configuration) for
+details.
## What's next?
-If you want to further configure your exporting connectors, see the [exporting engine
-reference](/exporting/README.md#configuration).
+If you want to further configure your exporting connectors, see
+the [exporting engine reference](https://github.com/netdata/netdata/blob/master/exporting/README.md#configuration).
-For a comprehensive example of using the Graphite connector, read our guide: [_Export and visualize Netdata metrics in
-Graphite_](/docs/guides/export/export-netdata-metrics-graphite.md). Or, start [using host
-labels](/docs/guides/using-host-labels.md) on exported metrics.
+For a comprehensive example of using the Graphite connector, read our guide:
+[_Export and visualize Netdata metrics in Graphite_](https://github.com/netdata/netdata/blob/master/docs/guides/export/export-netdata-metrics-graphite.md). Or, start
+[using host labels](https://github.com/netdata/netdata/blob/master/docs/guides/using-host-labels.md) on exported metrics.
### Related reference documentation
-- [Exporting engine reference](/exporting/README.md)
-- [OpenTSDB connector](/exporting/opentsdb/README.md)
-- [Graphite connector](/exporting/graphite/README.md)
+- [Exporting engine reference](https://github.com/netdata/netdata/blob/master/exporting/README.md)
+- [OpenTSDB connector](https://github.com/netdata/netdata/blob/master/exporting/opentsdb/README.md)
+- [Graphite connector](https://github.com/netdata/netdata/blob/master/exporting/graphite/README.md)
diff --git a/docs/export/external-databases.md b/docs/export/external-databases.md
index a542e8ee..00ca7410 100644
--- a/docs/export/external-databases.md
+++ b/docs/export/external-databases.md
@@ -1,13 +1,17 @@
<!--
title: "Export metrics to external time-series databases"
description: "Use the exporting engine to send Netdata metrics to popular external time series databases for long-term storage or further analysis."
-custom_edit_url: https://github.com/netdata/netdata/edit/master/docs/export/external-databases.md
+custom_edit_url: "https://github.com/netdata/netdata/edit/master/docs/export/external-databases.md"
+sidebar_label: "Export metrics to external time-series databases"
+learn_status: "Published"
+learn_topic_type: "Concepts"
+learn_rel_path: "Concepts"
-->
# Export metrics to external time-series databases
Netdata allows you to export metrics to external time-series databases with the [exporting
-engine](/exporting/README.md). This system uses a number of **connectors** to initiate connections to [more than
+engine](https://github.com/netdata/netdata/blob/master/exporting/README.md). This system uses a number of **connectors** to initiate connections to [more than
thirty](#supported-databases) supported databases, including InfluxDB, Prometheus, Graphite, ElasticSearch, and much
more.
@@ -18,55 +22,55 @@ Based on your needs and resources you allocated to your external time-series dat
that metrics are exported or export only certain charts with filtering. You can also choose whether metrics are exported
as-collected, a normalized average, or the sum/volume of metrics values over the configured interval.
-Exporting is an important part of Netdata's effort to be [interoperable](/docs/overview/netdata-monitoring-stack.md)
+Exporting is an important part of Netdata's effort to be [interoperable](https://github.com/netdata/netdata/blob/master/docs/overview/netdata-monitoring-stack.md)
with other monitoring software. You can use an external time-series database for long-term metrics retention, further
analysis, or correlation with other tools, such as application tracing.
## Supported databases
Netdata supports exporting metrics to the following databases through several
-[connectors](/exporting/README.md#features). Once you find the connector that works for your database, open its
-documentation and the [enabling a connector](/docs/export/enable-connector.md) doc for details on enabling it.
-
-- **AppOptics**: [Prometheus remote write](/exporting/prometheus/remote_write/README.md)
-- **AWS Kinesis**: [AWS Kinesis Data Streams](/exporting/aws_kinesis/README.md)
-- **Azure Data Explorer**: [Prometheus remote write](/exporting/prometheus/remote_write/README.md)
-- **Azure Event Hubs**: [Prometheus remote write](/exporting/prometheus/remote_write/README.md)
-- **Blueflood**: [Graphite](/exporting/graphite/README.md)
-- **Chronix**: [Prometheus remote write](/exporting/prometheus/remote_write/README.md)
-- **Cortex**: [Prometheus remote write](/exporting/prometheus/remote_write/README.md)
-- **CrateDB**: [Prometheus remote write](/exporting/prometheus/remote_write/README.md)
-- **ElasticSearch**: [Graphite](/exporting/graphite/README.md), [Prometheus remote
- write](/exporting/prometheus/remote_write/README.md)
-- **Gnocchi**: [Prometheus remote write](/exporting/prometheus/remote_write/README.md)
-- **Google BigQuery**: [Prometheus remote write](/exporting/prometheus/remote_write/README.md)
-- **Google Cloud Pub/Sub**: [Google Cloud Pub/Sub Service](/exporting/pubsub/README.md)
-- **Graphite**: [Graphite](/exporting/graphite/README.md), [Prometheus remote
- write](/exporting/prometheus/remote_write/README.md)
-- **InfluxDB**: [Graphite](/exporting/graphite/README.md), [Prometheus remote
- write](/exporting/prometheus/remote_write/README.md)
-- **IRONdb**: [Prometheus remote write](/exporting/prometheus/remote_write/README.md)
-- **JSON**: [JSON document databases](/exporting/json/README.md)
-- **Kafka**: [Prometheus remote write](/exporting/prometheus/remote_write/README.md)
-- **KairosDB**: [Graphite](/exporting/graphite/README.md), [OpenTSDB](/exporting/opentsdb/README.md)
-- **M3DB**: [Prometheus remote write](/exporting/prometheus/remote_write/README.md)
-- **MetricFire**: [Prometheus remote write](/exporting/prometheus/remote_write/README.md)
-- **MongoDB**: [MongoDB](/exporting/mongodb/README.md)
-- **New Relic**: [Prometheus remote write](/exporting/prometheus/remote_write/README.md)
-- **OpenTSDB**: [OpenTSDB](/exporting/opentsdb/README.md), [Prometheus remote
- write](/exporting/prometheus/remote_write/README.md)
-- **PostgreSQL**: [Prometheus remote write](/exporting/prometheus/remote_write/README.md)
+[connectors](https://github.com/netdata/netdata/blob/master/exporting/README.md#features). Once you find the connector that works for your database, open its
+documentation and the [enabling a connector](https://github.com/netdata/netdata/blob/master/docs/export/enable-connector.md) doc for details on enabling it.
+
+- **AppOptics**: [Prometheus remote write](https://github.com/netdata/netdata/blob/master/exporting/prometheus/remote_write/README.md)
+- **AWS Kinesis**: [AWS Kinesis Data Streams](https://github.com/netdata/netdata/blob/master/exporting/aws_kinesis/README.md)
+- **Azure Data Explorer**: [Prometheus remote write](https://github.com/netdata/netdata/blob/master/exporting/prometheus/remote_write/README.md)
+- **Azure Event Hubs**: [Prometheus remote write](https://github.com/netdata/netdata/blob/master/exporting/prometheus/remote_write/README.md)
+- **Blueflood**: [Graphite](https://github.com/netdata/netdata/blob/master/exporting/graphite/README.md)
+- **Chronix**: [Prometheus remote write](https://github.com/netdata/netdata/blob/master/exporting/prometheus/remote_write/README.md)
+- **Cortex**: [Prometheus remote write](https://github.com/netdata/netdata/blob/master/exporting/prometheus/remote_write/README.md)
+- **CrateDB**: [Prometheus remote write](https://github.com/netdata/netdata/blob/master/exporting/prometheus/remote_write/README.md)
+- **ElasticSearch**: [Graphite](https://github.com/netdata/netdata/blob/master/exporting/graphite/README.md), [Prometheus remote
+ write](https://github.com/netdata/netdata/blob/master/exporting/prometheus/remote_write/README.md)
+- **Gnocchi**: [Prometheus remote write](https://github.com/netdata/netdata/blob/master/exporting/prometheus/remote_write/README.md)
+- **Google BigQuery**: [Prometheus remote write](https://github.com/netdata/netdata/blob/master/exporting/prometheus/remote_write/README.md)
+- **Google Cloud Pub/Sub**: [Google Cloud Pub/Sub Service](https://github.com/netdata/netdata/blob/master/exporting/pubsub/README.md)
+- **Graphite**: [Graphite](https://github.com/netdata/netdata/blob/master/exporting/graphite/README.md), [Prometheus remote
+ write](https://github.com/netdata/netdata/blob/master/exporting/prometheus/remote_write/README.md)
+- **InfluxDB**: [Graphite](https://github.com/netdata/netdata/blob/master/exporting/graphite/README.md), [Prometheus remote
+ write](https://github.com/netdata/netdata/blob/master/exporting/prometheus/remote_write/README.md)
+- **IRONdb**: [Prometheus remote write](https://github.com/netdata/netdata/blob/master/exporting/prometheus/remote_write/README.md)
+- **JSON**: [JSON document databases](https://github.com/netdata/netdata/blob/master/exporting/json/README.md)
+- **Kafka**: [Prometheus remote write](https://github.com/netdata/netdata/blob/master/exporting/prometheus/remote_write/README.md)
+- **KairosDB**: [Graphite](https://github.com/netdata/netdata/blob/master/exporting/graphite/README.md), [OpenTSDB](https://github.com/netdata/netdata/blob/master/exporting/opentsdb/README.md)
+- **M3DB**: [Prometheus remote write](https://github.com/netdata/netdata/blob/master/exporting/prometheus/remote_write/README.md)
+- **MetricFire**: [Prometheus remote write](https://github.com/netdata/netdata/blob/master/exporting/prometheus/remote_write/README.md)
+- **MongoDB**: [MongoDB](https://github.com/netdata/netdata/blob/master/exporting/mongodb/README.md)
+- **New Relic**: [Prometheus remote write](https://github.com/netdata/netdata/blob/master/exporting/prometheus/remote_write/README.md)
+- **OpenTSDB**: [OpenTSDB](https://github.com/netdata/netdata/blob/master/exporting/opentsdb/README.md), [Prometheus remote
+ write](https://github.com/netdata/netdata/blob/master/exporting/prometheus/remote_write/README.md)
+- **PostgreSQL**: [Prometheus remote write](https://github.com/netdata/netdata/blob/master/exporting/prometheus/remote_write/README.md)
via [PostgreSQL Prometheus Adapter](https://github.com/CrunchyData/postgresql-prometheus-adapter)
-- **Prometheus**: [Prometheus scraper](/exporting/prometheus/README.md)
-- **TimescaleDB**: [Prometheus remote write](/exporting/prometheus/remote_write/README.md),
- [netdata-timescale-relay](/exporting/TIMESCALE.md)
-- **QuasarDB**: [Prometheus remote write](/exporting/prometheus/remote_write/README.md)
-- **SignalFx**: [Prometheus remote write](/exporting/prometheus/remote_write/README.md)
-- **Splunk**: [Prometheus remote write](/exporting/prometheus/remote_write/README.md)
-- **TiKV**: [Prometheus remote write](/exporting/prometheus/remote_write/README.md)
-- **Thanos**: [Prometheus remote write](/exporting/prometheus/remote_write/README.md)
-- **VictoriaMetrics**: [Prometheus remote write](/exporting/prometheus/remote_write/README.md)
-- **Wavefront**: [Prometheus remote write](/exporting/prometheus/remote_write/README.md)
+- **Prometheus**: [Prometheus scraper](https://github.com/netdata/netdata/blob/master/exporting/prometheus/README.md)
+- **TimescaleDB**: [Prometheus remote write](https://github.com/netdata/netdata/blob/master/exporting/prometheus/remote_write/README.md),
+ [netdata-timescale-relay](https://github.com/netdata/netdata/blob/master/exporting/TIMESCALE.md)
+- **QuasarDB**: [Prometheus remote write](https://github.com/netdata/netdata/blob/master/exporting/prometheus/remote_write/README.md)
+- **SignalFx**: [Prometheus remote write](https://github.com/netdata/netdata/blob/master/exporting/prometheus/remote_write/README.md)
+- **Splunk**: [Prometheus remote write](https://github.com/netdata/netdata/blob/master/exporting/prometheus/remote_write/README.md)
+- **TiKV**: [Prometheus remote write](https://github.com/netdata/netdata/blob/master/exporting/prometheus/remote_write/README.md)
+- **Thanos**: [Prometheus remote write](https://github.com/netdata/netdata/blob/master/exporting/prometheus/remote_write/README.md)
+- **VictoriaMetrics**: [Prometheus remote write](https://github.com/netdata/netdata/blob/master/exporting/prometheus/remote_write/README.md)
+- **Wavefront**: [Prometheus remote write](https://github.com/netdata/netdata/blob/master/exporting/prometheus/remote_write/README.md)
Can't find your preferred external time-series database? Ask our [community](https://community.netdata.cloud/) for
solutions, or file an [issue on
@@ -74,16 +78,16 @@ GitHub](https://github.com/netdata/netdata/issues/new?assignees=&labels=bug%2Cne
## What's next?
-We recommend you read our document on [enabling a connector](/docs/export/enable-connector.md) to learn about the
+We recommend you read our document on [enabling a connector](https://github.com/netdata/netdata/blob/master/docs/export/enable-connector.md) to learn about the
process and discover important configuration options. If you would rather skip ahead, click on any of the above links to
connectors for their reference documentation, which outline any prerequisites to install for that connector, along with
connector-specific configuration options.
Read about one possible use case for exporting metrics in our guide: [_Export and visualize Netdata metrics in
-Graphite_](/docs/guides/export/export-netdata-metrics-graphite.md).
+Graphite_](https://github.com/netdata/netdata/blob/master/docs/guides/export/export-netdata-metrics-graphite.md).
### Related reference documentation
-- [Exporting engine reference](/exporting/README.md)
+- [Exporting engine reference](https://github.com/netdata/netdata/blob/master/exporting/README.md)
diff --git a/docs/get-started.mdx b/docs/get-started.mdx
index 892baa0c..aa82e811 100644
--- a/docs/get-started.mdx
+++ b/docs/get-started.mdx
@@ -1,67 +1,96 @@
----
-title: "Get started with Netdata"
+<!--
+title: "Install Netdata"
description: "Download and install the open-source Netdata monitoring agent on physical/virtual servers, Linux (Ubuntu/Debian/CentOS/etc), Docker, Kubernetes, and many others, often with one command."
-type: how-to
-sidebar_label: "Get started"
-custom_edit_url: https://github.com/netdata/netdata/edit/master/docs/get-started.mdx
----
-
-import { OneLineInstallWget } from '@site/src/components/OneLineInstall/'
-import { Install, InstallBox } from '@site/src/components/Install/'
-
-# Get started with Netdata
+sidebar_label: "Install Netdata"
+custom_edit_url: "https://github.com/netdata/netdata/edit/master/docs/get-started.mdx"
+learn_status: "Published"
+learn_topic_type: "Tasks"
+learn_rel_path: "Getting started"
+-->
+
+import { OneLineInstallWget, OneLineInstallCurl } from '@site/src/components/OneLineInstall/'
+import { InstallRegexLink, InstallBoxRegexLink } from '@site/src/components/InstallRegexLink/'
+import Tabs from '@theme/Tabs';
+import TabItem from '@theme/TabItem';
Netdata is a free and open-source (FOSS) monitoring agent that collects thousands of hardware and software metrics from
any physical or virtual system (we call them _nodes_). These metrics are organized in an easy-to-use and -navigate interface.
-Together with [Netdata Cloud](https://learn.netdata.cloud/docs/cloud), you can monitor your entire infrastructure in
+Together with [Netdata Cloud](https://github.com/netdata/netdata/blob/master/docs/cloud/cloud.mdx), you can monitor your entire infrastructure in
real time and troubleshoot problems that threaten the health of your nodes.
Netdata runs permanently on all your physical/virtual servers, containers, cloud deployments, and edge/IoT devices. It
runs on Linux distributions (Ubuntu, Debian, CentOS, and more), container/microservice platforms (Kubernetes clusters,
Docker), and many other operating systems (FreeBSD, macOS), with no `sudo` required.
+To install Netdata in minutes on your platform:
+
+1. Sign up to https://app.netdata.cloud/
+2. You will be presented with an empty space, and a prompt to "Connect Nodes" with the install command for each platform
+3. Select the platform you want to install Netdata to, copy and paste the script into your node's terminal, and run it
+
+Upon installation completing successfully, you should be able to see the node live in your Netdata Space!
+
+Continue reading for more advanced instructions and installation options.
+
## Install on Linux with one-line installer
The **recommended** way to install Netdata on a Linux node (physical, virtual, container, IoT) is our one-line
-[kickstart script](/packaging/installer/methods/kickstart.md). This script automatically installs dependencies and
-builds Netdata from its source code.
+[kickstart script](https://github.com/netdata/netdata/blob/master/packaging/installer/methods/kickstart.md).
+This script automatically installs dependencies and builds Netdata from its source code.
-Copy the script, paste it into your node's terminal, and hit `Enter` to begin the installation process.
+To install, copy the script, paste it into your node's terminal, and hit `Enter` to begin the installation process.
-<OneLineInstallWget />
+ <Tabs>
+ <TabItem value="wget" label=<code>wget</code>>
+
+ <OneLineInstallWget/>
+
+ </TabItem>
+ <TabItem value="curl" label=<code>curl</code>>
+
+ <OneLineInstallCurl/>
+
+ </TabItem>
+</Tabs>
+
+:::note
+If you plan to also Claim the node to Netdata Cloud,
+make sure to replace `YOUR_CLAIM_TOKEN` with the claim token of your space,
+and `YOUR_ROOM_ID` with the ID of the room you are willing to claim to.
+:::
Jump down to [what's next](#whats-next) to learn how to view your new dashboard and take your next steps monitoring and
troubleshooting with Netdata.
## Other installation options
-<Install>
- <InstallBox
- to="/docs/agent/packaging/docker"
+<InstallRegexLink>
+ <InstallBoxRegexLink
+ to="[](https://github.com/netdata/netdata/blob/master/packaging/docker/README.md)"
os="Run with Docker"
svg="docker" />
- <InstallBox
- to="/docs/agent/packaging/installer/methods/kubernetes"
+ <InstallBoxRegexLink
+ to="[](https://github.com/netdata/netdata/blob/master/packaging/installer/methods/kubernetes.md)"
os="Deploy on Kubernetes"
svg="kubernetes" />
- <InstallBox
- to="/docs/agent/packaging/installer/methods/macos"
+ <InstallBoxRegexLink
+ to="[](https://github.com/netdata/netdata/blob/master/packaging/installer/methods/macos.md)"
os="Install on macOS"
svg="macos" />
- <InstallBox
- to="/docs/agent/packaging/installer/methods/manual"
+ <InstallBoxRegexLink
+ to="[](https://github.com/netdata/netdata/blob/master/packaging/installer/methods/manual.md)"
os="Linux from Git"
svg="linux" />
- <InstallBox
- to="/docs/agent/packaging/installer/methods/source"
+ <InstallBoxRegexLink
+ to="[](https://github.com/netdata/netdata/blob/master/packaging/installer/methods/source.md)"
os="Linux from source"
svg="linux" />
- <InstallBox
- to="/docs/agent/packaging/installer/methods/offline"
+ <InstallBoxRegexLink
+ to="[](https://github.com/netdata/netdata/blob/master/packaging/installer/methods/offline.md)"
os="Linux for offline nodes"
svg="linux" />
-</Install>
+</InstallRegexLink>
## What's next?
@@ -73,35 +102,28 @@ Where you go from here is based on your use case, immediate needs, and experienc
### Dashboard
-Learn more about [how the dashboard works](/docs/dashboard/how-dashboard-works.mdx), or dive directly into the many ways
-to [interact with charts](/docs/dashboard/interact-charts.mdx).
+Learn more about [how the dashboard works](https://github.com/netdata/netdata/blob/master/docs/dashboard/how-dashboard-works.mdx), or dive directly into the many ways
+to [interact with charts](https://github.com/netdata/netdata/blob/master/docs/dashboard/interact-charts.mdx).
### Configuration
-Discover the recommended way to [configure Netdata's settings or behavior](/docs/configure/nodes.md) using our built-in
+Discover the recommended way to [configure Netdata's settings or behavior](https://github.com/netdata/netdata/blob/master/docs/configure/nodes.md) using our built-in
`edit-config` script, then apply that knowledge to mission-critical tweaks, such as [changing how long Netdata stores
-metrics](/docs/store/change-metrics-storage.md).
+metrics](https://github.com/netdata/netdata/blob/master/docs/store/change-metrics-storage.md).
### Data collection
If Netdata didn't autodetect all the hardware, containers, services, or applications running on your node, you should
-learn more about [how data collectors work](/docs/collect/how-collectors-work.md). If there's a [supported
-collector](/collectors/COLLECTORS.md) for metrics you need, [configure the collector](/docs/collect/enable-configure.md)
+learn more about [how data collectors work](https://github.com/netdata/netdata/blob/master/docs/collect/how-collectors-work.md). If there's a [supported
+collector](https://github.com/netdata/netdata/blob/master/collectors/COLLECTORS.md) for metrics you need, [configure the collector](https://github.com/netdata/netdata/blob/master/docs/collect/enable-configure.md)
or read about its requirements to configure your endpoint to publish metrics in the correct format and endpoint.
### Alarms & notifications
Netdata comes with hundreds of preconfigured alarms, designed by our monitoring gurus in parallel with our open-source
-community, but you may want to [edit alarms](/docs/monitor/configure-alarms.md) or [enable
-notifications](/docs/monitor/enable-notifications.md) to customize your Netdata experience.
-
-### Need to monitor multiple nodes in one place?
+community, but you may want to [edit alarms](https://github.com/netdata/netdata/blob/master/docs/monitor/configure-alarms.md) or
+[enable notifications](https://github.com/netdata/netdata/blob/master/docs/monitor/enable-notifications.md) to customize your Netdata experience.
-For robust multi-node monitoring from a single interface, consider [Netdata
-Cloud](https://learn.netdata.cloud/docs/cloud), which streams, aggregates, and visualizes metrics from any number of
-nodes. It's all the same out-of-the-box, zero-configuration functionality of the open-source monitoring agent, but for
-any number of distributed nodes, _entirely for free_.
+### Make your deployment production ready
-There is an alternative for those who aren't interested in using Netdata Cloud, albeit with some required configuration.
-Each node can [stream](/streaming/README.md) its metrics to any other node, and the default
-[registry](/registry/README.md) is configurable to create a private "network" of Netdata dashboards.
+Both [securing Netdata](https://github.com/netdata/netdata/blob/master/docs/configure/secure-nodes.md) and [setting up replication](https://github.com/netdata/netdata/blob/master/streaming/README.md) are strongly recommended.
diff --git a/docs/getting-started/integrations.md b/docs/getting-started/integrations.md
new file mode 100644
index 00000000..9f38a67d
--- /dev/null
+++ b/docs/getting-started/integrations.md
@@ -0,0 +1,12 @@
+<!--
+title: "Integrations"
+sidebar_label: "Integrations"
+custom_edit_url: null
+learn_status: "Published"
+learn_topic_type: "Getting started"
+learn_rel_path: "Getting started"
+learn_docs_purpose: "Present all the Netdata integrations"
+learn_doc_type: "AUTOGENERATED"
+-->
+
+This page is autogenerated, this is placeholder document \ No newline at end of file
diff --git a/docs/getting-started/introduction.md b/docs/getting-started/introduction.md
new file mode 100644
index 00000000..1ace5e3a
--- /dev/null
+++ b/docs/getting-started/introduction.md
@@ -0,0 +1,158 @@
+<!--
+title: "Introduction"
+sidebar_label: "Introduction"
+custom_edit_url: "https://github.com/netdata/netdata/blob/master/docs/getting-started/intro.md"
+learn_status: "Published"
+sidebar_position: "1"
+learn_topic_type: "Getting started"
+learn_rel_path: "Getting started"
+learn_docs_purpose: "Present netdata in a nutshell"
+-->
+
+## What is Netdata ?
+
+Netdata is designed by system administrators, DevOps engineers, and developers to collect everything, help you visualize
+metrics, troubleshoot complex performance problems, and make data interoperable with the rest of your monitoring stack.
+
+You can install Netdata on most Linux distributions (Ubuntu, Debian, CentOS, and more), container platforms (Kubernetes
+clusters, Docker), and many other operating systems (FreeBSD).
+
+Netdata is:
+
+### Simple to deploy
+
+- **One-line deployment** for Linux distributions, plus support for Kubernetes/Docker infrastructures.
+- **Zero configuration and maintenance** required to collect thousands of metrics, every second, from the underlying
+ OS and running applications.
+- **Prebuilt charts and alarms** alert you to common anomalies and performance issues without manual configuration.
+- **Distributed storage** to simplify the cost and complexity of storing metrics data from any number of nodes.
+
+### Powerful and scalable
+
+- **1% CPU utilization, a few MB of RAM, and minimal disk I/O** to run the monitoring Agent on bare metal, virtual
+ machines, containers, and even IoT devices.
+- **Per-second granularity** for an unlimited number of metrics based on the hardware and applications you're running
+ on your nodes.
+- **Interoperable exporters** let you connect Netdata's per-second metrics with an existing monitoring stack and other
+ time-series databases.
+
+### Optimized for troubleshooting
+
+- **Visual anomaly detection** with a UI/UX that emphasizes the relationships between charts.
+- **Customizable dashboards** to pinpoint correlated metrics, respond to incidents, and help you streamline your
+ workflows.
+- **Distributed metrics in a centralized interface** to assist users or teams trace complex issues between distributed
+ nodes.
+
+### Secure by design
+
+- **Distributed data architecture** so fast and efficient, there’s no limit to the number of metrics you can follow.
+- Because your data is **stored at the edge**, security is ensured.
+-
+### Comparison with other monitoring solutions
+
+Netdata offers many benefits over the existing monitoring landscape, whether they're expensive SaaS products or other
+open-source tools.
+
+| Netdata | Others (open-source and commercial) |
+| :-------------------------------------------------------------- | :--------------------------------------------------------------- |
+| **High resolution metrics** (1s granularity) | Low resolution metrics (10s granularity at best) |
+| Collects **thousands of metrics per node** | Collects just a few metrics |
+| Fast UI optimized for **anomaly detection** | UI is good for just an abstract view |
+| **Long-term, autonomous storage** at one-second granularity | Centralized metrics in an expensive data lake at 10s granularity |
+| **Meaningful presentation**, to help you understand the metrics | You have to know the metrics before you start |
+| Install and get results **immediately** | Long sales process and complex installation process |
+| Use it for **troubleshooting** performance problems | Only gathers _statistics of past performance_ |
+| **Kills the console** for tracing performance issues | The console is always required for troubleshooting |
+| Requires **zero dedicated resources** | Require large dedicated resources |
+
+
+Netdata works with tons of applications, notifications platforms, and other time-series databases:
+
+- **300+ system, container, and application endpoints**: Collectors autodetect metrics from default endpoints and
+ immediately visualize them into meaningful charts designed for troubleshooting. See [everything we
+ support](https://github.com/netdata/netdata/blob/master/collectors/COLLECTORS.md).
+- **20+ notification platforms**: Netdata's health watchdog sends warning and critical alarms to your [favorite
+ platform](https://github.com/netdata/netdata/blob/master/docs/monitor/enable-notifications.md) to inform you of anomalies just seconds
+ after they affect your node.
+- **30+ external time-series databases**: Export resampled metrics as they're collected to other [local- and
+ Cloud-based databases](https://github.com/netdata/netdata/blob/master/docs/export/external-databases.md) for best-in-class
+ interoperability.
+
+
+## How it works
+
+Netdata is a highly efficient, highly modular, metrics management engine. Its lockless design makes it ideal for concurrent operations on the metrics.
+
+You can see a high level representation in the following diagram.
+
+![Diagram of Netdata's core functionality](https://user-images.githubusercontent.com/2662304/199225735-01a41cc5-c074-4fe2-b780-5f08e92c6769.png)
+
+And a higher level diagram in this one.
+
+![Diagram 2 of Netdata's core
+functionality](https://user-images.githubusercontent.com/1153921/95367248-5f755980-0889-11eb-827f-9b7aa02a556e.png)
+
+You can even visit this slightly dated [interactive infographic](https://my-netdata.io/infographic.html) and get lost in a rabbit hole.
+
+But the best way to get under the hood or in the steering wheel of our highly efficient, low-latency system (supporting multiple readers and one writer on each metric) is to read the rest of our docs, or just to jump in and [get started](app.netdata.com). But here's a good breakdown:
+
+### Netdata Agent
+
+Netdata's distributed monitoring Agent collects thousands of metrics from systems, hardware, and applications with zero configuration. It runs permanently on all your physical/virtual servers, containers, cloud deployments, and edge/IoT devices.
+
+You can install Netdata on most Linux distributions (Ubuntu, Debian, CentOS, and more), container/microservice platforms (Kubernetes clusters, Docker), and many other operating systems (FreeBSD, macOS), with no sudo required.
+
+### Netdata Cloud
+Netdata Cloud is a web application that gives you real-time visibility for your entire infrastructure. With Netdata Cloud, you can view key metrics, insightful charts, and active alarms from all your nodes in a single web interface. When an anomaly strikes, seamlessly navigate to any node to troubleshoot and discover the root cause with the familiar Netdata dashboard.
+
+Netdata Cloud is free! You can add an entire infrastructure of nodes, invite all your colleagues, and visualize any number of metrics, charts, and alarms entirely for free.
+
+While Netdata Cloud offers a centralized method of monitoring your Agents, your metrics data is not stored or centralized in any way. Metrics data remains with your nodes and is only streamed to your browser, through Cloud, when you're viewing the Netdata Cloud interface.
+
+
+## Community
+
+Netdata is an inclusive open-source project and community. Please read our [Code of Conduct](https://github.com/netdata/.github/blob/main/CODE_OF_CONDUCT.md).
+
+Find most of the Netdata team in our [community forums](https://community.netdata.cloud). It's the best place to
+ask questions, find resources, and engage with passionate professionals. The team is also available and active in our [Discord](https://discord.com/invite/mPZ6WZKKG2) too.
+
+You can also find Netdata on:
+
+- [Twitter](https://twitter.com/linuxnetdata)
+- [YouTube](https://www.youtube.com/c/Netdata)
+- [Reddit](https://www.reddit.com/r/netdata/)
+- [LinkedIn](https://www.linkedin.com/company/netdata-cloud/)
+- [StackShare](https://stackshare.io/netdata)
+- [Product Hunt](https://www.producthunt.com/posts/netdata-monitoring-agent/)
+- [Repology](https://repology.org/metapackage/netdata/versions)
+- [Facebook](https://www.facebook.com/linuxnetdata/)
+
+## Contribute
+
+Contributions are the lifeblood of open-source projects. While we continue to invest in and improve Netdata, we need help to democratize monitoring!
+
+- Read our [Contributing Guide](https://github.com/netdata/.github/blob/main/CONTRIBUTING.md), which contains all the information you need to contribute to Netdata, such as improving our documentation, engaging in the community, and developing new features. We've made it as frictionless as possible, but if you need help, just ping us on our community forums!
+- We have a whole category dedicated to contributing and extending Netdata on our [community forums](https://community.netdata.cloud/c/agent-development/9)
+- Found a bug? Open a [GitHub issue](https://github.com/netdata/netdata/issues/new?assignees=&labels=bug%2Cneeds+triage&template=BUG_REPORT.yml&title=%5BBug%5D%3A+).
+- View our [Security Policy](https://github.com/netdata/netdata/security/policy).
+
+Package maintainers should read the guide on [building Netdata from source](https://github.com/netdata/netdata/blob/master/packaging/installer/methods/source.md) for
+instructions on building each Netdata component from source and preparing a package.
+
+## License
+
+The Netdata Agent is an open source project distributed under [GPLv3+](https://github.com/netdata/netdata/blob/master/LICENSE). Netdata re-distributes other open-source tools and libraries. Please check the
+[third party licenses](https://github.com/netdata/netdata/blob/master/REDISTRIBUTED.md).
+
+## Is it any good?
+
+Yes.
+
+_When people first hear about a new product, they frequently ask if it is any good. A Hacker News user
+[remarked](https://news.ycombinator.com/item?id=3067434):_
+
+> Note to self: Starting immediately, all raganwald projects will have a “Is it any good?” section in the readme, and
+> the answer shall be “yes.".
+*******************************************************************************
diff --git a/docs/guidelines.md b/docs/guidelines.md
new file mode 100644
index 00000000..6c1c3ba7
--- /dev/null
+++ b/docs/guidelines.md
@@ -0,0 +1,772 @@
+<!--
+title: "Contribute to the documentation"
+sidebar_label: "to Documentation"
+custom_edit_url: "https://github.com/netdata/netdata/blob/master/docs/guidelines.md"
+sidebar_position: "10000"
+learn_status: "Published"
+learn_topic_type: "Custom"
+learn_rel_path: "Contribute"
+learn_docs_purpose: "TBD"
+-->
+
+import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem';
+
+Welcome to our docs developer guidelines!
+
+This document will guide you to the process of contributing to our
+docs (**learn.netdata.cloud**)
+
+## Documentation architecture
+
+Netdata docs follows has two principals.
+
+1. Keep the documentation of each component _as close as you can to the codebase_
+2. Every component is analyzed via topic related docs.
+
+To this end:
+
+1. Documentation lives in every possible repo in the netdata organization. At the moment we contribute to:
+ - netdata/netdata
+ - netdata/learn (final site)
+ - netdata/go.d.plugin
+ - netdata/agent-service-discovery
+
+ In each of these repos you will find markdown files. These markdown files may or not be part of the final docs. You
+ understand what documents are part of the final docs in the following section:[_How to update documentation of
+ learn.netdata.cloud_](#how-to-update-documentation-of-learn-netdata-cloud)
+
+2. Netdata docs processes are inspired from
+ the [DITA 1.2 guidelines](http://docs.oasis-open.org/dita/v1.2/os/spec/archSpec/dita-1.2_technicalContent_overview.html)
+ for Technical content.
+
+## Topic types
+
+### Concepts
+
+A concept introduces a single feature or concept. A concept should answer the questions:
+
+- What is this?
+- Why would I use it?
+
+Concept topics:
+
+- Are abstract ideas
+- Explain meaning or benefit
+- Can stay when specifications change
+- Provide background information
+
+### Tasks
+
+Concept and reference topics exist to support tasks. _The goal for users … is not to understand a concept but to
+complete a task_. A task gives instructions for how to complete a procedure.
+
+Much of the uncertainty whether a topic is a concept or a reference disappears, when you have strong, solid task topics
+in place, furthermore topics directly address your users and their daily tasks and help them to get their job done. A
+task **must give an answer** to the **following questions**:
+
+- How do I create cool espresso drinks with my new coffee machine?
+- How do I clean the milk steamer?
+
+For the title text, use the structure active verb + noun. For example, for instance _Deploy the Agent_.
+
+### References
+
+The reference document and information types provide for the separation of fact-based information from concepts and
+tasks. \
+Factual information may include tables and lists of specifications, parameters, parts, commands, edit-files and other
+information that the users are likely to look up. The reference information type allows fact-based content to be
+maintained by those responsible for its accuracy and consistency.
+
+## Contribute to the documentation of learn.netdata.cloud
+
+### Encapsulate topics into markdown files.
+
+Netdata uses markdown files to document everything. To implement concrete sections of these [Topic types](#topic-types)
+we encapsulate this logic as follows. Every document is characterized by its topic type ('learn_topic_type' metadata
+field). To avoid breaking every single netdata concept into numerous small markdown files each document can be either a
+single `Reference` or `Concept` or `Task` or a group of `References`, `Concepts`, `Tasks`.
+
+To this end, every single topic is encapsulated into a `Heading 3 (###)` section. That means, when you have a single
+file you only make use of `Headings 4` and lower (`4, 5, 6`, for templated section or subsection). In case you want to
+includ multiple (`Concepts` let's say) in a single document, you use `Headings 3` to seperate each concept. `Headings 2`
+are used only in case you want to logically group topics inside a document.
+
+For instance:
+
+```markdown
+
+Small introduction of the document.
+
+### Concept A
+
+Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna
+aliqua.
+
+#### Field from template 1
+
+Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
+
+#### Field from template 1
+
+Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.
+
+##### Subsection 1
+
+. . .
+
+### Concept A
+
+Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
+
+#### Field from template 1
+
+. . .
+
+
+```
+
+This approach gives a clean and readable outlook in each document from a single sidebar.
+
+Here you can find the preferred templates for each topic type:
+
+
+<Tabs>
+ <TabItem value="Concept" label="Concept" default>
+
+ ```markdown
+ Small intro, give some context to the user of what you will cover on this document
+
+ ### concept title (omit if the document describes only one concept)
+
+ A concept introduces a single feature or concept. A concept should answer the questions:
+
+ 1. What is this?
+ 2. Why would I use it?
+
+ ```
+
+ </TabItem>
+ <TabItem value="Task" label="Tasks">
+
+ ```markdown
+ Small intro, give some context to the user of what you will cover on this document
+
+ ### Task title (omit if the document describes only one task)
+
+ #### Prerequisite
+
+ Unordered list of what you will need.
+
+ #### Steps
+
+ Exact list of step the user must follow
+
+ #### Expected result
+
+ What you expect to see when you complete the steps above
+
+ #### Example
+
+ Example configuration/actions of the task
+
+ #### Related reference documentation
+
+ List of reference docs user needs to be aware of.
+ ```
+
+ </TabItem>
+ <TabItem value="Reference-collectors" label="Reference-collectors">
+
+ ```markdown
+ Small intro, give some context to the user of what you will cover on this document
+
+ ### Reference name (omit if the document describes only one reference)
+
+ #### Requirements
+
+ Document any dependencies needed to run this module
+
+ #### Requirements on the monitored component
+
+ Document any steps user must take to sucessful monitor application,
+ for instance (create a user)
+
+ #### Configuration files
+
+ table with path and configuration files purpose
+ Columns: File name | Description (Purpose in a nutshell)
+
+ #### Data collection
+
+ To make changes, see `the ./edit-config task <link>`
+
+ #### Auto discovery
+
+ ##### Single node installation
+
+ . . . we autodetect localhost:port and what configurations are defaults
+
+ ##### Kubernetes installations
+
+ . . . Service discovery, click here
+
+ #### Metrics
+
+ Columns: Metric (Context) | Scope | description (of the context) | dimensions | units (of the context) | Alert triggered
+
+
+ #### Alerts
+
+ Collapsible content for every alert, just like the alert guides
+
+ #### Configuration options
+
+ Table with all the configuration options available.
+
+ Columns: name | description | default | file_name
+
+ #### Configuration example
+
+ Default configuration example
+
+ #### Troubleshoot
+
+ backlink to the task to run this module in debug mode (here you provide the debug flags)
+
+
+```
+
+ </TabItem>
+</Tabs>
+
+### Metadata fields
+
+All Docs that are supposed to be part of learn.netdata.cloud have **hidden** sections in the begining of document. These
+sections are plain lines of text and we call them metadata. Their represented as `key : "Value"` pairs. Some of them are
+needed from our statice website builder (docusaurus) others are needed for our internal pipelines to build docs
+(have prefix `learn_`).
+
+So let's go through the different necessary metadata tags to get a document properly published on Learn:
+
+| metadata_key | Value(s) | Frontmatter effect | Mandatory | Limitations |
+|:---------------------:|---------------------------------------------------------------------------------------------------------------|:-----------------------------------------------------------------------------------------------------------------------------------------------------------:|:---------:|:---------------------------------------:|
+| `title` | `String` | Title in each document | yes | |
+| `custom_edit_url` | `String` | The source GH link of the file | yes | |
+| `description` | `String or multiline String` | - | yes | |
+| `sidebar_label` | `String or multiline String` | Name in the TOC tree | yes | |
+| `sidebar_position` | `String or multiline String` | Global position in the TOC tree (local for per folder) | yes | |
+| `learn_status` | [`Published`, `Unpublished`, `Hidden`] | `Published`: Document visible in learn,<br/> `Unpublished`: Document archived in learn, <br/>`Hidden`: Documentplaced under learn_rel_path but it's hidden] | yes | |
+| `learn_topic_type` | [`Concepts`, `Tasks`, `References`, `Getting Started`] | | yes | |
+| `learn_rel_path` | `Path` (the path you want this file to appear in learn<br/> without the /docs prefix and the name of the file | | yes | |
+| `learn_autogenerated` | `Dictionary` (for internal use) | | no | Keys in the dictionary must be in `' '` |
+
+:::important
+
+1. In case any mandatory tags are missing or falsely inputted the file will remain unpublished. This is by design to
+ prevent non-properly tagged files from getting published.
+2. All metadata values must be included in `" "`. From `string` noted text inside the fields use `' ''`
+
+
+While Docusaurus can make use of more metadata tags than the above, these are the minimum we require to publish the file
+on Learn.
+
+:::
+
+### Placing a document in learn
+
+Here you can see how the metadata are parsed and create a markdown file in learn.
+
+![](https://user-images.githubusercontent.com/12612986/207310336-f7cc150b-543c-4f13-be98-5058a4d29284.png)
+
+### Before you get started
+
+Anyone interested in contributing to documentation should first read the [Netdata style guide](#styling-guide) further
+down below and the [Netdata Community Code of Conduct](https://github.com/netdata/.github/blob/main/CODE_OF_CONDUCT.md).
+
+Netdata's documentation uses Markdown syntax. If you're not familiar with Markdown, read
+the [Mastering Markdown](https://guides.github.com/features/mastering-markdown/) guide from GitHub for the basics on
+creating paragraphs, styled text, lists, tables, and more, and read further down about some special
+occasions [while writing in MDX](#mdx-and-markdown).
+
+### Making your first contribution
+
+The easiest way to contribute to Netdata's documentation is to edit a file directly on GitHub. This is perfect for small
+fixes to a single document, such as fixing a typo or clarifying a confusing sentence.
+
+Click on the **Edit this page** button on any published document on [Netdata Learn](https://learn.netdata.cloud). Each
+page has two of these buttons: One beneath the table of contents, and another at the end of the document, which take you
+to GitHub's code editor. Make your suggested changes, keeping the [Netdata style guide](#styling-guide)
+in mind, and use the ***Preview changes*** button to ensure your Markdown syntax works as expected.
+
+Under the **Commit changes** header, write descriptive title for your requested change. Click the **Commit changes**
+button to initiate your pull request (PR).
+
+Jump down to our instructions on [PRs](#making-a-pull-request) for your next steps.
+
+**Note**: If you wish to contribute documentation that is more tailored from your specific infrastructure
+monitoring/troubleshooting experience, please consider submitting a blog post about your experience. Check
+the [README](https://github.com/netdata/blog/blob/master/README.md) in our blog repo! Any blog submissions that have
+widespread or universal application will be integrated into our permanent documentation.
+
+### Edit locally
+
+Editing documentation locally is the preferred method for complex changes that span multiple documents or change the
+documentation's style or structure.
+
+Create a fork of the Netdata Agent repository by visit the [Netdata repository](https://github.com/netdata/netdata) and
+clicking on the **Fork** button.
+
+GitHub will ask you where you want to clone the repository. When finished, you end up at the index of your forked
+Netdata Agent repository. Clone your fork to your local machine:
+
+```bash
+git clone https://github.com/YOUR-GITHUB-USERNAME/netdata.git
+```
+
+Create a new branch using `git checkout -b BRANCH-NAME`. Use your favorite text editor to make your changes, keeping
+the [Netdata style guide](https://github.com/netdata/netdata/blob/master/docs/contributing/style-guide.md) in mind. Add, commit, and push changes to your fork. When you're
+finished, visit the [Netdata Agent Pull requests](https://github.com/netdata/netdata/pulls) to create a new pull request
+based on the changes you made in the new branch of your fork.
+
+### Making a pull request
+
+Pull requests (PRs) should be concise and informative. See our [PR guidelines](/contribute/handbook#pr-guidelines) for
+specifics.
+
+- The title must follow the [imperative mood](https://en.wikipedia.org/wiki/Imperative_mood) and be no more than ~50
+ characters.
+- The description should explain what was changed and why. Verify that you tested any code or processes that you are
+ trying to change.
+
+The Netdata team will review your PR and assesses it for correctness, conciseness, and overall quality. We may point to
+specific sections and ask for additional information or other fixes.
+
+After merging your PR, the Netdata team rebuilds the [documentation site](https://learn.netdata.cloud) to publish the
+changed documentation.
+
+## Styling guide
+
+The *Netdata style guide* establishes editorial guidelines for any writing produced by the Netdata team or the Netdata
+community, including documentation, articles, in-product UX copy, and more. Both internal Netdata teams and external
+contributors to any of Netdata's open-source projects should reference and adhere to this style guide as much as
+possible.
+
+Netdata's writing should **empower** and **educate**. You want to help people understand Netdata's value, encourage them
+to learn more, and ultimately use Netdata's products to democratize monitoring in their organizations. To achieve these
+goals, your writing should be:
+
+- **Clear**. Use simple words and sentences. Use strong, direct, and active language that encourages readers to action.
+- **Concise**. Provide solutions and answers as quickly as possible. Give users the information they need right now,
+ along with opportunities to learn more.
+- **Universal**. Think of yourself as a guide giving a tour of Netdata's products, features, and capabilities to a
+ diverse group of users. Write to reach the widest possible audience.
+
+You can achieve these goals by reading and adhering to the principles outlined below.
+
+If you're not familiar with Markdown, read
+the [Mastering Markdown](https://guides.github.com/features/mastering-markdown/) guide from GitHub for the basics on
+creating paragraphs, styled text, lists, tables, and more.
+
+The following sections describe situations in which a specific syntax is required.
+
+#### Syntax standards (`remark-lint`)
+
+The Netdata team uses [`remark-lint`](https://github.com/remarkjs/remark-lint) for Markdown code styling.
+
+- Use a maximum of 120 characters per line.
+- Begin headings with hashes, such as `# H1 heading`, `## H2 heading`, and so on.
+- Use `_` for italics/emphasis.
+- Use `**` for bold.
+- Use dashes `-` to begin an unordered list, and put a single space after the dash.
+- Tables should be padded so that pipes line up vertically with added whitespace.
+
+If you want to see all the settings, open the
+[`remarkrc.js`](https://github.com/netdata/netdata/blob/master/.remarkrc.js) file in the `netdata/netdata` repository.
+
+#### MDX and markdown
+
+While writing in Docusaurus, you might want to take leverage of it's features that are supported in MDX formatted files.
+One of those that we use is [Tabs](https://docusaurus.io/docs/next/markdown-features/tabs). They use an HTML syntax,
+which requires some changes in the way we write markdown inside them.
+
+In detail:
+
+Due to a bug with docusaurus, we prefer to use `<h1>heading</h1> instead of # H1` so that docusaurus doesn't render the
+contents of all Tabs on the right hand side, while not being able to navigate
+them [relative link](https://github.com/facebook/docusaurus/issues/7008).
+
+You can use markdown syntax for every other styling you want to do except Admonitions:
+For admonitions, follow [this](https://docusaurus.io/docs/markdown-features/admonitions#usage-in-jsx) guide to use
+admonitions inside JSX. While writing in JSX, all the markdown stylings have to be in HTML format to be rendered
+properly.
+
+#### Admonitions
+
+Use admonitions cautiously. Admonitions may draw user's attention, to that end we advise you to use them only for side
+content/info, without significantly interrupting the document flow.
+
+You can find the supported admonitions in the docusaurus's [documentation](https://docusaurus.io/docs/markdown-features/admonitions).
+
+#### Images
+
+Don't rely on images to convey features, ideas, or instructions. Accompany every image with descriptive alt text.
+
+In Markdown, use the standard image syntax, `![](/docs/agent/contributing)`, and place the alt text between the
+brackets `[]`. Here's an example using our logo:
+
+```markdown
+![The Netdata logo](/docs/agent/web/gui/static/img/netdata-logomark.svg)
+```
+
+Reference in-product text, code samples, and terminal output with actual text content, not screen captures or other
+images. Place the text in an appropriate element, such as a blockquote or code block, so all users can parse the
+information.
+
+#### Syntax highlighting
+
+Our documentation site at [learn.netdata.cloud](https://learn.netdata.cloud) uses
+[Prism](https://v2.docusaurus.io/docs/markdown-features#syntax-highlighting) for syntax highlighting. Netdata can use
+any of
+the [supported languages by prism-react-renderer](https://github.com/FormidableLabs/prism-react-renderer/blob/master/src/vendor/prism/includeLangs.js)
+.
+
+If no language is specified, Prism tries to guess the language based on its content.
+
+Include the language directly after the three backticks (```` ``` ````) that start the code block. For highlighting C
+code, for example:
+
+````c
+```c
+inline char *health_stock_config_dir(void) {
+ char buffer[FILENAME_MAX + 1];
+ snprintfz(buffer, FILENAME_MAX, "%s/health.d", netdata_configured_stock_config_dir);
+ return config_get(CONFIG_SECTION_DIRECTORIES, "stock health config", buffer);
+}
+```
+````
+
+And the prettified result:
+
+```c
+inline char *health_stock_config_dir(void) {
+ char buffer[FILENAME_MAX + 1];
+ snprintfz(buffer, FILENAME_MAX, "%s/health.d", netdata_configured_stock_config_dir);
+ return config_get(CONFIG_SECTION_DIRECTORIES, "stock health config", buffer);
+}
+```
+
+Prism also supports titles and line highlighting. See
+the [Docusaurus documentation](https://v2.docusaurus.io/docs/markdown-features#code-blocks) for more information.
+
+## Language, grammar, and mechanics
+
+#### Voice and tone
+
+One way we write empowering, educational content is by using a consistent voice and an appropriate tone.
+
+*Voice* is like your personality, which doesn't really change day to day.
+
+*Tone* is how you express your personality. Your expression changes based on your attitude or mood, or based on who
+you're around. In writing, your reflect tone in your word choice, punctuation, sentence structure, or even the use of
+emoji.
+
+The same idea about voice and tone applies to organizations, too. Our voice shouldn't change much between two pieces of
+content, no matter who wrote each, but the tone might be quite different based on who we think is reading.
+
+For example, a [blog post](https://www.netdata.cloud/blog/) and a [press release](https://www.netdata.cloud/news/)
+should have a similar voice, despite most often being written by different people. However, blog posts are relaxed and
+witty, while press releases are focused and academic. You won't see any emoji in a press release.
+
+##### Voice
+
+Netdata's voice is authentic, passionate, playful, and respectful.
+
+- **Authentic** writing is honest and fact-driven. Focus on Netdata's strength while accurately communicating what
+ Netdata can and cannot do, and emphasize technical accuracy over hard sells and marketing jargon.
+- **Passionate** writing is strong and direct. Be a champion for the product or feature you're writing about, and let
+ your unique personality and writing style shine.
+- **Playful** writing is friendly, thoughtful, and engaging. Don't take yourself too seriously, as long as it's not at
+ the expense of Netdata or any of its users.
+- **Respectful** writing treats people the way you want to be treated. Prioritize giving solutions and answers as
+ quickly as possible.
+
+##### Tone
+
+Netdata's tone is fun and playful, but clarity and conciseness comes first. We also tend to be informal, and aren't
+afraid of a playful joke or two.
+
+While we have general standards for voice and tone, we do want every individual's unique writing style to reflect in
+published content.
+
+#### Universal communication
+
+Netdata is a global company in every sense, with employees, contributors, and users from around the world. We strive to
+communicate in a way that is clear and easily understood by everyone.
+
+Here are some guidelines, pointers, and questions to be aware of as you write to ensure your writing is universal. Some
+of these are expanded into individual sections in
+the [language, grammar, and mechanics](#language-grammar-and-mechanics) section below.
+
+- Would this language make sense to someone who doesn't work here?
+- Could someone quickly scan this document and understand the material?
+- Create an information hierarchy with key information presented first and clearly called out to improve scannability.
+- Avoid directional language like "sidebar on the right of the page" or "header at the top of the page" since
+ presentation elements may adapt for devices.
+- Use descriptive links rather than "click here" or "learn more".
+- Include alt text for images and image links.
+- Ensure any information contained within a graphic element is also available as plain text.
+- Avoid idioms that may not be familiar to the user or that may not make sense when translated.
+- Avoid local, cultural, or historical references that may be unfamiliar to users.
+- Prioritize active, direct language.
+- Avoid referring to someone's age unless it is directly relevant; likewise, avoid referring to people with age-related
+ descriptors like "young" or "elderly."
+- Avoid disability-related idioms like "lame" or "falling on deaf ears." Don't refer to a person's disability unless
+ it’s directly relevant to what you're writing.
+- Don't call groups of people "guys." Don't call women "girls."
+- Avoid gendered terms in favor of neutral alternatives, like "server" instead of "waitress" and "businessperson"
+ instead of "businessman."
+- When writing about a person, use their communicated pronouns. When in doubt, just ask or use their name. It's OK to
+ use "they" as a singular pronoun.
+
+> Some of these guidelines were adapted from MailChimp under the Creative Commons license.
+
+To ensure Netdata's writing is clear, concise, and universal, we have established standards for language, grammar, and
+certain writing mechanics. However, if you're writing about Netdata for an external publication, such as a guest blog
+post, follow that publication's style guide or standards, while keeping
+the [preferred spelling of Netdata terms](#netdata-specific-terms) in mind.
+
+#### Active voice
+
+Active voice is more concise and easier to understand compared to passive voice. When using active voice, the subject of
+the sentence is action. In passive voice, the subject is acted upon. A famous example of passive voice is the phrase
+"mistakes were made."
+
+| | |
+| --------------- | ----------------------------------------------------------------------------------------- |
+| Not recommended | When an alarm is triggered by a metric, a notification is sent by Netdata. |
+| **Recommended** | When a metric triggers an alarm, Netdata sends a notification to your preferred endpoint. |
+
+#### Second person
+
+Use the second person ("you") to give instructions or "talk" directly to users.
+
+In these situations, avoid "we," "I," "let's," and "us," particularly in documentation. The "you" pronoun can also be
+implied, depending on your sentence structure.
+
+One valid exception is when a member of the Netdata team or community wants to write about said team or community.
+
+| | |
+| ------------------------------ | ------------------------------------------------------------ |
+| Not recommended | To install Netdata, we should try the one-line installer... |
+| **Recommended** | To install Netdata, you should try the one-line installer... |
+| **Recommended**, implied "you" | To install Netdata, try the one-line installer... |
+
+#### "Easy" or "simple"
+
+Using words that imply the complexity of a task or feature goes against our policy
+of [universal communication](#universal-communication). If you claim that a task is easy and the reader struggles to
+complete it, you may inadvertently discourage them.
+
+However, if you give users two options and want to relay that one option is genuinely less complex than another, be
+specific about how and why.
+
+For example, don't write, "Netdata's one-line installer is the easiest way to install Netdata." Instead, you might want
+to say, "Netdata's one-line installer requires fewer steps than manually installing from source."
+
+#### Slang, metaphors, and jargon
+
+A particular word, phrase, or metaphor you're familiar with might not translate well to the other cultures featured
+among Netdata's global community. We recommended you avoid slang or colloquialisms in your writing.
+
+In addition, don't use abbreviations that have not yet been defined in the content. See our section on
+[abbreviations](#abbreviations-acronyms-and-initialisms) for additional guidance.
+
+If you must use industry jargon, such as "mean time to resolution," define the term as clearly and concisely as you can.
+
+> Netdata helps you reduce your organization's mean time to resolution (MTTR), which is the average time the responsible
+> team requires to repair a system and resolve an ongoing incident.
+
+#### Spelling
+
+While the Netdata team is mostly *not* American, we still aspire to use American spelling whenever possible, as it is
+the standard for the monitoring industry.
+
+See the [word list](#word-list) for spellings of specific words.
+
+#### Capitalization
+
+Follow the general [English standards](https://owl.purdue.edu/owl/general_writing/mechanics/help_with_capitals.html) for
+capitalization. In summary:
+
+- Capitalize the first word of every new sentence.
+- Don't use uppercase for emphasis. (Netdata is the BEST!)
+- Capitalize the names of brands, software, products, and companies according to their official guidelines. (Netdata,
+ Docker, Apache, NGINX)
+- Avoid camel case (NetData) or all caps (NETDATA).
+
+Whenever you refer to the company Netdata, Inc., or the open-source monitoring agent the company develops, capitalize
+**Netdata**.
+
+However, if you are referring to a process, user, or group on a Linux system, use lowercase and fence the word in an
+inline code block: `` `netdata` ``.
+
+| | |
+| --------------- | ---------------------------------------------------------------------------------------------- |
+| Not recommended | The netdata agent, which spawns the netdata process, is actively maintained by netdata, inc. |
+| **Recommended** | The Netdata Agent, which spawns the `netdata` process, is actively maintained by Netdata, Inc. |
+
+##### Capitalization of document titles and page headings
+
+Document titles and page headings should use sentence case. That means you should only capitalize the first word.
+
+If you need to use the name of a brand, software, product, and company, capitalize it according to their official
+guidelines.
+
+Also, don't put a period (`.`) or colon (`:`) at the end of a title or header.
+
+| | |
+| --------------- | --------------------------------------------------------------------------------------------------- |
+| Not recommended | Getting Started Guide <br />Service Discovery and Auto-Detection: <br />Install netdata with docker |
+| **
+Recommended** | Getting started guide <br />Service discovery and auto-detection <br />Install Netdata with Docker |
+
+#### Abbreviations (acronyms and initialisms)
+
+Use abbreviations (including [acronyms and initialisms](https://www.dictionary.com/e/acronym-vs-abbreviation/)) in
+documentation when one exists, when it's widely accepted within the monitoring/sysadmin community, and when it improves
+the readability of a document.
+
+When introducing an abbreviation to a document for the first time, give the reader both the spelled-out version and the
+shortened version at the same time. For example:
+
+> Use Netdata to monitor Extended Berkeley Packet Filter (eBPF) metrics in real-time. After you define an abbreviation, don't switch back and forth. Use only the abbreviation for the rest of the document.
+
+You can also use abbreviations in a document's title to keep the title short and relevant. If you do this, you should
+still introduce the spelled-out name alongside the abbreviation as soon as possible.
+
+#### Clause order
+
+When instructing users to take action, give them the context first. By placing the context in an initial clause at the
+beginning of the sentence, users can immediately know if they want to read more, follow a link, or skip ahead.
+
+| | |
+| --------------- | ------------------------------------------------------------------------------ |
+| Not recommended | Read the reference guide if you'd like to learn more about custom dashboards. |
+| **Recommended** | If you'd like to learn more about custom dashboards, read the reference guide. |
+
+#### Oxford comma
+
+The Oxford comma is the comma used after the second-to-last item in a list of three or more items. It appears just
+before "and" or "or."
+
+| | |
+| --------------- | ---------------------------------------------------------------------------- |
+| Not recommended | Netdata can monitor RAM, disk I/O, MySQL queries per second and lm-sensors. |
+| **Recommended** | Netdata can monitor RAM, disk I/O, MySQL queries per second, and lm-sensors. |
+
+#### Future releases or features
+
+Do not mention future releases or upcoming features in writing unless they have been previously communicated via a
+public roadmap.
+
+In particular, documentation must describe, as accurately as possible, the Netdata Agent _as of
+the [latest commit](https://github.com/netdata/netdata/commits/master) in the GitHub repository_. For Netdata Cloud,
+documentation must reflect the *current state* of [production](https://app.netdata.cloud).
+
+#### Informational links
+
+Every link should clearly state its destination. Don't use words like "here" to describe where a link will take your
+reader.
+
+| | |
+| --------------- | ------------------------------------------------------------------------------------------ |
+| Not recommended | To install Netdata, click [here](https://github.com/netdata/netdata/blob/master/packaging/installer/README.md). |
+| **Recommended** | To install Netdata, read the [installation instructions](https://github.com/netdata/netdata/blob/master/packaging/installer/README.md). |
+
+Use links as often as required to provide necessary context. Blog posts and guides require less hyperlinks than
+documentation. See the section on [linking between documentation](#linking-between-documentation) for guidance on the
+Markdown syntax and path structure of inter-documentation links.
+
+#### Contractions
+
+Contractions like "you'll" or "they're" are acceptable in most Netdata writing. They're both authentic and playful, and
+reinforce the idea that you, as a writer, are guiding users through a particular idea, process, or feature.
+
+Contractions are generally not used in press releases or other media engagements.
+
+#### Emoji
+
+Emoji can add fun and character to your writing, but should be used sparingly and only if it matches the content's tone
+and desired audience.
+
+#### Switching Linux users
+
+Netdata documentation often suggests that users switch from their normal user to the `netdata` user to run specific
+commands. Use the following command to instruct users to make the switch:
+
+```bash
+sudo su -s /bin/bash netdata
+```
+
+#### Hostname/IP address of a node
+
+Use `NODE` instead of an actual or example IP address/hostname when referencing the process of navigating to a dashboard
+or API endpoint in a browser.
+
+| | |
+| --------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| Not recommended | Navigate to `http://example.com:19999` in your browser to see Netdata's dashboard. <br />Navigate to `http://203.0.113.0:19999` in your browser to see Netdata's dashboard. |
+| **
+Recommended** | Navigate to `http://NODE:19999` in your browser to see Netdata's dashboard. |
+
+If you worry that `NODE` doesn't provide enough context for the user, particularly in documentation or guides designed
+for beginners, you can provide an explanation:
+
+> With the Netdata Agent running, visit `http://NODE:19999/api/v1/info` in your browser, replacing `NODE` with the IP
+> address or hostname of your Agent.
+
+#### Paths and running commands
+
+When instructing users to run a Netdata-specific command, don't assume the path to said command, because not every
+Netdata Agent installation will have commands under the same paths. When applicable, help them navigate to the correct
+path, providing a recommendation or instructions on how to view the running configuration, which includes the correct
+paths.
+
+For example, the [configuration](https://github.com/netdata/netdata/blob/master/docs/configure/nodes.md) doc first teaches users how to find the Netdata config directory
+and navigate to it, then runs commands from the `/etc/netdata` path so that the instructions are more universal.
+
+Don't include full paths, beginning from the system's root (`/`), as these might not work on certain systems.
+
+| | |
+| --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| Not recommended | Use `edit-config` to edit Netdata's configuration: `sudo /etc/netdata/edit-config netdata.conf`. |
+| **
+Recommended** | Use `edit-config` to edit Netdata's configuration by first navigating to your [Netdata config directory](https://github.com/netdata/netdata/blob/master/docs/configure/nodes.md#the-netdata-config-directory), which is typically at `/etc/netdata`, then running `sudo edit-config netdata.conf`. |
+
+#### `sudo`
+
+Include `sudo` before a command if you believe most Netdata users will need to elevate privileges to run it. This makes
+our writing more universal, and users on `sudo`-less systems are generally already aware that they need to run commands
+differently.
+
+For example, most users need to use `sudo` with the `edit-config` script, because the Netdata config directory is owned
+by the `netdata` user. Same goes for restarting the Netdata Agent with `systemctl`.
+
+| | |
+| --------------- | -------------------------------------------------------------------------------------------------------------------------------------------- |
+| Not recommended | Run `edit-config netdata.conf` to configure the Netdata Agent. <br />Run `systemctl restart netdata` to restart the Netdata Agent. |
+| **
+Recommended** | Run `sudo edit-config netdata.conf` to configure the Netdata Agent. <br />Run `sudo systemctl restart netdata` to restart the Netdata Agent. |
+
+## Deploy and test docs
+
+<!--
+TODO: Update this section after implemeting a _docker-compose_ for builting and testing learn
+-->
+
+The Netdata team aggregates and publishes all documentation at [learn.netdata.cloud](/) using
+[Docusaurus](https://v2.docusaurus.io/) over at the [`netdata/learn` repository](https://github.com/netdata/learn).
+
+## Netdata-specific terms
+
+Consult the [Netdata Glossary](https://github.com/netdata/netdata/blob/master/docs/glossary.md) Netdata specific terms \ No newline at end of file
diff --git a/docs/guides/collect-apache-nginx-web-logs.md b/docs/guides/collect-apache-nginx-web-logs.md
index a75a4b1c..b4a52547 100644
--- a/docs/guides/collect-apache-nginx-web-logs.md
+++ b/docs/guides/collect-apache-nginx-web-logs.md
@@ -16,7 +16,7 @@ You can use the [LTSV log format](http://ltsv.org/), track TLS and cipher usage,
ever. In one test on a system with SSD storage, the collector consistently parsed the logs for 200,000 requests in
200ms, using ~30% of a single core.
-The [web_log](https://learn.netdata.cloud/docs/agent/collectors/go.d.plugin/modules/weblog/) collector is currently compatible
+The [web_log](https://github.com/netdata/go.d.plugin/blob/master/modules/weblog/README.md) collector is currently compatible
with [Nginx](https://nginx.org/en/) and [Apache](https://httpd.apache.org/).
This guide will walk you through using the new Go-based web log collector to turn the logs these web servers
@@ -90,7 +90,7 @@ jobs:
```
Restart Netdata with `sudo systemctl restart netdata`, or the [appropriate
-method](/docs/configure/start-stop-restart.md) for your system. Netdata should pick up your web server's access log and
+method](https://github.com/netdata/netdata/blob/master/docs/configure/start-stop-restart.md) for your system. Netdata should pick up your web server's access log and
begin showing real-time charts!
### Custom log formats and fields
@@ -99,7 +99,7 @@ The web log collector is capable of parsing custom Nginx and Apache log formats
leave that topic for a separate guide.
We do have [extensive
-documentation](https://learn.netdata.cloud/docs/agent/collectors/go.d.plugin/modules/weblog/#custom-log-format) on how
+documentation](https://github.com/netdata/go.d.plugin/blob/master/modules/weblog/README.md#custom-log-format) on how
to build custom parsing for Nginx and Apache logs.
## Tweak web log collector alarms
@@ -117,11 +117,11 @@ You can also edit this file directly with `edit-config`:
```
For more information about editing the defaults or writing new alarm entities, see our [health monitoring
-documentation](/health/README.md).
+documentation](https://github.com/netdata/netdata/blob/master/health/README.md).
## What's next?
-Now that you have web log collection up and running, we recommend you take a look at the collector's [documentation](https://learn.netdata.cloud/docs/agent/collectors/go.d.plugin/modules/weblog/) for some ideas of how you can turn these rather "boring" logs into powerful real-time tools for keeping your servers happy.
+Now that you have web log collection up and running, we recommend you take a look at the collector's [documentation](https://github.com/netdata/go.d.plugin/blob/master/modules/weblog/README.md) for some ideas of how you can turn these rather "boring" logs into powerful real-time tools for keeping your servers happy.
Don't forget to give GitHub user [Wing924](https://github.com/Wing924) a big 👍 for his hard work in starting up the Go
refactoring effort.
diff --git a/docs/guides/collect-unbound-metrics.md b/docs/guides/collect-unbound-metrics.md
index 8edcab10..5400fd83 100644
--- a/docs/guides/collect-unbound-metrics.md
+++ b/docs/guides/collect-unbound-metrics.md
@@ -55,7 +55,7 @@ You may not need to do any more configuration to have Netdata collect your Unbou
If you followed the steps above to enable `remote-control` and make your Unbound files readable by Netdata, that should
be enough. Restart Netdata with `sudo systemctl restart netdata`, or the [appropriate
-method](/docs/configure/start-stop-restart.md) for your system. You should see Unbound metrics in your Netdata
+method](https://github.com/netdata/netdata/blob/master/docs/configure/start-stop-restart.md) for your system. You should see Unbound metrics in your Netdata
dashboard!
![Some charts showing Unbound metrics in real-time](https://user-images.githubusercontent.com/1153921/69659974-93160f00-103c-11ea-88e6-27e9efcf8c0d.png)
@@ -100,7 +100,7 @@ Netdata will attempt to read `unbound.conf` to get the appropriate `address`, `c
`tls_key` parameters.
Restart Netdata with `sudo systemctl restart netdata`, or the [appropriate
-method](/docs/configure/start-stop-restart.md) for your system.
+method](https://github.com/netdata/netdata/blob/master/docs/configure/start-stop-restart.md) for your system.
### Manual setup for a remote Unbound server
diff --git a/docs/guides/configure/performance.md b/docs/guides/configure/performance.md
index cb52a114..256d6e85 100644
--- a/docs/guides/configure/performance.md
+++ b/docs/guides/configure/performance.md
@@ -18,7 +18,7 @@ threads. Despite collecting 100,000 metrics every second, the Agent still only u
single core.
But not everyone has such powerful systems at their disposal. For example, you might run the Agent on a cloud VM with
-only 512 MiB of RAM, or an IoT device like a [Raspberry Pi](/docs/guides/monitor/pi-hole-raspberry-pi.md). In these
+only 512 MiB of RAM, or an IoT device like a [Raspberry Pi](https://github.com/netdata/netdata/blob/master/docs/guides/monitor/pi-hole-raspberry-pi.md). In these
cases, reducing Netdata's footprint beyond its already diminutive size can pay big dividends, giving your services more
horsepower while still monitoring the health and the performance of the node, OS, hardware, and applications.
@@ -33,7 +33,7 @@ enabled, since we want you to experience the full thing.
- Familiarity with configuring the Netdata Agent with `edit-config`.
If you're not familiar with how to configure the Netdata Agent, read our [node configuration
-doc](/docs/configure/nodes.md) before continuing with this guide. This guide assumes familiarity with the Netdata config
+doc](https://github.com/netdata/netdata/blob/master/docs/configure/nodes.md) before continuing with this guide. This guide assumes familiarity with the Netdata config
directory, using `edit-config`, and the process of uncommenting/editing various settings in `netdata.conf` and other
configuration files.
@@ -43,11 +43,11 @@ Netdata's performance is primarily affected by **data collection/retention** and
You can configure almost all aspects of data collection/retention, and certain aspects of clients accessing data. For
example, you can't control how many users might be viewing a local Agent dashboard, [viewing an
-infrastructure](/docs/visualize/overview-infrastructure.md) in real-time with Netdata Cloud, or running [Metric
-Correlations](https://learn.netdata.cloud/docs/cloud/insights/metric-correlations).
+infrastructure](https://github.com/netdata/netdata/blob/master/docs/visualize/overview-infrastructure.md) in real-time with Netdata Cloud, or running [Metric
+Correlations](https://github.com/netdata/netdata/blob/master/docs/cloud/insights/metric-correlations.md).
The Netdata Agent runs with the lowest possible [process scheduling
-policy](/daemon/README.md#netdata-process-scheduling-policy), which is `nice 19`, and uses the `idle` process scheduler.
+policy](https://github.com/netdata/netdata/blob/master/daemon/README.md#netdata-process-scheduling-policy), which is `nice 19`, and uses the `idle` process scheduler.
Together, these settings ensure that the Agent only gets CPU resources when the node has CPU resources to space. If the
node reaches 100% CPU utilization, the Agent is stopped first to ensure your applications get any available resources.
In addition, under heavy load, collectors that require disk I/O may stop and show gaps in charts.
@@ -80,10 +80,10 @@ seconds, respectively.
Every collector and plugin has its own `update every` setting, which you can also change in the `go.d.conf`,
`python.d.conf`, or `charts.d.conf` files, or in individual collector configuration files. If the `update
every` for an individual collector is less than the global, the Netdata Agent uses the global setting. See the [enable
-or configure a collector](/docs/collect/enable-configure.md) doc for details.
+or configure a collector](https://github.com/netdata/netdata/blob/master/docs/collect/enable-configure.md) doc for details.
To reduce the frequency of an [internal
-plugin/collector](/docs/collect/how-collectors-work.md#collector-architecture-and-terminology), open `netdata.conf` and
+plugin/collector](https://github.com/netdata/netdata/blob/master/docs/collect/how-collectors-work.md#collector-architecture-and-terminology), open `netdata.conf` and
find the appropriate section. For example, to reduce the frequency of the `apps` plugin, which collects and visualizes
metrics on application resource utilization:
@@ -92,7 +92,7 @@ metrics on application resource utilization:
update every = 5
```
-To [configure an individual collector](/docs/collect/enable-configure.md), open its specific configuration file with
+To [configure an individual collector](https://github.com/netdata/netdata/blob/master/docs/collect/enable-configure.md), open its specific configuration file with
`edit-config` and look for the `update_every` setting. For example, to reduce the frequency of the `nginx` collector,
run `sudo ./edit-config go.d/nginx.conf`:
@@ -104,7 +104,7 @@ update_every: 10
## Disable unneeded plugins or collectors
If you know that you don't need an [entire plugin or a specific
-collector](/docs/collect/how-collectors-work.md#collector-architecture-and-terminology), you can disable any of them.
+collector](https://github.com/netdata/netdata/blob/master/docs/collect/how-collectors-work.md#collector-architecture-and-terminology), you can disable any of them.
Keep in mind that if a plugin/collector has nothing to do, it simply shuts down and does not consume system resources.
You will only improve the Agent's performance by disabling plugins/collectors that are actively collecting metrics.
@@ -139,7 +139,7 @@ modules:
## Lower memory usage for metrics retention
-Reduce the disk space that the [database engine](/database/engine/README.md) uses to retain metrics by editing
+Reduce the disk space that the [database engine](https://github.com/netdata/netdata/blob/master/database/engine/README.md) uses to retain metrics by editing
the `dbengine multihost disk space` option in `netdata.conf`. The default value is `256`, but can be set to a minimum of
`64`. By reducing the disk space allocation, Netdata also needs to store less metadata in the node's memory.
@@ -147,7 +147,7 @@ The `page cache size` option also directly impacts Netdata's memory usage, but h
Reducing the value of `dbengine multihost disk space` does slim down Netdata's resource usage, but it also reduces how
long Netdata retains metrics. Find the right balance of performance and metrics retention by using the [dbengine
-calculator](/docs/store/change-metrics-storage.md#calculate-the-system-resources-ram-disk-space-needed-to-store-metrics).
+calculator](https://github.com/netdata/netdata/blob/master/docs/store/change-metrics-storage.md#calculate-the-system-resources-ram-disk-space-needed-to-store-metrics).
All the settings are found in the `[global]` section of `netdata.conf`:
@@ -187,11 +187,11 @@ with the following:
## Run Netdata behind Nginx
-A dedicated web server like Nginx provides far more robustness than the Agent's internal [web server](/web/README.md).
+A dedicated web server like Nginx provides far more robustness than the Agent's internal [web server](https://github.com/netdata/netdata/blob/master/web/README.md).
Nginx can handle more concurrent connections, reuse idle connections, and use fast gzip compression to reduce payloads.
For details on installing Nginx as a proxy for the local Agent dashboard, see our [Nginx
-doc](/docs/Running-behind-nginx.md).
+doc](https://github.com/netdata/netdata/blob/master/docs/Running-behind-nginx.md).
After you complete Nginx setup according to the doc linked above, we recommend setting `keepalive` to `1024`, and using
gzip compression with the following options in the `location /` block:
@@ -264,14 +264,14 @@ On the child nodes you should add to `netdata.conf` the following:
We hope this guide helped you better understand how to optimize the performance of the Netdata Agent.
-Now that your Agent is running smoothly, we recommend you [secure your nodes](/docs/configure/nodes.md) if you haven't
+Now that your Agent is running smoothly, we recommend you [secure your nodes](https://github.com/netdata/netdata/blob/master/docs/configure/nodes.md) if you haven't
already.
Next, dive into some of Netdata's more complex features, such as configuring its health watchdog or exporting metrics to
an external time-series database.
-- [Interact with dashboards and charts](/docs/visualize/interact-dashboards-charts.md)
-- [Configure health alarms](/docs/monitor/configure-alarms.md)
-- [Export metrics to external time-series databases](/docs/export/external-databases.md)
+- [Interact with dashboards and charts](https://github.com/netdata/netdata/blob/master/docs/visualize/interact-dashboards-charts.md)
+- [Configure health alarms](https://github.com/netdata/netdata/blob/master/docs/monitor/configure-alarms.md)
+- [Export metrics to external time-series databases](https://github.com/netdata/netdata/blob/master/docs/export/external-databases.md)
[![analytics](https://www.google-analytics.com/collect?v=1&aip=1&t=pageview&_s=1&ds=github&dr=https%3A%2F%2Fgithub.com%2Fnetdata%2Fnetdata&dl=https%3A%2F%2Fmy-netdata.io%2Fgithub%2Fdocs%2Fguides%2Fconfigure%2Fperformance.md&_u=MAC~&cid=5792dfd7-8dc4-476b-af31-da2fdb9f93d2&tid=UA-64295674-3)](<>)
diff --git a/docs/guides/deploy/ansible.md b/docs/guides/deploy/ansible.md
index 35c94602..0472bdc6 100644
--- a/docs/guides/deploy/ansible.md
+++ b/docs/guides/deploy/ansible.md
@@ -3,11 +3,15 @@ title: Deploy Netdata with Ansible
description: "Deploy an infrastructure monitoring solution in minutes with the Netdata Agent and Ansible. Use and customize a simple playbook for monitoring as code."
image: /img/seo/guides/deploy/ansible.png
custom_edit_url: https://github.com/netdata/netdata/edit/master/docs/guides/deploy/ansible.md
+sidebar_label: "Install Netdata with Ansible"
+learn_status: "Published"
+learn_topic_type: "Tasks"
+learn_rel_path: "Installation"
-->
# Deploy Netdata with Ansible
-Netdata's [one-line kickstart](/docs/get-started.mdx) is zero-configuration, highly adaptable, and compatible with tons
+Netdata's [one-line kickstart](https://github.com/netdata/netdata/blob/master/docs/get-started.mdx) is zero-configuration, highly adaptable, and compatible with tons
of different operating systems and Linux distributions. You can use it on bare metal, VMs, containers, and everything
in-between.
@@ -101,8 +105,8 @@ two different SSH keys supplied by AWS.
### Edit the `vars/main.yml` file
In order to connect your node(s) to your Space in Netdata Cloud, and see all their metrics in real-time in [composite
-charts](/docs/visualize/overview-infrastructure.md) or perform [Metric
-Correlations](https://learn.netdata.cloud/docs/cloud/insights/metric-correlations), you need to set the `claim_token`
+charts](https://github.com/netdata/netdata/blob/master/docs/visualize/overview-infrastructure.md) or perform [Metric
+Correlations](https://github.com/netdata/netdata/blob/master/docs/cloud/insights/metric-correlations.md), you need to set the `claim_token`
and `claim_room` variables.
To find your `claim_token` and `claim_room`, go to Netdata Cloud, then click on your Space's name in the top navigation,
@@ -127,7 +131,7 @@ hostname of the node, the playbook disables that local dashboard by setting `web
security boost by not allowing any unwanted access to the local dashboard.
You can read more about this decision, or other ways you might lock down the local dashboard, in our [node security
-doc](https://learn.netdata.cloud/docs/configure/secure-nodes).
+doc](https://github.com/netdata/netdata/blob/master/docs/configure/secure-nodes.md).
> Curious about why Netdata's dashboard is open by default? Read our [blog
> post](https://www.netdata.cloud/blog/netdata-agent-dashboard/) on that zero-configuration design decision.
@@ -162,11 +166,11 @@ want to do with Netdata, so use those categories to dive in.
Some of the best places to start:
-- [Enable or configure a collector](/docs/collect/enable-configure.md)
-- [Supported collectors list](/collectors/COLLECTORS.md)
-- [See an overview of your infrastructure](/docs/visualize/overview-infrastructure.md)
-- [Interact with dashboards and charts](/docs/visualize/interact-dashboards-charts.md)
-- [Change how long Netdata stores metrics](/docs/store/change-metrics-storage.md)
+- [Enable or configure a collector](https://github.com/netdata/netdata/blob/master/docs/collect/enable-configure.md)
+- [Supported collectors list](https://github.com/netdata/netdata/blob/master/collectors/COLLECTORS.md)
+- [See an overview of your infrastructure](https://github.com/netdata/netdata/blob/master/docs/visualize/overview-infrastructure.md)
+- [Interact with dashboards and charts](https://github.com/netdata/netdata/blob/master/docs/visualize/interact-dashboards-charts.md)
+- [Change how long Netdata stores metrics](https://github.com/netdata/netdata/blob/master/docs/store/change-metrics-storage.md)
We're looking for more deployment and configuration management strategies, whether via Ansible or other
provisioning/infrastructure as code software, such as Chef or Puppet, in our [community
diff --git a/docs/guides/export/export-netdata-metrics-graphite.md b/docs/guides/export/export-netdata-metrics-graphite.md
index dd742e45..985ba224 100644
--- a/docs/guides/export/export-netdata-metrics-graphite.md
+++ b/docs/guides/export/export-netdata-metrics-graphite.md
@@ -13,9 +13,10 @@ action on these metrics, you may need to develop a stack of monitoring tools tha
anomalies and discover root causes faster.
We designed Netdata with interoperability in mind. The Agent collects thousands of metrics every second, and then what
-you do with them is up to you. You can [store metrics in the database engine](/docs/guides/longer-metrics-storage.md),
-or send them to another time series database for long-term storage or further analysis using Netdata's [exporting
-engine](/docs/export/external-databases.md).
+you do with them is up to you. You
+can [store metrics in the database engine](https://github.com/netdata/netdata/blob/master/docs/guides/longer-metrics-storage.md),
+or send them to another time series database for long-term storage or further analysis using
+Netdata's [exporting engine](https://github.com/netdata/netdata/blob/master/docs/export/external-databases.md).
In this guide, we'll show you how to export Netdata metrics to [Graphite](https://graphiteapp.org/) for long-term
storage and further analysis. Graphite is a free open-source software (FOSS) tool that collects graphs numeric
@@ -29,7 +30,8 @@ Let's get started.
## Install the Netdata Agent
-If you don't have the Netdata Agent installed already, visit the [installation guide](/packaging/installer/README.md)
+If you don't have the Netdata Agent installed already, visit
+the [installation guide](https://github.com/netdata/netdata/blob/master/packaging/installer/README.md)
for the recommended instructions for your system. In most cases, you can use the one-line installation script:
<OneLineInstallWget/>
@@ -63,8 +65,7 @@ docker run -d \
Open your browser and navigate to `http://NODE`, to see the Graphite interface. Nothing yet, but we'll fix that soon
enough.
-![An empty Graphite
-dashboard](https://user-images.githubusercontent.com/1153921/83798958-ea371500-a659-11ea-8403-d46f77a05b78.png)
+![An empty Graphite dashboard](https://user-images.githubusercontent.com/1153921/83798958-ea371500-a659-11ea-8403-d46f77a05b78.png)
## Enable the Graphite exporting connector
@@ -115,7 +116,8 @@ the port accordingly.
```
We'll not worry about the rest of the settings for now. Restart the Agent using `sudo systemctl restart netdata`, or the
-[appropriate method](/docs/configure/start-stop-restart.md) for your system, to spin up the exporting engine.
+[appropriate method](https://github.com/netdata/netdata/blob/master/docs/configure/start-stop-restart.md) for your
+system, to spin up the exporting engine.
## See and organize Netdata metrics in Graphite
@@ -125,8 +127,7 @@ metrics. You can also navigate directly to `http://NODE/dashboard`.
Let's switch the interface to help you understand which metrics Netdata is exporting to Graphite. Click on **Dashboard**
and **Configure UI**, then choose the **Tree** option. Refresh your browser to change the UI.
-![Change the Graphite
-UI](https://user-images.githubusercontent.com/1153921/83798697-77c63500-a659-11ea-8ed5-5e274953c871.png)
+![Change the Graphite UI](https://user-images.githubusercontent.com/1153921/83798697-77c63500-a659-11ea-8ed5-5e274953c871.png)
You should now see a tree of available contexts, including one that matches the hostname of the Agent exporting metrics.
In this example, the Agent's hostname is `arcturus`.
@@ -138,46 +139,43 @@ in the dashboard. Add a few other system CPU charts to flesh things out.
Next, let's combine one or two of these charts. Click and drag one chart onto the other, and wait until the green **Drop
to merge** dialog appears. Release to merge the charts.
-![Merging charts in
-Graphite](https://user-images.githubusercontent.com/1153921/83817628-1bbfd880-a67a-11ea-81bc-05efc639b6ce.png)
+![Merging charts in Graphite](https://user-images.githubusercontent.com/1153921/83817628-1bbfd880-a67a-11ea-81bc-05efc639b6ce.png)
Finally, save your dashboard. Click **Dashboard**, then **Save As**, then choose a name. Your dashboard is now saved.
Of course, this is just the beginning of the customization you can do with Graphite. You can change the time range,
share your dashboard with others, or use the composer to customize the size and appearance of specific charts. Learn
-more about adding, modifying, and combining graphs in the [Graphite
-docs](https://graphite.readthedocs.io/en/latest/dashboard.html).
+more about adding, modifying, and combining graphs in
+the [Graphite docs](https://graphite.readthedocs.io/en/latest/dashboard.html).
## Monitor the exporting engine
As soon as the exporting engine begins, Netdata begins reporting metrics about the system's health and performance.
-![Graphs for monitoring the exporting
-engine](https://user-images.githubusercontent.com/1153921/83800787-e5c02b80-a65c-11ea-865a-c447d2ce4cbb.png)
+![Graphs for monitoring the exporting engine](https://user-images.githubusercontent.com/1153921/83800787-e5c02b80-a65c-11ea-865a-c447d2ce4cbb.png)
You can use these charts to verify that Netdata is properly exporting metrics to Graphite. You can even add these
exporting charts to your Graphite dashboard!
### Add exporting charts to Netdata Cloud
-You can also show these exporting engine metrics on Netdata Cloud. If you don't have an account already, go [sign
-in](https://app.netdata.cloud) and get started for free. If you need some help along the way, read the [get started with
-Cloud guide](https://learn.netdata.cloud/docs/cloud/get-started).
+You can also show these exporting engine metrics on Netdata Cloud. If you don't have an account already,
+go [sign in](https://app.netdata.cloud) and get started for free. If you need some help along the way, read
+the [get started with Cloud guide](https://github.com/netdata/netdata/blob/master/docs/cloud/get-started.mdx).
Add more metrics to a War Room's Nodes view by clicking on the **Add metric** button, then typing `exporting` into the
context field. Choose the exporting contexts you want to add, then click **Add**. You'll see these charts alongside any
others you've customized in Netdata Cloud.
-![Exporting engine metrics in Netdata
-Cloud](https://user-images.githubusercontent.com/1153921/83902769-db139e00-a711-11ea-828e-aa7e32b04c75.png)
+![Exporting engine metrics in Netdata Cloud](https://user-images.githubusercontent.com/1153921/83902769-db139e00-a711-11ea-828e-aa7e32b04c75.png)
## What's next?
What you do with your exported metrics is entirely up to you, but as you might have seen in the Graphite connector
configuration block, there are many other ways to tweak and customize which metrics you export to Graphite and how
-often.
+often.
-For full details about each configuration option and what it does, see the [exporting reference
-guide](/exporting/README.md).
+For full details about each configuration option and what it does, see
+the [exporting reference guide](https://github.com/netdata/netdata/blob/master/exporting/README.md).
diff --git a/docs/guides/monitor-cockroachdb.md b/docs/guides/monitor-cockroachdb.md
index 46dd2535..3c6e1b2c 100644
--- a/docs/guides/monitor-cockroachdb.md
+++ b/docs/guides/monitor-cockroachdb.md
@@ -6,8 +6,9 @@ custom_edit_url: https://github.com/netdata/netdata/edit/master/docs/guides/moni
# Monitor CockroachDB metrics with Netdata
[CockroachDB](https://github.com/cockroachdb/cockroach) is an open-source project that brings SQL databases into
-scalable, disaster-resilient cloud deployments. Thanks to a [new CockroachDB
-collector](https://learn.netdata.cloud/docs/agent/collectors/go.d.plugin/modules/cockroachdb/) released in
+scalable, disaster-resilient cloud deployments. Thanks to
+a [new CockroachDB collector](https://github.com/netdata/go.d.plugin/blob/master/modules/cockroachdb/README.md)
+released in
[v1.20](https://blog.netdata.cloud/posts/release-1.20/), you can now monitor any number of CockroachDB databases with
maximum granularity using Netdata. Collect more than 50 unique metrics and put them on interactive visualizations
designed for better visual anomaly detection.
@@ -19,9 +20,9 @@ Let's dive in and walk through the process of monitoring CockroachDB metrics wit
## What's in this guide
-- [Configure the CockroachDB collector](#configure-the-cockroachdb-collector)
- - [Manual setup for a local CockroachDB database](#manual-setup-for-a-local-cockroachdb-database)
-- [Tweak CockroachDB alarms](#tweak-cockroachdb-alarms)
+- [Configure the CockroachDB collector](#configure-the-cockroachdb-collector)
+ - [Manual setup for a local CockroachDB database](#manual-setup-for-a-local-cockroachdb-database)
+- [Tweak CockroachDB alarms](#tweak-cockroachdb-alarms)
## Configure the CockroachDB collector
@@ -31,7 +32,7 @@ display them on the dashboard.
If your CockroachDB instance is accessible through `http://localhost:8080/` or `http://127.0.0.1:8080`, your setup is
complete. Restart Netdata with `sudo systemctl restart netdata`, or the [appropriate
-method](/docs/configure/start-stop-restart.md) for your system, and refresh your browser. You should see CockroachDB
+method](https://github.com/netdata/netdata/blob/master/docs/configure/start-stop-restart.md) for your system, and refresh your browser. You should see CockroachDB
metrics in your Netdata dashboard!
<figure>
@@ -59,8 +60,8 @@ edit, or create a new job with any of the parameters listed above in the file. B
required, and everything else is optional.
For a production cluster, you'll use either an IP address or the system's hostname. Be sure that your remote system
-allows TCP communication on port 8080, or whichever port you have configured CockroachDB's [Admin
-UI](https://www.cockroachlabs.com/docs/stable/monitoring-and-alerting.html#prometheus-endpoint) to listen on.
+allows TCP communication on port 8080, or whichever port you have configured CockroachDB's
+[Admin UI](https://www.cockroachlabs.com/docs/stable/monitoring-and-alerting.html#prometheus-endpoint) to listen on.
```yaml
# [ JOBS ]
@@ -80,7 +81,7 @@ jobs:
- name: remote
url: https://203.0.113.0:8080/_status/vars
tls_skip_verify: yes # If your certificate is self-signed
-
+
- name: remote_hostname
url: https://cockroachdb.example.com:8080/_status/vars
tls_skip_verify: yes # If your certificate is self-signed
@@ -109,28 +110,24 @@ cd /etc/netdata/ # Replace with your Netdata configuration directory, if not /et
```
For more information about editing the defaults or writing new alarm entities, see our health monitoring [quickstart
-guide](/health/QUICKSTART.md).
+guide](https://github.com/netdata/netdata/blob/master/health/QUICKSTART.md).
## What's next?
Now that you're collecting metrics from your CockroachDB databases, let us know how it's working for you! There's always
room for improvement or refinement based on real-world use cases. Feel free to [file an
-issue](https://github.com/netdata/netdata/issues/new?assignees=&labels=bug%2Cneeds+triage&template=BUG_REPORT.yml) with your
+issue](https://github.com/netdata/netdata/issues/new?assignees=&labels=bug%2Cneeds+triage&template=BUG_REPORT.yml) with
+your
thoughts.
Also, be sure to check out these useful resources:
-- [Netdata's CockroachDB
- documentation](https://learn.netdata.cloud/docs/agent/collectors/go.d.plugin/modules/cockroachdb/)
-- [Netdata's CockroachDB
- configuration](https://github.com/netdata/go.d.plugin/blob/master/config/go.d/cockroachdb.conf)
-- [Netdata's CockroachDB
- alarms](https://github.com/netdata/netdata/blob/29d9b5e51603792ee27ef5a21f1de0ba8e130158/health/health.d/cockroachdb.conf)
-- [CockroachDB homepage](https://www.cockroachlabs.com/product/)
-- [CockroachDB documentation](https://www.cockroachlabs.com/docs/stable/)
-- [`_status/vars` endpoint
- docs](https://www.cockroachlabs.com/docs/stable/monitoring-and-alerting.html#prometheus-endpoint)
-- [Monitor CockroachDB with
- Prometheus](https://www.cockroachlabs.com/docs/stable/monitor-cockroachdb-with-prometheus.html)
+- [Netdata's CockroachDB documentation](https://github.com/netdata/go.d.plugin/blob/master/modules/cockroachdb/README.md)
+- [Netdata's CockroachDB configuration](https://github.com/netdata/go.d.plugin/blob/master/config/go.d/cockroachdb.conf)
+- [Netdata's CockroachDB alarms](https://github.com/netdata/netdata/blob/29d9b5e51603792ee27ef5a21f1de0ba8e130158/health/health.d/cockroachdb.conf)
+- [CockroachDB homepage](https://www.cockroachlabs.com/product/)
+- [CockroachDB documentation](https://www.cockroachlabs.com/docs/stable/)
+- [`_status/vars` endpoint docs](https://www.cockroachlabs.com/docs/stable/monitoring-and-alerting.html#prometheus-endpoint)
+- [Monitor CockroachDB with Prometheus](https://www.cockroachlabs.com/docs/stable/monitor-cockroachdb-with-prometheus.html)
diff --git a/docs/guides/monitor-hadoop-cluster.md b/docs/guides/monitor-hadoop-cluster.md
index 62403f89..cce261fe 100644
--- a/docs/guides/monitor-hadoop-cluster.md
+++ b/docs/guides/monitor-hadoop-cluster.md
@@ -23,8 +23,8 @@ alternative, like the guide available from
For more specifics on the collection modules used in this guide, read the respective pages in our documentation:
-- [HDFS](https://learn.netdata.cloud/docs/agent/collectors/go.d.plugin/modules/hdfs)
-- [Zookeeper](https://learn.netdata.cloud/docs/agent/collectors/go.d.plugin/modules/zookeeper)
+- [HDFS](https://github.com/netdata/go.d.plugin/blob/master/modules/hdfs/README.md)
+- [Zookeeper](https://github.com/netdata/go.d.plugin/blob/master/modules/zookeeper/README.md)
## Set up your HDFS and Zookeeper installations
@@ -160,7 +160,7 @@ jobs:
address : 203.0.113.10:2182
```
-Finally, [restart Netdata](/docs/configure/start-stop-restart.md).
+Finally, [restart Netdata](https://github.com/netdata/netdata/blob/master/docs/configure/start-stop-restart.md).
```sh
sudo systemctl restart netdata
@@ -185,7 +185,7 @@ sudo /etc/netdata/edit-config health.d/zookeeper.conf
```
For more information about editing the defaults or writing new alarm entities, see our [health monitoring
-documentation](/health/README.md).
+documentation](https://github.com/netdata/netdata/blob/master/health/README.md).
## What's next?
diff --git a/docs/guides/monitor/anomaly-detection-python.md b/docs/guides/monitor/anomaly-detection-python.md
index ad8398cc..d6d27f4e 100644
--- a/docs/guides/monitor/anomaly-detection-python.md
+++ b/docs/guides/monitor/anomaly-detection-python.md
@@ -23,7 +23,7 @@ library](https://github.com/yzhao062/pyod/tree/master), which periodically runs
quantify how anomalous certain charts are.
All these metrics and alarms are available for centralized monitoring in [Netdata Cloud](https://app.netdata.cloud). If
-you choose to sign up for Netdata Cloud and [connect your nodes](/claim/README.md), you will have the ability to run
+you choose to sign up for Netdata Cloud and [connect your nodes](https://github.com/netdata/netdata/blob/master/claim/README.md), you will have the ability to run
tailored anomaly detection on every node in your infrastructure, regardless of its purpose or workload.
In this guide, you'll learn how to set up the anomalies collector to instantly detect anomalies in an Nginx web server
@@ -35,9 +35,9 @@ server](https://user-images.githubusercontent.com/1153921/103586700-da5b0a00-4ea
## Prerequisites
-- A node running the Netdata Agent. If you don't yet have that, [get Netdata](/docs/get-started.mdx).
+- A node running the Netdata Agent. If you don't yet have that, [get Netdata](https://github.com/netdata/netdata/blob/master/docs/get-started.mdx).
- A Netdata Cloud account. [Sign up](https://app.netdata.cloud) if you don't have one already.
-- Familiarity with configuring the Netdata Agent with [`edit-config`](/docs/configure/nodes.md).
+- Familiarity with configuring the Netdata Agent with [`edit-config`](https://github.com/netdata/netdata/blob/master/docs/configure/nodes.md).
- _Optional_: An Nginx web server running on the same node to follow the example configuration steps.
## Install required Python packages
@@ -65,7 +65,7 @@ Use `exit` to become your normal user again.
## Enable the anomalies collector
-Navigate to your [Netdata config directory](/docs/configure/nodes.md#the-netdata-config-directory) and use `edit-config`
+Navigate to your [Netdata config directory](https://github.com/netdata/netdata/blob/master/docs/configure/nodes.md#the-netdata-config-directory) and use `edit-config`
to open the `python.d.conf` file.
```bash
@@ -79,8 +79,8 @@ yourself if it doesn't already exist. Either way, the final result should look l
anomalies: yes
```
-[Restart the Agent](/docs/configure/start-stop-restart.md) with `sudo systemctl restart netdata`, or the [appropriate
-method](/docs/configure/start-stop-restart.md) for your system, to start up the anomalies collector. By default, the
+[Restart the Agent](https://github.com/netdata/netdata/blob/master/docs/configure/start-stop-restart.md) with `sudo systemctl restart netdata`, or the [appropriate
+method](https://github.com/netdata/netdata/blob/master/docs/configure/start-stop-restart.md) for your system, to start up the anomalies collector. By default, the
model training process runs every 30 minutes, and uses the previous 4 hours of metrics to establish a baseline for
health and performance across the default included charts.
@@ -105,7 +105,7 @@ involve tweaking the behavior of the ML training itself.
- `train_every_n`: How often to train the ML models.
- `train_n_secs`: The number of historical observations to train each model on. The default is 4 hours, but if your node
doesn't have historical metrics going back that far, consider [changing the metrics retention
- policy](/docs/store/change-metrics-storage.md) or reducing this window.
+ policy](https://github.com/netdata/netdata/blob/master/docs/store/change-metrics-storage.md) or reducing this window.
- `custom_models`: A way to define custom models that you want anomaly probabilities for, including multi-node or
streaming setups.
@@ -119,8 +119,8 @@ involve tweaking the behavior of the ML training itself.
As mentioned above, this guide uses an Nginx web server to demonstrate how the anomalies collector works. You must
configure the collector to monitor charts from the
-[Nginx](https://learn.netdata.cloud/docs/agent/collectors/go.d.plugin/modules/nginx) and [web
-log](https://learn.netdata.cloud/docs/agent/collectors/go.d.plugin/modules/weblog) collectors.
+[Nginx](https://github.com/netdata/go.d.plugin/blob/master/modules/nginx/README.md) and [web
+log](https://github.com/netdata/go.d.plugin/blob/master/modules/weblog/README.md) collectors.
`charts_regex` allows for some basic regex, such as wildcards (`*`) to match all contexts with a certain pattern. For
example, `system\..*` matches with any chart with a context that begins with `system.`, and ends in any number of other
@@ -163,27 +163,27 @@ volume of requests/responses, not, for example, which type of 4xx response a use
dimensions](https://user-images.githubusercontent.com/1153921/102820642-d69f9180-4392-11eb-91c5-d3d166d40105.png)
Apply the ideas behind the collector's regex and exclude settings to any other
-[system](/docs/collect/system-metrics.md), [container](/docs/collect/container-metrics.md), or
-[application](/docs/collect/application-metrics.md) metrics you want to detect anomalies for.
+[system](https://github.com/netdata/netdata/blob/master/docs/collect/system-metrics.md), [container](https://github.com/netdata/netdata/blob/master/docs/collect/container-metrics.md), or
+[application](https://github.com/netdata/netdata/blob/master/docs/collect/application-metrics.md) metrics you want to detect anomalies for.
## What's next?
Now that you know how to set up unsupervised anomaly detection in the Netdata Agent, using an Nginx web server as an
example, it's time to apply that knowledge to other mission-critical parts of your infrastructure. If you're not sure
-what to monitor next, check out our list of [collectors](/collectors/COLLECTORS.md) to see what kind of metrics Netdata
+what to monitor next, check out our list of [collectors](https://github.com/netdata/netdata/blob/master/collectors/COLLECTORS.md) to see what kind of metrics Netdata
can collect from your systems, containers, and applications.
-Keep on moving to [part 2](/docs/guides/monitor/visualize-monitor-anomalies.md), which covers the charts and alarms
+Keep on moving to [part 2](https://github.com/netdata/netdata/blob/master/docs/guides/monitor/visualize-monitor-anomalies.md), which covers the charts and alarms
Netdata creates for unsupervised anomaly detection.
For a different troubleshooting experience, try out the [Metric
-Correlations](https://learn.netdata.cloud/docs/cloud/insights/metric-correlations) feature in Netdata Cloud. Metric
+Correlations](https://github.com/netdata/netdata/blob/master/docs/cloud/insights/metric-correlations.md) feature in Netdata Cloud. Metric
Correlations helps you perform faster root cause analysis by narrowing a dashboard to only the charts most likely to be
related to an anomaly.
### Related reference documentation
-- [Netdata Agent · Anomalies collector](/collectors/python.d.plugin/anomalies/README.md)
-- [Netdata Agent · Nginx collector](https://learn.netdata.cloud/docs/agent/collectors/go.d.plugin/modules/nginx)
-- [Netdata Agent · web log collector](https://learn.netdata.cloud/docs/agent/collectors/go.d.plugin/modules/weblog)
-- [Netdata Cloud · Metric Correlations](https://learn.netdata.cloud/docs/cloud/insights/metric-correlations)
+- [Netdata Agent · Anomalies collector](https://github.com/netdata/netdata/blob/master/collectors/python.d.plugin/anomalies/README.md)
+- [Netdata Agent · Nginx collector](https://github.com/netdata/go.d.plugin/blob/master/modules/nginx/README.md)
+- [Netdata Agent · web log collector](https://github.com/netdata/go.d.plugin/blob/master/modules/weblog/README.md)
+- [Netdata Cloud · Metric Correlations](https://github.com/netdata/netdata/blob/master/docs/cloud/insights/metric-correlations.md)
diff --git a/docs/guides/monitor/anomaly-detection.md b/docs/guides/monitor/anomaly-detection.md
index e98c5c02..ce819d93 100644
--- a/docs/guides/monitor/anomaly-detection.md
+++ b/docs/guides/monitor/anomaly-detection.md
@@ -14,27 +14,27 @@ custom_edit_url: https://github.com/netdata/netdata/edit/master/docs/guides/moni
As of [`v1.32.0`](https://github.com/netdata/netdata/releases/tag/v1.32.0), Netdata comes with some ML powered [anomaly detection](https://en.wikipedia.org/wiki/Anomaly_detection) capabilities built into it and available to use out of the box, with zero configuration required (ML was enabled by default in `v1.35.0-29-nightly` in [this PR](https://github.com/netdata/netdata/pull/13158), previously it required a one line config change).
-This means that in addition to collecting raw value metrics, the Netdata agent will also produce an [`anomaly-bit`](https://learn.netdata.cloud/docs/agent/ml#anomaly-bit---100--anomalous-0--normal) every second which will be `100` when recent raw metric values are considered anomalous by Netdata and `0` when they look normal. Once we aggregate beyond one second intervals this aggregated `anomaly-bit` becomes an ["anomaly rate"](https://learn.netdata.cloud/docs/agent/ml#anomaly-rate---averageanomaly-bit).
+This means that in addition to collecting raw value metrics, the Netdata agent will also produce an [`anomaly-bit`](https://github.com/netdata/netdata/blob/master/ml/README.md#anomaly-bit---100--anomalous-0--normal) every second which will be `100` when recent raw metric values are considered anomalous by Netdata and `0` when they look normal. Once we aggregate beyond one second intervals this aggregated `anomaly-bit` becomes an ["anomaly rate"](https://github.com/netdata/netdata/blob/master/ml/README.md#anomaly-rate---averageanomaly-bit).
-To be as concrete as possible, the below api call shows how to access the raw anomaly bit of the `system.cpu` chart from the [london.my-netdata.io](https://london.my-netdata.io) Netdata demo server. Passing `options=anomaly-bit` returns the anomay bit instead of the raw metric value.
+To be as concrete as possible, the below api call shows how to access the raw anomaly bit of the `system.cpu` chart from the [london.my-netdata.io](https://london.my-netdata.io) Netdata demo server. Passing `options=anomaly-bit` returns the anomaly bit instead of the raw metric value.
```
https://london.my-netdata.io/api/v1/data?chart=system.cpu&options=anomaly-bit
```
-If we aggregate the above to just 1 point by adding `points=1` we get an "[Anomaly Rate](https://learn.netdata.cloud/docs/agent/ml#anomaly-rate---averageanomaly-bit)":
+If we aggregate the above to just 1 point by adding `points=1` we get an "[Anomaly Rate](https://github.com/netdata/netdata/blob/master/ml/README.md#anomaly-rate---averageanomaly-bit)":
```
https://london.my-netdata.io/api/v1/data?chart=system.cpu&options=anomaly-bit&points=1
```
-The fundamentals of Netdata's anomaly detection approach and implmentation are covered in lots more detail in the [agent ML documentation](https://learn.netdata.cloud/docs/agent/ml).
+The fundamentals of Netdata's anomaly detection approach and implementation are covered in lots more detail in the [agent ML documentation](https://github.com/netdata/netdata/blob/master/ml/README.md).
This guide will explain how to get started using these ML based anomaly detection capabilities within Netdata.
## Anomaly Advisor
-The [Anomaly Advisor](https://learn.netdata.cloud/docs/cloud/insights/anomaly-advisor) is the flagship anomaly detection feature within Netdata. In the "Anomalies" tab of Netdata you will see an overall "Anomaly Rate" chart that aggregates node level anomaly rate for all nodes in a space. The aim of this chart is to make it easy to quickly spot periods of time where the overall "[node anomaly rate](https://learn.netdata.cloud/docs/agent/ml#node-anomaly-rate)" is evelated in some unusual way and for what node or nodes this relates to.
+The [Anomaly Advisor](https://github.com/netdata/netdata/blob/master/docs/cloud/insights/anomaly-advisor.mdx) is the flagship anomaly detection feature within Netdata. In the "Anomalies" tab of Netdata you will see an overall "Anomaly Rate" chart that aggregates node level anomaly rate for all nodes in a space. The aim of this chart is to make it easy to quickly spot periods of time where the overall "[node anomaly rate](https://github.com/netdata/netdata/blob/master/ml/README.md#node-anomaly-rate)" is elevated in some unusual way and for what node or nodes this relates to.
![image](https://user-images.githubusercontent.com/2178292/175928290-490dd8b9-9c55-4724-927e-e145cb1cc837.png)
@@ -44,7 +44,7 @@ Once an area on the Anomaly Rate chart is highlighted netdata will append a "hea
## Embedded Anomaly Rate Charts
-Charts in both the [Overview](https://learn.netdata.cloud/docs/cloud/visualize/overview) and [single node dashboard](https://learn.netdata.cloud/docs/cloud/visualize/overview#jump-to-single-node-dashboards) tabs also expose the underlying anomaly rates for each dimension so users can easily see if the raw metrics are considered anomalous or not by Netdata.
+Charts in both the [Overview](https://github.com/netdata/netdata/blob/master/docs/cloud/visualize/overview.md) and [single node dashboard](https://github.com/netdata/netdata/blob/master/docs/cloud/visualize/overview.md#jump-to-single-node-dashboards) tabs also expose the underlying anomaly rates for each dimension so users can easily see if the raw metrics are considered anomalous or not by Netdata.
Pressing the anomalies icon (next to the information icon in the chart header) will expand the anomaly rate chart to make it easy to see how the anomaly rate for any individual dimension corresponds to the raw underlying data. In the example below we can see that the spike in `system.pgpgio|in` corresponded in the anomaly rate for that dimension jumping to 100% for a small period of time until the spike passed.
@@ -65,9 +65,9 @@ You can see some example ML based alert configurations below:
Check out the resources below to learn more about how Netdata is approaching ML:
-- [Agent ML documentation](https://learn.netdata.cloud/docs/agent/ml).
-- [Anomaly Advisor documentation](https://learn.netdata.cloud/docs/cloud/insights/anomaly-advisor).
-- [Metric Correlations documentation](https://learn.netdata.cloud/docs/cloud/insights/metric-correlations).
+- [Agent ML documentation](https://github.com/netdata/netdata/blob/master/ml/README.md).
+- [Anomaly Advisor documentation](https://github.com/netdata/netdata/blob/master/docs/cloud/insights/anomaly-advisor.mdx).
+- [Metric Correlations documentation](https://github.com/netdata/netdata/blob/master/docs/cloud/insights/metric-correlations.md).
- Anomaly Advisor [launch blog post](https://www.netdata.cloud/blog/introducing-anomaly-advisor-unsupervised-anomaly-detection-in-netdata/).
- Netdata Approach to ML [blog post](https://www.netdata.cloud/blog/our-approach-to-machine-learning/).
- `areal/ml` related [GitHub Discussions](https://github.com/netdata/netdata/discussions?discussions_q=label%3Aarea%2Fml).
diff --git a/docs/guides/monitor/dimension-templates.md b/docs/guides/monitor/dimension-templates.md
index 53912736..d2795a9c 100644
--- a/docs/guides/monitor/dimension-templates.md
+++ b/docs/guides/monitor/dimension-templates.md
@@ -8,24 +8,27 @@ custom_edit_url: https://github.com/netdata/netdata/edit/master/docs/guides/moni
Your ability to monitor the health of your systems and applications relies on your ability to create and maintain
the best set of alarms for your particular needs.
-In v1.18 of Netdata, we introduced **dimension templates** for alarms, which simplifies the process of writing [alarm
-entities](/health/REFERENCE.md#health-entity-reference) for charts with many dimensions.
+In v1.18 of Netdata, we introduced **dimension templates** for alarms, which simplifies the process of
+writing [alarm entities](https://github.com/netdata/netdata/blob/master/health/REFERENCE.md#health-entity-reference) for
+charts with many dimensions.
Dimension templates can condense many individual entities into one—no more copy-pasting one entity and changing the
`alarm`/`template` and `lookup` lines for each dimension you'd like to monitor.
They are, however, an advanced health monitoring feature. For more basic instructions on creating your first alarm,
-check out our [health monitoring documentation](/health/README.md), which also includes
-[examples](/health/REFERENCE.md#example-alarms).
+check out our [health monitoring documentation](https://github.com/netdata/netdata/blob/master/health/README.md), which also includes
+[examples](https://github.com/netdata/netdata/blob/master/health/REFERENCE.md#example-alarms).
## The fundamentals of `foreach`
-Our dimension templates update creates a new `foreach` parameter to the existing [`lookup`
-line](/health/REFERENCE.md#alarm-line-lookup). This is where the magic happens.
+Our dimension templates update creates a new `foreach` parameter to the
+existing [`lookup` line](https://github.com/netdata/netdata/blob/master/health/REFERENCE.md#alarm-line-lookup). This
+is where the magic happens.
You use the `foreach` parameter to specify which dimensions you want to monitor with this single alarm. You can separate
-them with a comma (`,`) or a pipe (`|`). You can also use a [Netdata simple pattern](/libnetdata/simple_pattern/README.md)
-to create many alarms with a regex-like syntax.
+them with a comma (`,`) or a pipe (`|`). You can also use
+a [Netdata simple pattern](https://github.com/netdata/netdata/blob/master/libnetdata/simple_pattern/README.md) to create
+many alarms with a regex-like syntax.
The `foreach` parameter _has_ to be the last parameter in your `lookup` line, and if you have both `of` and `foreach` in
the same `lookup` line, Netdata will ignore the `of` parameter and use `foreach` instead.
@@ -95,7 +98,7 @@ Let's look at some other examples of how `foreach` works so you can best apply i
In the last example, we used `foreach system,user,nice` to create three distinct alarms using dimension templates. But
what if you want to quickly create alarms for _all_ the dimensions of a given chart?
-Use a [simple pattern](/libnetdata/simple_pattern/README.md)! One example of a simple pattern is a single wildcard
+Use a [simple pattern](https://github.com/netdata/netdata/blob/master/libnetdata/simple_pattern/README.md)! One example of a simple pattern is a single wildcard
(`*`).
Instead of monitoring system CPU usage, let's monitor per-application CPU usage using the `apps.cpu` chart. Passing a
@@ -113,14 +116,15 @@ lookup: average -10m percentage foreach *
This entity will now create alarms for every dimension in the `apps.cpu` chart. Given that most `apps.cpu` charts have
10 or more dimensions, using the wildcard ensures you catch every CPU-hogging process.
-To learn more about how to use simple patterns with dimension templates, see our [simple patterns
-documentation](/libnetdata/simple_pattern/README.md).
+To learn more about how to use simple patterns with dimension templates, see
+our [simple patterns documentation](https://github.com/netdata/netdata/blob/master/libnetdata/simple_pattern/README.md).
## Using `foreach` with alarm templates
-Dimension templates also work with [alarm templates](/health/REFERENCE.md#alarm-line-alarm-or-template). Alarm
-templates help you create alarms for all the charts with a given context—for example, all the cores of your system's
-CPU.
+Dimension templates also work
+with [alarm templates](https://github.com/netdata/netdata/blob/master/health/REFERENCE.md#alarm-line-alarm-or-template).
+Alarm templates help you create alarms for all the charts with a given context—for example, all the cores of your
+system's CPU.
By combining the two, you can create dozens of individual alarms with a single template entity. Here's how you would
create alarms for the `system`, `user`, and `nice` dimensions for every chart in the `cpu.cpu` context—or, in other
@@ -170,7 +174,8 @@ alarms that will help you better monitor the health of your systems.
Or, at the very least, simplify your configuration files.
-For information about other advanced features in Netdata's health monitoring toolkit, check out our [health
-documentation](/health/README.md). And if you have some cool alarms you built using dimension templates,
+For information about other advanced features in Netdata's health monitoring toolkit, check out
+our [health documentation](https://github.com/netdata/netdata/blob/master/health/README.md). And if you have some cool
+alarms you built using dimension templates,
diff --git a/docs/guides/monitor/kubernetes-k8s-netdata.md b/docs/guides/monitor/kubernetes-k8s-netdata.md
index 5cfefe89..5732fc96 100644
--- a/docs/guides/monitor/kubernetes-k8s-netdata.md
+++ b/docs/guides/monitor/kubernetes-k8s-netdata.md
@@ -46,7 +46,7 @@ To follow this tutorial, you need:
- A free Netdata Cloud account. [Sign up](https://app.netdata.cloud/sign-up?cloudRoute=/spaces) if you don't have one
already.
- A working cluster running Kubernetes v1.9 or newer, with a Netdata deployment and connected parent/child nodes. See
- our [Kubernetes deployment process](/packaging/installer/methods/kubernetes.md) for details on deployment and
+ our [Kubernetes deployment process](https://github.com/netdata/netdata/blob/master/packaging/installer/methods/kubernetes.md) for details on deployment and
conneting to Cloud.
- The [`kubectl`](https://kubernetes.io/docs/reference/kubectl/overview/) command line tool, within [one minor version
difference](https://kubernetes.io/docs/tasks/tools/install-kubectl/#before-you-begin) of your cluster, on an
@@ -104,7 +104,7 @@ To get started, [sign in](https://app.netdata.cloud/sign-in?cloudRoute=/spaces)
to the War Room you connected your cluster to, if not **General**.
Netdata Cloud is already visualizing your Kubernetes metrics, streamed in real-time from each node, in the
-[Overview](https://learn.netdata.cloud/docs/cloud/visualize/overview):
+[Overview](https://github.com/netdata/netdata/blob/master/docs/cloud/visualize/overview.md):
![Netdata's Kubernetes monitoring
dashboard](https://user-images.githubusercontent.com/1153921/109037415-eafc5500-7687-11eb-8773-9b95941e3328.png)
@@ -126,8 +126,8 @@ cluster](https://user-images.githubusercontent.com/1153921/109042169-19c8fa00-76
For example, the chart above shows a spike in the CPU utilization from `rabbitmq` every minute or so, along with a
baseline CPU utilization of 10-15% across the cluster.
-Read about the [Overview](https://learn.netdata.cloud/docs/cloud/visualize/overview) and some best practices on [viewing
-an overview of your infrastructure](/docs/visualize/overview-infrastructure.md) for details on using composite charts to
+Read about the [Overview](https://github.com/netdata/netdata/blob/master/docs/cloud/visualize/overview.md) and some best practices on [viewing
+an overview of your infrastructure](https://github.com/netdata/netdata/blob/master/docs/visualize/overview-infrastructure.md) for details on using composite charts to
drill down into per-node performance metrics.
## Pod and container metrics
@@ -154,7 +154,7 @@ Let's explore the most colorful box by hovering over it.
container](https://user-images.githubusercontent.com/1153921/109049544-a8417980-7695-11eb-80a7-109b4a645a27.png)
The **Context** tab shows `rabbitmq-5bb66bb6c9-6xr5b` as the container's image name, which means this container is
-running a [RabbitMQ](https://learn.netdata.cloud/docs/agent/collectors/go.d.plugin/modules/rabbitmq) workload.
+running a [RabbitMQ](https://github.com/netdata/go.d.plugin/blob/master/modules/rabbitmq/README.md) workload.
Click the **Metrics** tab to see real-time metrics from that container. Unsurprisingly, it shows a spike in CPU
utilization at regular intervals.
@@ -173,7 +173,7 @@ different namespaces.
![Time-series Kubernetes monitoring in Netdata
Cloud](https://user-images.githubusercontent.com/1153921/109075210-126a1680-76b6-11eb-918d-5acdcdac152d.png)
-Each composite chart has a [definition bar](https://learn.netdata.cloud/docs/cloud/visualize/overview#definition-bar)
+Each composite chart has a [definition bar](https://github.com/netdata/netdata/blob/master/docs/cloud/visualize/overview.md#definition-bar)
for complete customization. For example, grouping the top chart by `k8s_container_name` reveals new information.
![Changing time-series charts](https://user-images.githubusercontent.com/1153921/109075212-139b4380-76b6-11eb-836f-939482ae55fc.png)
@@ -183,20 +183,20 @@ for complete customization. For example, grouping the top chart by `k8s_containe
Netdata has a [service discovery plugin](https://github.com/netdata/agent-service-discovery), which discovers and
creates configuration files for [compatible
services](https://github.com/netdata/helmchart#service-discovery-and-supported-services) and any endpoints covered by
-our [generic Prometheus collector](https://learn.netdata.cloud/docs/agent/collectors/go.d.plugin/modules/prometheus).
+our [generic Prometheus collector](https://github.com/netdata/go.d.plugin/blob/master/modules/prometheus/README.md).
Netdata uses these files to collect metrics from any compatible application as they run _inside_ of a pod. Service
discovery happens without manual intervention as pods are created, destroyed, or moved between nodes.
Service metrics show up on the Overview as well, beneath the **Kubernetes** section, and are labeled according to the
service in question. For example, the **RabbitMQ** section has numerous charts from the [`rabbitmq`
-collector](https://learn.netdata.cloud/docs/agent/collectors/go.d.plugin/modules/rabbitmq):
+collector](https://github.com/netdata/go.d.plugin/blob/master/modules/rabbitmq/README.md):
![Finding service discovery
metrics](https://user-images.githubusercontent.com/1153921/109054511-2eac8a00-769b-11eb-97f1-da93acb4b5fe.png)
> The robot-shop cluster has more supported services, such as MySQL, which are not visible with zero configuration. This
> is usually because of services running on non-default ports, using non-default names, or required passwords. Read up
-> on [configuring service discovery](/packaging/installer/methods/kubernetes.md#configure-service-discovery) to collect
+> on [configuring service discovery](https://github.com/netdata/netdata/blob/master/packaging/installer/methods/kubernetes.md#configure-service-discovery) to collect
> more service metrics.
Service metrics are essential to infrastructure monitoring, as they're the best indicator of the end-user experience,
@@ -210,7 +210,7 @@ Netdata also automatically collects metrics from two essential Kubernetes proces
The **k8s kubelet** section visualizes metrics from the Kubernetes agent responsible for managing every pod on a given
node. This also happens without any configuration thanks to the [kubelet
-collector](https://learn.netdata.cloud/docs/agent/collectors/go.d.plugin/modules/k8s_kubelet).
+collector](https://github.com/netdata/go.d.plugin/blob/master/modules/k8s_kubelet/README.md).
Monitoring each node's kubelet can be invaluable when diagnosing issues with your Kubernetes cluster. For example, you
can see if the number of running containers/pods has dropped, which could signal a fault or crash in a particular
@@ -226,7 +226,7 @@ configuration-related errors, and the actual vs. desired numbers of volumes, plu
The **k8s kube-proxy** section displays metrics about the network proxy that runs on each node in your Kubernetes
cluster. kube-proxy lets pods communicate with each other and accept sessions from outside your cluster. Its metrics are
collected by the [kube-proxy
-collector](https://learn.netdata.cloud/docs/agent/collectors/go.d.plugin/modules/k8s_kubeproxy).
+collector](https://github.com/netdata/go.d.plugin/blob/master/modules/k8s_kubeproxy/README.md).
With Netdata, you can monitor how often your k8s proxies are syncing proxy rules between nodes. Dramatic changes in
these figures could indicate an anomaly in your cluster that's worthy of further investigation.
@@ -246,9 +246,9 @@ clusters of all sizes.
- [Netdata Helm chart](https://github.com/netdata/helmchart)
- [Netdata service discovery](https://github.com/netdata/agent-service-discovery)
- [Netdata Agent · `kubelet`
- collector](https://learn.netdata.cloud/docs/agent/collectors/go.d.plugin/modules/k8s_kubelet)
+ collector](https://github.com/netdata/go.d.plugin/blob/master/modules/k8s_kubelet/README.md)
- [Netdata Agent · `kube-proxy`
- collector](https://learn.netdata.cloud/docs/agent/collectors/go.d.plugin/modules/k8s_kubeproxy)
-- [Netdata Agent · `cgroups.plugin`](/collectors/cgroups.plugin/README.md)
+ collector](https://github.com/netdata/go.d.plugin/blob/master/modules/k8s_kubeproxy/README.md)
+- [Netdata Agent · `cgroups.plugin`](https://github.com/netdata/netdata/blob/master/collectors/cgroups.plugin/README.md)
diff --git a/docs/guides/monitor/lamp-stack.md b/docs/guides/monitor/lamp-stack.md
index 29b35e14..165888c4 100644
--- a/docs/guides/monitor/lamp-stack.md
+++ b/docs/guides/monitor/lamp-stack.md
@@ -58,7 +58,7 @@ To follow this tutorial, you need:
## Install the Netdata Agent
If you don't have the free, open-source Netdata monitoring agent installed on your node yet, get started with a [single
-kickstart command](/docs/get-started.mdx):
+kickstart command](https://github.com/netdata/netdata/blob/master/docs/get-started.mdx):
<OneLineInstallWget/>
@@ -68,15 +68,15 @@ replacing `NODE` with the hostname or IP address of your system.
## Enable hardware and Linux system monitoring
-There's nothing you need to do to enable [system monitoring](/docs/collect/system-metrics.md) and Linux monitoring with
+There's nothing you need to do to enable [system monitoring](https://github.com/netdata/netdata/blob/master/docs/collect/system-metrics.md) and Linux monitoring with
the Netdata Agent, which autodetects metrics from CPUs, memory, disks, networking devices, and Linux processes like
systemd without any configuration. If you're using containers, Netdata automatically collects resource utilization
-metrics from each using the [cgroups data collector](/collectors/cgroups.plugin/README.md).
+metrics from each using the [cgroups data collector](https://github.com/netdata/netdata/blob/master/collectors/cgroups.plugin/README.md).
## Enable Apache monitoring
Let's begin by configuring Apache to work with Netdata's [Apache data
-collector](https://learn.netdata.cloud/docs/agent/collectors/go.d.plugin/modules/apache).
+collector](https://github.com/netdata/go.d.plugin/blob/master/modules/apache/README.md).
Actually, there's nothing for you to do to enable Apache monitoring with Netdata.
@@ -87,7 +87,7 @@ metrics](https://httpd.apache.org/docs/2.4/mod/mod_status.html), which is just _
## Enable web log monitoring
The Netdata Agent also comes with a [web log
-collector](https://learn.netdata.cloud/docs/agent/collectors/go.d.plugin/modules/weblog), which reads Apache's access
+collector](https://github.com/netdata/go.d.plugin/blob/master/modules/weblog/README.md), which reads Apache's access
log file, processes each line, and converts them into per-second metrics. On Debian systems, it reads the file at
`/var/log/apache2/access.log`.
@@ -100,7 +100,7 @@ monitoring.
Because your MySQL database is password-protected, you do need to tell MySQL to allow the `netdata` user to connect to
without a password. Netdata's [MySQL data
-collector](https://learn.netdata.cloud/docs/agent/collectors/go.d.plugin/modules/mysql) collects metrics in _read-only_
+collector](https://github.com/netdata/go.d.plugin/blob/master/modules/mysql/README.md) collects metrics in _read-only_
mode, without being able to alter or affect operations in any way.
First, log into the MySQL shell. Then, run the following three commands, one at a time:
@@ -112,15 +112,15 @@ FLUSH PRIVILEGES;
```
Run `sudo systemctl restart netdata`, or the [appropriate alternative for your
-system](/docs/configure/start-stop-restart.md), to collect dozens of metrics every second for robust MySQL monitoring.
+system](https://github.com/netdata/netdata/blob/master/docs/configure/start-stop-restart.md), to collect dozens of metrics every second for robust MySQL monitoring.
## Enable PHP monitoring
Unlike Apache or MySQL, PHP isn't a service that you can monitor directly, unless you instrument a PHP-based application
-with [StatsD](/collectors/statsd.plugin/README.md).
+with [StatsD](https://github.com/netdata/netdata/blob/master/collectors/statsd.plugin/README.md).
However, if you use [PHP-FPM](https://php-fpm.org/) in your LAMP stack, you can monitor that process with our [PHP-FPM
-data collector](https://learn.netdata.cloud/docs/agent/collectors/go.d.plugin/modules/phpfpm).
+data collector](https://github.com/netdata/go.d.plugin/blob/master/modules/phpfpm/README.md).
Open your PHP-FPM configuration for editing, replacing `7.4` with your version of PHP:
@@ -166,12 +166,12 @@ If the Netdata Agent isn't already open in your browser, open a new tab and navi
> If you [signed up](https://app.netdata.cloud/sign-up?cloudRoute=/spaces) for Netdata Cloud earlier, you can also view
> the exact same LAMP stack metrics there, plus additional features, like drag-and-drop custom dashboards. Be sure to
-> [connecting your node](/claim/README.md) to start streaming metrics to your browser through Netdata Cloud.
+> [connecting your node](https://github.com/netdata/netdata/blob/master/claim/README.md) to start streaming metrics to your browser through Netdata Cloud.
Netdata automatically organizes all metrics and charts onto a single page for easy navigation. Peek at gauges to see
overall system performance, then scroll down to see more. Click-and-drag with your mouse to pan _all_ charts back and
forth through different time intervals, or hold `SHIFT` and use the scrollwheel (or two-finger scroll) to zoom in and
-out. Check out our doc on [interacting with charts](/docs/visualize/interact-dashboards-charts.md) for all the details.
+out. Check out our doc on [interacting with charts](https://github.com/netdata/netdata/blob/master/docs/visualize/interact-dashboards-charts.md) for all the details.
![The Netdata
dashboard](https://user-images.githubusercontent.com/1153921/109520555-98e17800-7a69-11eb-86ec-16f689da4527.png)
@@ -205,15 +205,15 @@ Here's a quick reference for what charts you might want to focus on after settin
The Netdata Agent comes with hundreds of pre-configured alarms to help you keep tabs on your system, including 19 alarms
designed for smarter LAMP stack monitoring.
-Click the 🔔 icon in the top navigation to [see active alarms](/docs/monitor/view-active-alarms.md). The **Active** tabs
+Click the 🔔 icon in the top navigation to [see active alarms](https://github.com/netdata/netdata/blob/master/docs/monitor/view-active-alarms.md). The **Active** tabs
shows any alarms currently triggered, while the **All** tab displays a list of _every_ pre-configured alarm. The
![An example of LAMP stack
alarms](https://user-images.githubusercontent.com/1153921/109524120-5883f900-7a6d-11eb-830e-0e7baaa28163.png)
-[Tweak alarms](/docs/monitor/configure-alarms.md) based on your infrastructure monitoring needs, and to see these alarms
+[Tweak alarms](https://github.com/netdata/netdata/blob/master/docs/monitor/configure-alarms.md) based on your infrastructure monitoring needs, and to see these alarms
in other places, like your inbox or a Slack channel, [enable a notification
-method](/docs/monitor/enable-notifications.md).
+method](https://github.com/netdata/netdata/blob/master/docs/monitor/enable-notifications.md).
## What's next?
@@ -223,7 +223,7 @@ services. The per-second metrics granularity means you have the most accurate in
any LAMP-related issues.
Another powerful way to monitor the availability of a LAMP stack is the [`httpcheck`
-collector](https://learn.netdata.cloud/docs/agent/collectors/go.d.plugin/modules/httpcheck), which pings a web server at
+collector](https://github.com/netdata/go.d.plugin/blob/master/modules/httpcheck/README.md), which pings a web server at
a regular interval and tells you whether if and how quickly it's responding. The `response_match` option also lets you
monitor when the web server's response isn't what you expect it to be, which might happen if PHP-FPM crashes, for
example.
@@ -233,14 +233,14 @@ we're not covering it here, but it _does_ work in a single-node setup. Just don'
node crashed.
If you're planning on managing more than one node, or want to take advantage of advanced features, like finding the
-source of issues faster with [Metric Correlations](https://learn.netdata.cloud/docs/cloud/insights/metric-correlations),
+source of issues faster with [Metric Correlations](https://github.com/netdata/netdata/blob/master/docs/cloud/insights/metric-correlations.md),
[sign up](https://app.netdata.cloud/sign-up?cloudRoute=/spaces) for a free Netdata Cloud account.
### Related reference documentation
-- [Netdata Agent · Get started](/docs/get-started.mdx)
-- [Netdata Agent · Apache data collector](https://learn.netdata.cloud/docs/agent/collectors/go.d.plugin/modules/apache)
-- [Netdata Agent · Web log collector](https://learn.netdata.cloud/docs/agent/collectors/go.d.plugin/modules/weblog)
-- [Netdata Agent · MySQL data collector](https://learn.netdata.cloud/docs/agent/collectors/go.d.plugin/modules/mysql)
-- [Netdata Agent · PHP-FPM data collector](https://learn.netdata.cloud/docs/agent/collectors/go.d.plugin/modules/phpfpm)
+- [Netdata Agent · Get started](https://github.com/netdata/netdata/blob/master/docs/get-started.mdx)
+- [Netdata Agent · Apache data collector](https://github.com/netdata/go.d.plugin/blob/master/modules/apache/README.md)
+- [Netdata Agent · Web log collector](https://github.com/netdata/go.d.plugin/blob/master/modules/weblog/README.md)
+- [Netdata Agent · MySQL data collector](https://github.com/netdata/go.d.plugin/blob/master/modules/mysql/README.md)
+- [Netdata Agent · PHP-FPM data collector](https://github.com/netdata/go.d.plugin/blob/master/modules/phpfpm/README.md)
diff --git a/docs/guides/monitor/pi-hole-raspberry-pi.md b/docs/guides/monitor/pi-hole-raspberry-pi.md
index 1246d8ba..5099d12b 100644
--- a/docs/guides/monitor/pi-hole-raspberry-pi.md
+++ b/docs/guides/monitor/pi-hole-raspberry-pi.md
@@ -79,7 +79,7 @@ service](https://discourse.pi-hole.net/t/how-do-i-configure-my-devices-to-use-pi
finished setting up Pi-hole at this point.
As far as configuring Netdata to monitor Pi-hole metrics, there's nothing you actually need to do. Netdata's [Pi-hole
-collector](https://learn.netdata.cloud/docs/agent/collectors/go.d.plugin/modules/pihole) will autodetect the new service
+collector](https://github.com/netdata/go.d.plugin/blob/master/modules/pihole/README.md) will autodetect the new service
running on your Raspberry Pi and immediately start collecting metrics every second.
Restart Netdata with `sudo systemctl restart netdata`, which will then recognize that Pi-hole is running and start a
@@ -98,15 +98,15 @@ part of your system might affect another.
![The Netdata dashboard in
action](https://user-images.githubusercontent.com/1153921/80827388-b9fee100-8b98-11ea-8f60-0d7824667cd3.gif)
-If you're completely new to Netdata, look at our [step-by-step guide](/docs/guides/step-by-step/step-00.md) for a
-walkthrough of all its features. For a more expedited tour, see the [get started guide](/docs/get-started.mdx).
+If you're completely new to Netdata, look at our [step-by-step guide](https://github.com/netdata/netdata/blob/master/docs/guides/step-by-step/step-00.md) for a
+walkthrough of all its features. For a more expedited tour, see the [get started guide](https://github.com/netdata/netdata/blob/master/docs/get-started.mdx).
### Enable temperature sensor monitoring
You need to manually enable Netdata's built-in [temperature sensor
-collector](https://learn.netdata.cloud/docs/agent/collectors/charts.d.plugin/sensors) to start collecting metrics.
+collector](https://github.com/netdata/netdata/blob/master/collectors/charts.d.plugin/sensors/README.md) to start collecting metrics.
-> Netdata uses a few plugins to manage its [collectors](/collectors/REFERENCE.md), each using a different language: Go,
+> Netdata uses a few plugins to manage its [collectors](https://github.com/netdata/netdata/blob/master/collectors/REFERENCE.md), each using a different language: Go,
> Python, Node.js, and Bash. While our Go collectors are undergoing the most active development, we still support the
> other languages. In this case, you need to enable a temperature sensor collector that's written in Bash.
@@ -124,7 +124,7 @@ Raspberry Pi temperature sensor monitoring.
### Storing historical metrics on your Raspberry Pi
By default, Netdata allocates 256 MiB in disk space to store historical metrics inside the [database
-engine](/database/engine/README.md). On the Raspberry Pi used for this guide, Netdata collects 1,500 metrics every
+engine](https://github.com/netdata/netdata/blob/master/database/engine/README.md). On the Raspberry Pi used for this guide, Netdata collects 1,500 metrics every
second, which equates to storing 3.5 days worth of historical metrics.
You can increase this allocation by editing `netdata.conf` and increasing the `dbengine multihost disk space` setting to
@@ -136,8 +136,8 @@ more than 256.
```
Use our [database sizing
-calculator](/docs/store/change-metrics-storage.md#calculate-the-system-resources-ram-disk-space-needed-to-store-metrics)
-and [guide on storing historical metrics](/docs/guides/longer-metrics-storage.md) to help you determine the right
+calculator](https://github.com/netdata/netdata/blob/master/docs/store/change-metrics-storage.md#calculate-the-system-resources-ram-disk-space-needed-to-store-metrics)
+and [guide on storing historical metrics](https://github.com/netdata/netdata/blob/master/docs/guides/longer-metrics-storage.md) to help you determine the right
setting for your Raspberry Pi.
## What's next?
@@ -146,12 +146,12 @@ Now that you're monitoring Pi-hole and your Raspberry Pi with Netdata, you can e
configure Netdata to more specific goals.
Most importantly, you can always install additional services and instantly collect metrics from many of them with our
-[300+ integrations](/collectors/COLLECTORS.md).
+[300+ integrations](https://github.com/netdata/netdata/blob/master/collectors/COLLECTORS.md).
-- [Optimize performance](/docs/guides/configure/performance.md) using tweaks developed for IoT devices.
-- [Stream Raspberry Pi metrics](/streaming/README.md) to a parent host for easy access or longer-term storage.
-- [Tweak alarms](/health/QUICKSTART.md) for either Pi-hole or the health of your Raspberry Pi.
-- [Export metrics to external databases](/exporting/README.md) with the exporting engine.
+- [Optimize performance](https://github.com/netdata/netdata/blob/master/docs/guides/configure/performance.md) using tweaks developed for IoT devices.
+- [Stream Raspberry Pi metrics](https://github.com/netdata/netdata/blob/master/streaming/README.md) to a parent host for easy access or longer-term storage.
+- [Tweak alarms](https://github.com/netdata/netdata/blob/master/health/QUICKSTART.md) for either Pi-hole or the health of your Raspberry Pi.
+- [Export metrics to external databases](https://github.com/netdata/netdata/blob/master/exporting/README.md) with the exporting engine.
Or, head over to [our guides](https://learn.netdata.cloud/guides/) for even more experiments and insights into
troubleshooting the health of your systems and services.
diff --git a/docs/guides/monitor/process.md b/docs/guides/monitor/process.md
index 2f46d7ab..7cc327a0 100644
--- a/docs/guides/monitor/process.md
+++ b/docs/guides/monitor/process.md
@@ -23,38 +23,46 @@ SQL queries or know a bunch of arbitrary command-line flags.
With Netdata's process monitoring, you can:
-- Benchmark/optimize performance of standard applications, like web servers or databases
-- Benchmark/optimize performance of custom applications
-- Troubleshoot CPU/memory/disk utilization issues (why is my system's CPU spiking right now?)
-- Perform granular capacity planning based on the specific needs of your infrastructure
-- Search for leaking file descriptors
-- Investigate zombie processes
+- Benchmark/optimize performance of standard applications, like web servers or databases
+- Benchmark/optimize performance of custom applications
+- Troubleshoot CPU/memory/disk utilization issues (why is my system's CPU spiking right now?)
+- Perform granular capacity planning based on the specific needs of your infrastructure
+- Search for leaking file descriptors
+- Investigate zombie processes
... and much more. Let's get started.
## Prerequisites
-- One or more Linux nodes running [Netdata](/docs/get-started.mdx). If you need more time to understand Netdata before
- following this guide, see the [infrastructure](/docs/quickstart/infrastructure.md) or
- [single-node](/docs/quickstart/single-node.md) monitoring quickstarts.
-- A general understanding of how to [configure the Netdata Agent](/docs/configure/nodes.md) using `edit-config`.
-- A Netdata Cloud account. [Sign up](https://app.netdata.cloud) if you don't have one already.
+- One or more Linux nodes running [Netdata](https://github.com/netdata/netdata/blob/master/docs/get-started.mdx). If you
+ need more time to understand Netdata before
+ following this guide, see
+ the [infrastructure](https://github.com/netdata/netdata/blob/master/docs/quickstart/infrastructure.md) or
+ [single-node](https://github.com/netdata/netdata/blob/master/docs/quickstart/single-node.md) monitoring quickstarts.
+- A general understanding of how
+ to [configure the Netdata Agent](https://github.com/netdata/netdata/blob/master/docs/configure/nodes.md)
+ using `edit-config`.
+- A Netdata Cloud account. [Sign up](https://app.netdata.cloud) if you don't have one already.
## How does Netdata do process monitoring?
-The Netdata Agent already knows to look for hundreds of [standard applications that we support via
-collectors](/collectors/COLLECTORS.md), and groups them based on their purpose. Let's say you want to monitor a MySQL
+The Netdata Agent already knows to look for hundreds
+of [standard applications that we support via collectors](https://github.com/netdata/netdata/blob/master/collectors/COLLECTORS.md),
+and groups them based on their
+purpose. Let's say you want to monitor a MySQL
database using its process. The Netdata Agent already knows to look for processes with the string `mysqld` in their
name, along with a few others, and puts them into the `sql` group. This `sql` group then becomes a dimension in all
process-specific charts.
The process and groups settings are used by two unique and powerful collectors.
-[**`apps.plugin`**](/collectors/apps.plugin/README.md) looks at the Linux process tree every second, much like `top` or
+[**`apps.plugin`**](https://github.com/netdata/netdata/blob/master/collectors/apps.plugin/README.md) looks at the Linux
+process tree every second, much like `top` or
`ps fax`, and collects resource utilization information on every running process. It then automatically adds a layer of
meaningful visualization on top of these metrics, and creates per-process/application charts.
-[**`ebpf.plugin`**](/collectors/ebpf.plugin/README.md): Netdata's extended Berkeley Packet Filter (eBPF) collector
+[**`ebpf.plugin`**](https://github.com/netdata/netdata/blob/master/collectors/ebpf.plugin/README.md): Netdata's extended
+Berkeley Packet Filter (eBPF) collector
monitors Linux kernel-level metrics for file descriptors, virtual filesystem IO, and process management, and then hands
process-specific metrics over to `apps.plugin` for visualization. The eBPF collector also collects and visualizes
metrics on an _event frequency_, which means it captures every kernel interaction, and not just the volume of
@@ -65,55 +73,55 @@ interaction at every second in time. That's even more precise than Netdata's sta
With these collectors working in parallel, Netdata visualizes the following per-second metrics for _any_ process on your
Linux systems:
-- CPU utilization (`apps.cpu`)
- - Total CPU usage
- - User/system CPU usage (`apps.cpu_user`/`apps.cpu_system`)
-- Disk I/O
- - Physical reads/writes (`apps.preads`/`apps.pwrites`)
- - Logical reads/writes (`apps.lreads`/`apps.lwrites`)
- - Open unique files (if a file is found open multiple times, it is counted just once, `apps.files`)
-- Memory
- - Real Memory Used (non-shared, `apps.mem`)
- - Virtual Memory Allocated (`apps.vmem`)
- - Minor page faults (i.e. memory activity, `apps.minor_faults`)
-- Processes
- - Threads running (`apps.threads`)
- - Processes running (`apps.processes`)
- - Carried over uptime (since the last Netdata Agent restart, `apps.uptime`)
- - Minimum uptime (`apps.uptime_min`)
- - Average uptime (`apps.uptime_average`)
- - Maximum uptime (`apps.uptime_max`)
- - Pipes open (`apps.pipes`)
-- Swap memory
- - Swap memory used (`apps.swap`)
- - Major page faults (i.e. swap activity, `apps.major_faults`)
-- Network
- - Sockets open (`apps.sockets`)
-- eBPF file
- - Number of calls to open files. (`apps.file_open`)
- - Number of files closed. (`apps.file_closed`)
- - Number of calls to open files that returned errors.
- - Number of calls to close files that returned errors.
-- eBPF syscall
- - Number of calls to delete files. (`apps.file_deleted`)
- - Number of calls to `vfs_write`. (`apps.vfs_write_call`)
- - Number of calls to `vfs_read`. (`apps.vfs_read_call`)
- - Number of bytes written with `vfs_write`. (`apps.vfs_write_bytes`)
- - Number of bytes read with `vfs_read`. (`apps.vfs_read_bytes`)
- - Number of calls to write a file that returned errors.
- - Number of calls to read a file that returned errors.
-- eBPF process
- - Number of process created with `do_fork`. (`apps.process_create`)
- - Number of threads created with `do_fork` or `__x86_64_sys_clone`, depending on your system's kernel version. (`apps.thread_create`)
- - Number of times that a process called `do_exit`. (`apps.task_close`)
-- eBPF net
- - Number of bytes sent. (`apps.bandwidth_sent`)
- - Number of bytes received. (`apps.bandwidth_recv`)
+- CPU utilization (`apps.cpu`)
+ - Total CPU usage
+ - User/system CPU usage (`apps.cpu_user`/`apps.cpu_system`)
+- Disk I/O
+ - Physical reads/writes (`apps.preads`/`apps.pwrites`)
+ - Logical reads/writes (`apps.lreads`/`apps.lwrites`)
+ - Open unique files (if a file is found open multiple times, it is counted just once, `apps.files`)
+- Memory
+ - Real Memory Used (non-shared, `apps.mem`)
+ - Virtual Memory Allocated (`apps.vmem`)
+ - Minor page faults (i.e. memory activity, `apps.minor_faults`)
+- Processes
+ - Threads running (`apps.threads`)
+ - Processes running (`apps.processes`)
+ - Carried over uptime (since the last Netdata Agent restart, `apps.uptime`)
+ - Minimum uptime (`apps.uptime_min`)
+ - Average uptime (`apps.uptime_average`)
+ - Maximum uptime (`apps.uptime_max`)
+ - Pipes open (`apps.pipes`)
+- Swap memory
+ - Swap memory used (`apps.swap`)
+ - Major page faults (i.e. swap activity, `apps.major_faults`)
+- Network
+ - Sockets open (`apps.sockets`)
+- eBPF file
+ - Number of calls to open files. (`apps.file_open`)
+ - Number of files closed. (`apps.file_closed`)
+ - Number of calls to open files that returned errors.
+ - Number of calls to close files that returned errors.
+- eBPF syscall
+ - Number of calls to delete files. (`apps.file_deleted`)
+ - Number of calls to `vfs_write`. (`apps.vfs_write_call`)
+ - Number of calls to `vfs_read`. (`apps.vfs_read_call`)
+ - Number of bytes written with `vfs_write`. (`apps.vfs_write_bytes`)
+ - Number of bytes read with `vfs_read`. (`apps.vfs_read_bytes`)
+ - Number of calls to write a file that returned errors.
+ - Number of calls to read a file that returned errors.
+- eBPF process
+ - Number of process created with `do_fork`. (`apps.process_create`)
+ - Number of threads created with `do_fork` or `__x86_64_sys_clone`, depending on your system's kernel
+ version. (`apps.thread_create`)
+ - Number of times that a process called `do_exit`. (`apps.task_close`)
+- eBPF net
+ - Number of bytes sent. (`apps.bandwidth_sent`)
+ - Number of bytes received. (`apps.bandwidth_recv`)
As an example, here's the per-process CPU utilization chart, including a `sql` group/dimension.
-![A per-process CPU utilization chart in Netdata
-Cloud](https://user-images.githubusercontent.com/1153921/101217226-3a5d5700-363e-11eb-8610-aa1640aefb5d.png)
+![A per-process CPU utilization chart in Netdata Cloud](https://user-images.githubusercontent.com/1153921/101217226-3a5d5700-363e-11eb-8610-aa1640aefb5d.png)
## Configure the Netdata Agent to recognize a specific process
@@ -123,7 +131,8 @@ aware of hundreds of processes, and collects metrics from them automatically.
But, if you want to change the grouping behavior, add an application that isn't yet supported in the Netdata Agent, or
monitor a custom application, you need to edit the `apps_groups.conf` configuration file.
-Navigate to your [Netdata config directory](/docs/configure/nodes.md) and use `edit-config` to edit the file.
+Navigate to your [Netdata config directory](https://github.com/netdata/netdata/blob/master/docs/configure/nodes.md) and
+use `edit-config` to edit the file.
```bash
cd /etc/netdata # Replace this with your Netdata config directory if not at /etc/netdata.
@@ -138,7 +147,8 @@ others, and groups them into `sql`. That makes sense, since all these processes
sql: mysqld* mariad* postgres* postmaster* oracle_* ora_* sqlservr
```
-These groups are then reflected as [dimensions](/web/README.md#dimensions) within Netdata's charts.
+These groups are then reflected as [dimensions](https://github.com/netdata/netdata/blob/master/web/README.md#dimensions)
+within Netdata's charts.
![An example per-process CPU utilization chart in Netdata
Cloud](https://user-images.githubusercontent.com/1153921/101369156-352e2100-3865-11eb-9f0d-b8fac162e034.png)
@@ -153,12 +163,13 @@ shouldn't need to configure it to discover them.
However, if you're using multiple applications that the Netdata Agent groups together you may want to separate them for
more precise monitoring. If you're not running any other types of SQL databases on that node, you don't need to change
-the grouping, since you know that any MySQL is the only process contributing to the `sql` group.
+the grouping, since you know that any MySQL is the only process contributing to the `sql` group.
Let's say you're using both MySQL and PostgreSQL databases on a single node, and want to monitor their processes
-independently. Open the `apps_groups.conf` file as explained in the [section
-above](#configure-the-netdata-agent-to-recognize-a-specific-process) and scroll down until you find the `database
-servers` section. Create new groups for MySQL and PostgreSQL, and move their process queries into the unique groups.
+independently. Open the `apps_groups.conf` file as explained in
+the [section above](#configure-the-netdata-agent-to-recognize-a-specific-process) and scroll down until you find
+the `database servers` section. Create new groups for MySQL and PostgreSQL, and move their process queries into the
+unique groups.
```conf
# -----------------------------------------------------------------------------
@@ -169,17 +180,18 @@ postgres: postgres*
sql: mariad* postmaster* oracle_* ora_* sqlservr
```
-Restart Netdata with `sudo systemctl restart netdata`, or the [appropriate
-method](/docs/configure/start-stop-restart.md) for your system, to start collecting utilization metrics from your
-application. Time to [visualize your process metrics](#visualize-process-metrics).
+Restart Netdata with `sudo systemctl restart netdata`, or
+the [appropriate method](https://github.com/netdata/netdata/blob/master/docs/configure/start-stop-restart.md) for your system, to start collecting utilization metrics
+from your application. Time to [visualize your process metrics](#visualize-process-metrics).
### Custom applications
Let's assume you have an application that runs on the process `custom-app`. To monitor eBPF metrics for that application
separate from any others, you need to create a new group in `apps_groups.conf` and associate that process name with it.
-Open the `apps_groups.conf` file as explained in the [section
-above](#configure-the-netdata-agent-to-recognize-a-specific-process). Scroll down to `# NETDATA processes accounting`.
+Open the `apps_groups.conf` file as explained in
+the [section above](#configure-the-netdata-agent-to-recognize-a-specific-process). Scroll down
+to `# NETDATA processes accounting`.
Above that, paste in the following text, which creates a new `custom-app` group with the `custom-app` process. Replace
`custom-app` with the name of your application's Linux process. `apps_groups.conf` should now look like this:
@@ -195,26 +207,25 @@ custom-app: custom-app
...
```
-Restart Netdata with `sudo systemctl restart netdata`, or the [appropriate
-method](/docs/configure/start-stop-restart.md) for your system, to start collecting utilization metrics from your
-application.
+Restart Netdata with `sudo systemctl restart netdata`, or
+the [appropriate method](https://github.com/netdata/netdata/blob/master/docs/configure/start-stop-restart.md) for your system, to start collecting utilization metrics
+from your application.
## Visualize process metrics
Now that you're collecting metrics for your process, you'll want to visualize them using Netdata's real-time,
-interactive charts. Find these visualizations in the same section regardless of whether you use [Netdata
-Cloud](https://app.netdata.cloud) for infrastructure monitoring, or single-node monitoring with the local Agent's
-dashboard at `http://localhost:19999`.
+interactive charts. Find these visualizations in the same section regardless of whether you
+use [Netdata Cloud](https://app.netdata.cloud) for infrastructure monitoring, or single-node monitoring with the local
+Agent's dashboard at `http://localhost:19999`.
-If you need a refresher on all the available per-process charts, see the [above
-list](#per-process-metrics-and-charts-in-netdata).
+If you need a refresher on all the available per-process charts, see
+the [above list](#per-process-metrics-and-charts-in-netdata).
### Using Netdata's application collector (`apps.plugin`)
`apps.plugin` puts all of its charts under the **Applications** section of any Netdata dashboard.
-![Screenshot of the Applications section on a Netdata
-dashboard](https://user-images.githubusercontent.com/1153921/101401172-2ceadb80-388f-11eb-9e9a-88443894c272.png)
+![Screenshot of the Applications section on a Netdata dashboard](https://user-images.githubusercontent.com/1153921/101401172-2ceadb80-388f-11eb-9e9a-88443894c272.png)
Let's continue with the MySQL example. We can create a [test
database](https://www.digitalocean.com/community/tutorials/how-to-measure-mysql-query-performance-with-mysqlslap) in
@@ -223,11 +234,9 @@ MySQL to generate load on the `mysql` process.
`apps.plugin` immediately collects and visualizes this activity `apps.cpu` chart, which shows an increase in CPU
utilization from the `sql` group. There is a parallel increase in `apps.pwrites`, which visualizes writes to disk.
-![Per-application CPU utilization
-metrics](https://user-images.githubusercontent.com/1153921/101409725-8527da80-389b-11eb-96e9-9f401535aafc.png)
+![Per-application CPU utilization metrics](https://user-images.githubusercontent.com/1153921/101409725-8527da80-389b-11eb-96e9-9f401535aafc.png)
-![Per-application disk writing
-metrics](https://user-images.githubusercontent.com/1153921/101409728-85c07100-389b-11eb-83fd-d79dd1545b5a.png)
+![Per-application disk writing metrics](https://user-images.githubusercontent.com/1153921/101409728-85c07100-389b-11eb-83fd-d79dd1545b5a.png)
Next, the `mysqlslap` utility queries the database to provide some benchmarking load on the MySQL database. It won't
look exactly like a production database executing lots of user queries, but it gives you an idea into the possibility of
@@ -240,8 +249,7 @@ sudo mysqlslap --user=sysadmin --password --host=localhost --concurrency=50 --i
The following per-process disk utilization charts show spikes under the `sql` group at the same time `mysqlslap` was run
numerous times, with slightly different concurrency and query options.
-![Per-application disk
-metrics](https://user-images.githubusercontent.com/1153921/101411810-d08fb800-389e-11eb-85b3-f3fa41f1f887.png)
+![Per-application disk metrics](https://user-images.githubusercontent.com/1153921/101411810-d08fb800-389e-11eb-85b3-f3fa41f1f887.png)
> 💡 Click on any dimension below a chart in Netdata Cloud (or to the right of a chart on a local Agent dashboard), to
> visualize only that dimension. This can be particularly useful in process monitoring to separate one process'
@@ -256,8 +264,7 @@ For example, running the above workload shows the entire "story" how MySQL inter
processes/threads to handle a large number of SQL queries, then subsequently close the tasks as each query returns the
relevant data.
-![Per-process eBPF
-charts](https://user-images.githubusercontent.com/1153921/101412395-c8844800-389f-11eb-86d2-20c8a0f7b3c0.png)
+![Per-process eBPF charts](https://user-images.githubusercontent.com/1153921/101412395-c8844800-389f-11eb-86d2-20c8a0f7b3c0.png)
`ebpf.plugin` visualizes additional eBPF metrics, which are system-wide and not per-process, under the **eBPF** section.
@@ -267,35 +274,39 @@ Now that you have `apps_groups.conf` configured correctly, and know where to fin
Netdata's ecosystem, you can precisely monitor the health and performance of any process on your node using per-second
metrics.
-For even more in-depth troubleshooting, see our guide on [monitoring and debugging applications with
-eBPF](/docs/guides/troubleshoot/monitor-debug-applications-ebpf.md).
+For even more in-depth troubleshooting, see our guide
+on [monitoring and debugging applications with eBPF](https://github.com/netdata/netdata/blob/master/docs/guides/troubleshoot/monitor-debug-applications-ebpf.md).
-If the process you're monitoring also has a [supported collector](/collectors/COLLECTORS.md), now is a great time to set
+If the process you're monitoring also has
+a [supported collector](https://github.com/netdata/netdata/blob/master/collectors/COLLECTORS.md), now is a great time to
+set
that up if it wasn't autodetected. With both process utilization and application-specific metrics, you should have every
-piece of data needed to discover the root cause of an incident. See our [collector
-setup](/docs/collect/enable-configure.md) doc for details.
+piece of data needed to discover the root cause of an incident. See
+our [collector setup](https://github.com/netdata/netdata/blob/master/docs/collect/enable-configure.md) doc for details.
-[Create new dashboards](/docs/visualize/create-dashboards.md) in Netdata Cloud using charts from `apps.plugin`,
+[Create new dashboards](https://github.com/netdata/netdata/blob/master/docs/visualize/create-dashboards.md) in Netdata
+Cloud using charts from `apps.plugin`,
`ebpf.plugin`, and application-specific collectors to build targeted dashboards for monitoring key processes across your
infrastructure.
-Try running [Metric Correlations](https://learn.netdata.cloud/docs/cloud/insights/metric-correlations) on a node that's
-running the process(es) you're monitoring. Even if nothing is going wrong at the moment, Netdata Cloud's embedded
-intelligence helps you better understand how a MySQL database, for example, might influence a system's volume of memory
-page faults. And when an incident is afoot, use Metric Correlations to reduce mean time to resolution (MTTR) and
-cognitive load.
-
-If you want more specific metrics from your custom application, check out Netdata's [statsd
-support](/collectors/statsd.plugin/README.md). With statd, you can send detailed metrics from your application to
-Netdata and visualize them with per-second granularity. Netdata's statsd collector works with dozens of [statsd server
-implementations](https://github.com/etsy/statsd/wiki#client-implementations), which work with most application
+Try
+running [Metric Correlations](https://github.com/netdata/netdata/blob/master/docs/cloud/insights/metric-correlations.md)
+on a node that's running the process(es) you're monitoring. Even if nothing is going wrong at the moment, Netdata
+Cloud's embedded intelligence helps you better understand how a MySQL database, for example, might influence a system's
+volume of memory page faults. And when an incident is afoot, use Metric Correlations to reduce mean time to resolution (
+MTTR) and cognitive load.
+
+If you want more specific metrics from your custom application, check out
+Netdata's [statsd support](https://github.com/netdata/netdata/blob/master/collectors/statsd.plugin/README.md). With statd, you can send detailed metrics from your
+application to Netdata and visualize them with per-second granularity. Netdata's statsd collector works with dozens of
+[statsd server implementations](https://github.com/etsy/statsd/wiki#client-implementations), which work with most application
frameworks.
### Related reference documentation
-- [Netdata Agent · `apps.plugin`](/collectors/apps.plugin/README.md)
-- [Netdata Agent · `ebpf.plugin`](/collectors/ebpf.plugin/README.md)
-- [Netdata Agent · Dashboards](/web/README.md#dimensions)
-- [Netdata Agent · MySQL collector](https://learn.netdata.cloud/docs/agent/collectors/go.d.plugin/modules/mysql)
+- [Netdata Agent · `apps.plugin`](https://github.com/netdata/netdata/blob/master/collectors/apps.plugin/README.md)
+- [Netdata Agent · `ebpf.plugin`](https://github.com/netdata/netdata/blob/master/collectors/ebpf.plugin/README.md)
+- [Netdata Agent · Dashboards](https://github.com/netdata/netdata/blob/master/web/README.md#dimensions)
+- [Netdata Agent · MySQL collector](https://github.com/netdata/go.d.plugin/blob/master/modules/mysql/README.md)
diff --git a/docs/guides/monitor/raspberry-pi-anomaly-detection.md b/docs/guides/monitor/raspberry-pi-anomaly-detection.md
index 73f57cd0..00b652bf 100644
--- a/docs/guides/monitor/raspberry-pi-anomaly-detection.md
+++ b/docs/guides/monitor/raspberry-pi-anomaly-detection.md
@@ -12,7 +12,7 @@ We love IoT and edge at Netdata, we also love machine learning. Even better if w
of monitoring increasingly complex systems.
We recently explored what might be involved in enabling our Python-based [anomalies
-collector](/collectors/python.d.plugin/anomalies/README.md) on a Raspberry Pi. To our delight, it's actually quite
+collector](https://github.com/netdata/netdata/blob/master/collectors/python.d.plugin/anomalies/README.md) on a Raspberry Pi. To our delight, it's actually quite
straightforward!
Read on to learn all the steps and enable unsupervised anomaly detection on your on Raspberry Pi(s).
@@ -23,14 +23,14 @@ Read on to learn all the steps and enable unsupervised anomaly detection on your
- A Raspberry Pi running Raspbian, which we'll call a _node_.
- The [open-source Netdata](https://github.com/netdata/netdata) monitoring agent. If you don't have it installed on your
- node yet, [get started now](/docs/get-started.mdx).
+ node yet, [get started now](https://github.com/netdata/netdata/blob/master/docs/get-started.mdx).
## Install dependencies
First make sure Netdata is using Python 3 when it runs Python-based data collectors.
-Next, open `netdata.conf` using [`edit-config`](/docs/configure/nodes.md#use-edit-config-to-edit-configuration-files)
-from within the [Netdata config directory](/docs/configure/nodes.md#the-netdata-config-directory). Scroll down to the
+Next, open `netdata.conf` using [`edit-config`](https://github.com/netdata/netdata/blob/master/docs/configure/nodes.md#use-edit-config-to-edit-configuration-files)
+from within the [Netdata config directory](https://github.com/netdata/netdata/blob/master/docs/configure/nodes.md#the-netdata-config-directory). Scroll down to the
`[plugin:python.d]` section to pass in the `-ppython3` command option.
```conf
@@ -59,7 +59,7 @@ LLVM_CONFIG=llvm-config-9 pip3 install --user llvmlite numpy==1.20.1 netdata-pan
## Enable the anomalies collector
-Now you're ready to enable the collector and [restart Netdata](/docs/configure/start-stop-restart.md).
+Now you're ready to enable the collector and [restart Netdata](https://github.com/netdata/netdata/blob/master/docs/configure/start-stop-restart.md).
```bash
sudo ./edit-config python.d.conf
@@ -82,7 +82,7 @@ centralized cloud somewhere) is the resource utilization impact of running a mon
With the default configuration, the anomalies collector uses about 6.5% of CPU at each run. During the retraining step,
CPU utilization jumps to between 20-30% for a few seconds, but you can [configure
-retraining](/collectors/python.d.plugin/anomalies/README.md#configuration) to happen less often if you wish.
+retraining](https://github.com/netdata/netdata/blob/master/collectors/python.d.plugin/anomalies/README.md#configuration) to happen less often if you wish.
![CPU utilization of anomaly detection on the Raspberry
Pi](https://user-images.githubusercontent.com/1153921/110149718-9d749c00-7d9b-11eb-9af8-46e2032cd1d0.png)
@@ -108,18 +108,18 @@ looks like a potentially useful addition to enable unsupervised anomaly detectio
See our two-part guide series for a more complete picture of configuring the anomalies collector, plus some best
practices on using the charts it automatically generates:
-- [_Detect anomalies in systems and applications_](/docs/guides/monitor/anomaly-detection-python.md)
-- [_Monitor and visualize anomalies with Netdata_](/docs/guides/monitor/visualize-monitor-anomalies.md)
+- [_Detect anomalies in systems and applications_](https://github.com/netdata/netdata/blob/master/docs/guides/monitor/anomaly-detection-python.md)
+- [_Monitor and visualize anomalies with Netdata_](https://github.com/netdata/netdata/blob/master/docs/guides/monitor/visualize-monitor-anomalies.md)
If you're using your Raspberry Pi for other purposes, like blocking ads/trackers with Pi-hole, check out our companions
-Pi guide: [_Monitor Pi-hole (and a Raspberry Pi) with Netdata_](/docs/guides/monitor/pi-hole-raspberry-pi.md).
+Pi guide: [_Monitor Pi-hole (and a Raspberry Pi) with Netdata_](https://github.com/netdata/netdata/blob/master/docs/guides/monitor/pi-hole-raspberry-pi.md).
Once you've had a chance to give unsupervised anomaly detection a go, share your use cases and let us know of any
feedback on our [community forum](https://community.netdata.cloud/t/anomalies-collector-feedback-megathread/767).
### Related reference documentation
-- [Netdata Agent · Get Netdata](/docs/get-started.mdx)
-- [Netdata Agent · Anomalies collector](/collectors/python.d.plugin/anomalies/README.md)
+- [Netdata Agent · Get Netdata](https://github.com/netdata/netdata/blob/master/docs/get-started.mdx)
+- [Netdata Agent · Anomalies collector](https://github.com/netdata/netdata/blob/master/collectors/python.d.plugin/anomalies/README.md)
diff --git a/docs/guides/monitor/statsd.md b/docs/guides/monitor/statsd.md
index 3e2f0f85..848e2649 100644
--- a/docs/guides/monitor/statsd.md
+++ b/docs/guides/monitor/statsd.md
@@ -22,7 +22,7 @@ In general, the process for creating a StatsD collector can be summarized in 2 s
- Run an experiment by sending StatsD metrics to Netdata, without any prior configuration. This will create a chart per metric (called private charts) and will help you verify that everything works as expected from the application side of things.
- Make sure to reload the dashboard tab **after** you start sending data to Netdata.
-- Create a configuration file for your app using [edit-config](/docs/configure/nodes.md): `sudo ./edit-config
+- Create a configuration file for your app using [edit-config](https://github.com/netdata/netdata/blob/master/docs/configure/nodes.md): `sudo ./edit-config
statsd.d/myapp.conf`
- Each app will have it's own section in the right-hand menu.
@@ -30,7 +30,7 @@ Now, let's see the above process in detail.
## Prerequisites
-- A node with the [Netdata](/docs/get-started.mdx) installed.
+- A node with the [Netdata](https://github.com/netdata/netdata/blob/master/docs/get-started.mdx) installed.
- An application to instrument. For this guide, that will be [k6](https://k6.io/docs/getting-started/installation).
## Understanding the metrics
@@ -63,7 +63,7 @@ Here are some examples of default private charts. You can see that the histogram
## Create a new StatsD configuration file
-Start by creating a new configuration file under the `statsd.d/` folder in the [Netdata config directory](/docs/configure/nodes.md#the-netdata-config-directory). Use [`edit-config`](/docs/configure/nodes.md#use-edit-config-to-edit-configuration-files) to create a new file called `k6.conf`.
+Start by creating a new configuration file under the `statsd.d/` folder in the [Netdata config directory](https://github.com/netdata/netdata/blob/master/docs/configure/nodes.md#the-netdata-config-directory). Use [`edit-config`](https://github.com/netdata/netdata/blob/master/docs/configure/nodes.md#use-edit-config-to-edit-configuration-files) to create a new file called `k6.conf`.
```bash=
sudo ./edit-config statsd.d/k6.conf
@@ -104,7 +104,7 @@ Families and context are additional ways to group metrics. Families control the
Context is a second way to group metrics, when the metrics are of the same nature but different origin. In our case, if we ran several different load testing experiments side-by-side, we could define the same app, but different context (e.g `http_requests.experiment1`, `http_requests.experiment2`).
-Find more details about family and context in our [documentation](/web/README.md#families).
+Find more details about family and context in our [documentation](https://github.com/netdata/netdata/blob/master/web/README.md#families).
### Dimension
@@ -115,7 +115,7 @@ Now, having decided on how we are going to group the charts, we need to define h
The dimension option has this syntax: `dimension = [pattern] METRIC NAME TYPE MULTIPLIER DIVIDER OPTIONS`
-- **pattern**: A keyword that tells the StatsD server the `METRIC` string is actually a [simple pattern].(/libnetdata/simple_pattern/README.md). We don't simple patterns in the example, but if we wanted to visualize all the `http_req` metrics, we could have a single dimension: `dimension = pattern 'k6.http_req*' last 1 1`. Find detailed examples with patterns in our [documentation](/collectors/statsd.plugin/README.md#dimension-patterns).
+- **pattern**: A keyword that tells the StatsD server the `METRIC` string is actually a [simple pattern].(/libnetdata/simple_pattern/README.md). We don't simple patterns in the example, but if we wanted to visualize all the `http_req` metrics, we could have a single dimension: `dimension = pattern 'k6.http_req*' last 1 1`. Find detailed examples with patterns in our [documentation](https://github.com/netdata/netdata/blob/master/collectors/statsd.plugin/README.md#dimension-patterns).
- **METRIC** The id of the metric as it comes from the client. You can easily find this in the private charts above, for example: `k6.http_req_connecting`.
- **NAME**: The name of the dimension. You can use the dictionary to expand this to something more human-readable.
- **TYPE**:
@@ -212,7 +212,7 @@ Following the above steps, we append to the `k6.conf` that we defined above, the
> Take note that Netdata will report the rate for metrics and counters, even if k6 or another application sends an _absolute_ number. For example, k6 sends absolute HTTP requests with `http_reqs`, but Netdat visualizes that in `requests/second`.
-To enable this StatsD configuration, [restart Netdata](/docs/configure/start-stop-restart.md).
+To enable this StatsD configuration, [restart Netdata](https://github.com/netdata/netdata/blob/master/docs/configure/start-stop-restart.md).
## Final touches
@@ -293,6 +293,6 @@ Netdata allows you easily visualize any StatsD metric without any configuration,
### Related reference documentation
-- [Netdata Agent · StatsD](/collectors/statsd.plugin/README.md)
+- [Netdata Agent · StatsD](https://github.com/netdata/netdata/blob/master/collectors/statsd.plugin/README.md)
diff --git a/docs/guides/monitor/stop-notifications-alarms.md b/docs/guides/monitor/stop-notifications-alarms.md
index a8b73a86..3c026a89 100644
--- a/docs/guides/monitor/stop-notifications-alarms.md
+++ b/docs/guides/monitor/stop-notifications-alarms.md
@@ -13,7 +13,7 @@ relevant if you run Netdata on your laptop or a small virtual server. If they're
to real issues with health and performance.
Silencing individual alarms is an excellent solution for situations where you're not interested in seeing a specific
-alarm but don't want to disable a [notification system](/health/notifications/README.md) entirely.
+alarm but don't want to disable a [notification system](https://github.com/netdata/netdata/blob/master/health/notifications/README.md) entirely.
## Find the alarm configuration file
@@ -34,7 +34,7 @@ In the `source` row, you see that this chart is getting its configuration from
the file you need to edit if you want to silence this alarm.
For more information about editing or referencing health configuration files on your system, see the [health
-quickstart](/health/QUICKSTART.md#edit-health-configuration-files).
+quickstart](https://github.com/netdata/netdata/blob/master/health/QUICKSTART.md#edit-health-configuration-files).
## Edit the file to enable silencing
@@ -70,7 +70,7 @@ To silence this alarm, change `sysadmin` to `silent`.
to: silent
```
-Use one of the available [methods](/health/QUICKSTART.md#reload-health-configuration) to reload your health configuration
+Use one of the available [methods](https://github.com/netdata/netdata/blob/master/health/QUICKSTART.md#reload-health-configuration) to reload your health configuration
and ensure you get no more notifications about that alarm**.
You can add `to: silent` to any alarm you'd rather not bother you with notifications.
@@ -80,12 +80,12 @@ You can add `to: silent` to any alarm you'd rather not bother you with notificat
You should now know the fundamentals behind silencing any individual alarm in Netdata.
To learn about _all_ of Netdata's health configuration possibilities, visit the [health reference
-guide](/health/REFERENCE.md), or check out other [tutorials on health monitoring](/health/README.md#guides).
+guide](https://github.com/netdata/netdata/blob/master/health/REFERENCE.md), or check out other [tutorials on health monitoring](https://github.com/netdata/netdata/blob/master/health/README.md#guides).
Or, take better control over how you get notified about alarms via the [notification
-system](/health/notifications/README.md).
+system](https://github.com/netdata/netdata/blob/master/health/notifications/README.md).
-You can also use Netdata's [Health Management API](/web/api/health/README.md#health-management-api) to control health
+You can also use Netdata's [Health Management API](https://github.com/netdata/netdata/blob/master/web/api/health/README.md#health-management-api) to control health
checks and notifications while Netdata runs. With this API, you can disable health checks during a maintenance window or
backup process, for example.
diff --git a/docs/guides/monitor/visualize-monitor-anomalies.md b/docs/guides/monitor/visualize-monitor-anomalies.md
index 1f8c2c8f..90ce20a4 100644
--- a/docs/guides/monitor/visualize-monitor-anomalies.md
+++ b/docs/guides/monitor/visualize-monitor-anomalies.md
@@ -10,7 +10,7 @@ custom_edit_url: https://github.com/netdata/netdata/edit/master/docs/guides/moni
Welcome to part 2 of our series of guides on using _unsupervised anomaly detection_ to detect issues with your systems,
containers, and applications using the open-source Netdata Agent. For an introduction to detecting anomalies and
-monitoring associated metrics, see [part 1](/docs/guides/monitor/anomaly-detection-python.md), which covers prerequisites and
+monitoring associated metrics, see [part 1](https://github.com/netdata/netdata/blob/master/docs/guides/monitor/anomaly-detection-python.md), which covers prerequisites and
configuration basics.
With anomaly detection in the Netdata Agent set up, you will now want to visualize and monitor which charts have
@@ -48,8 +48,8 @@ analysis (RCA).
The anomalies collector creates two "classes" of alarms for each chart captured by the `charts_regex` setting. All these
alarms are preconfigured based on your [configuration in
-`anomalies.conf`](/docs/guides/monitor/anomaly-detection-python.md#configure-the-anomalies-collector). With the `charts_regex`
-and `charts_to_exclude` settings from [part 1](/docs/guides/monitor/anomaly-detection-python.md) of this guide series, the
+`anomalies.conf`](https://github.com/netdata/netdata/blob/master/docs/guides/monitor/anomaly-detection-python.md#configure-the-anomalies-collector). With the `charts_regex`
+and `charts_to_exclude` settings from [part 1](https://github.com/netdata/netdata/blob/master/docs/guides/monitor/anomaly-detection-python.md) of this guide series, the
Netdata Agent creates 32 alarms driven by unsupervised anomaly detection.
The first class triggers warning alarms when the average anomaly probability for a given chart has stayed above 50% for
@@ -69,17 +69,17 @@ there's a full-blown incident, depending on what application/service you're usin
further investigation.
As you use the anomalies collector, you may find that the default settings provide too many or too few genuine alarms.
-In this case, [configure the alarm](/docs/monitor/configure-alarms.md) with `sudo ./edit-config
+In this case, [configure the alarm](https://github.com/netdata/netdata/blob/master/docs/monitor/configure-alarms.md) with `sudo ./edit-config
health.d/anomalies.conf`. Take a look at the `lookup` line syntax in the [health
-reference](/health/REFERENCE.md#alarm-line-lookup) to understand how the anomalies collector automatically creates
+reference](https://github.com/netdata/netdata/blob/master/health/REFERENCE.md#alarm-line-lookup) to understand how the anomalies collector automatically creates
alarms for any dimension on the `anomalies_local.probability` and `anomalies_local.anomaly` charts.
## Visualize anomalies in charts
In either [Netdata Cloud](https://app.netdata.cloud) or the local Agent dashboard at `http://NODE:19999`, click on the
-**Anomalies** [section](/web/gui/README.md#sections) to see the pair of anomaly detection charts, which are
+**Anomalies** [section](https://github.com/netdata/netdata/blob/master/web/gui/README.md#sections) to see the pair of anomaly detection charts, which are
preconfigured to visualize per-second anomaly metrics based on your [configuration in
-`anomalies.conf`](/docs/guides/monitor/anomaly-detection-python.md#configure-the-anomalies-collector).
+`anomalies.conf`](https://github.com/netdata/netdata/blob/master/docs/guides/monitor/anomaly-detection-python.md#configure-the-anomalies-collector).
These charts have the contexts `anomalies.probability` and `anomalies.anomaly`. Together, these charts
create meaningful visualizations for immediately recognizing not only that something is going wrong on your node, but
@@ -88,7 +88,7 @@ give context as to where to look next.
The `anomalies_local.probability` chart shows the probability that the latest observed data is anomalous, based on the
trained model. The `anomalies_local.anomaly` chart visualizes 0&rarr;1 predictions based on whether the latest observed
data is anomalous based on the trained model. Both charts share the same dimensions, which you configured via
-`charts_regex` and `charts_to_exclude` in [part 1](/docs/guides/monitor/anomaly-detection-python.md).
+`charts_regex` and `charts_to_exclude` in [part 1](https://github.com/netdata/netdata/blob/master/docs/guides/monitor/anomaly-detection-python.md).
In other words, the `probability` chart shows the amplitude of the anomaly, whereas the `anomaly` chart provides quick
yes/no context.
@@ -108,7 +108,7 @@ dimensions that immediately shot to 100% anomaly probability, and remained there
## Build an anomaly detection dashboard
[Netdata Cloud](https://app.netdata.cloud) features a drag-and-drop [dashboard
-editor](/docs/visualize/create-dashboards.md) that helps you create entirely new dashboards with charts targeted for
+editor](https://github.com/netdata/netdata/blob/master/docs/visualize/create-dashboards.md) that helps you create entirely new dashboards with charts targeted for
your specific applications.
For example, here's a dashboard designed for visualizing anomalies present in an Nginx web server, including
@@ -119,12 +119,12 @@ dashboard](https://user-images.githubusercontent.com/1153921/104226915-c6188f00-
Use the anomaly charts for instant visual identification of potential anomalies, and then Nginx-specific charts, in the
right column, to validate whether the probability and anomaly counters are showing a valid incident worth further
-investigation using [Metric Correlations](https://learn.netdata.cloud/docs/cloud/insights/metric-correlations) to narrow
+investigation using [Metric Correlations](https://github.com/netdata/netdata/blob/master/docs/cloud/insights/metric-correlations.md) to narrow
the dashboard into only the charts relevant to what you're seeing from the anomalies collector.
## What's next?
-Between this guide and [part 1](/docs/guides/monitor/anomaly-detection-python.md), which covered setup and configuration, you
+Between this guide and [part 1](https://github.com/netdata/netdata/blob/master/docs/guides/monitor/anomaly-detection-python.md), which covered setup and configuration, you
now have a fundamental understanding of how unsupervised anomaly detection in Netdata works, from root cause to alarms
to preconfigured or custom dashboards.
@@ -132,11 +132,11 @@ We'd love to hear your feedback on the anomalies collector. Hop over to the [com
forum](https://community.netdata.cloud/t/anomalies-collector-feedback-megathread/767), and let us know if you're already getting value from
unsupervised anomaly detection, or would like to see something added to it. You might even post a custom configuration
that works well for monitoring some other popular application, like MySQL, PostgreSQL, Redis, or anything else we
-[support through collectors](/collectors/COLLECTORS.md).
+[support through collectors](https://github.com/netdata/netdata/blob/master/collectors/COLLECTORS.md).
### Related reference documentation
-- [Netdata Agent · Anomalies collector](/collectors/python.d.plugin/anomalies/README.md)
-- [Netdata Cloud · Build new dashboards](https://learn.netdata.cloud/docs/cloud/visualize/dashboards)
+- [Netdata Agent · Anomalies collector](https://github.com/netdata/netdata/blob/master/collectors/python.d.plugin/anomalies/README.md)
+- [Netdata Cloud · Build new dashboards](https://github.com/netdata/netdata/blob/master/docs/cloud/visualize/dashboards.md)
diff --git a/docs/guides/python-collector.md b/docs/guides/python-collector.md
index 920b9b9e..e0e7a604 100644
--- a/docs/guides/python-collector.md
+++ b/docs/guides/python-collector.md
@@ -10,9 +10,9 @@ custom_edit_url: https://github.com/netdata/netdata/edit/master/docs/guides/pyth
# Develop a custom data collector in Python
-The Netdata Agent uses [data collectors](/docs/collect/how-collectors-work.md) to fetch metrics from hundreds of system,
+The Netdata Agent uses [data collectors](https://github.com/netdata/netdata/blob/master/docs/collect/how-collectors-work.md) to fetch metrics from hundreds of system,
container, and service endpoints. While the Netdata team and community has built [powerful
-collectors](/collectors/COLLECTORS.md) for most system, container, and service/application endpoints, there are plenty
+collectors](https://github.com/netdata/netdata/blob/master/collectors/COLLECTORS.md) for most system, container, and service/application endpoints, there are plenty
of custom applications that can't be monitored by default.
## Problem
@@ -29,7 +29,7 @@ covered here, or use the included examples for collecting and organizing either
## What you need to get started
- A physical or virtual Linux system, which we'll call a _node_.
-- A working installation of the free and open-source [Netdata](/docs/get-started.mdx) monitoring agent.
+- A working installation of the free and open-source [Netdata](https://github.com/netdata/netdata/blob/master/docs/get-started.mdx) monitoring agent.
## Jobs and elements of a Python collector
@@ -90,7 +90,7 @@ context, charttype]`, where:
that is `A.B`, with `A` being the name of the collector, and `B` being the name of the specific metric.
- `charttype`: Either `line`, `area`, or `stacked`. If null line is the default value.
-You can read more about `family` and `context` in the [web dashboard](/web/README.md#families) doc.
+You can read more about `family` and `context` in the [web dashboard](https://github.com/netdata/netdata/blob/master/web/README.md#families) doc.
Once the chart has been defined, you should define the dimensions of the chart. Dimensions are basically the metrics to
be represented in this chart and each chart can have more than one dimension. In order to define the dimensions, the
@@ -166,7 +166,7 @@ class Service(UrlService):
In our use-case, we use the `SimpleService` framework, since there is no framework class that suits our needs.
-You can read more about the [framework classes](/collectors/python.d.plugin/README.md#how-to-write-a-new-module) from
+You can read more about the [framework classes](https://github.com/netdata/netdata/blob/master/collectors/python.d.plugin/README.md#how-to-write-a-new-module) from
the Netdata documentation.
## An example collector using weather station data
@@ -348,7 +348,7 @@ ORDER = [
]
```
-[Restart Netdata](/docs/configure/start-stop-restart.md) with `sudo systemctl restart netdata` to see the new humidity
+[Restart Netdata](https://github.com/netdata/netdata/blob/master/docs/configure/start-stop-restart.md) with `sudo systemctl restart netdata` to see the new humidity
chart:
![A snapshot of the modified chart](https://i.imgur.com/XOeCBmg.png)
@@ -405,7 +405,7 @@ ORDER = [
]
```
-[Restart Netdata](/docs/configure/start-stop-restart.md) with `sudo systemctl restart netdata` to see the new
+[Restart Netdata](https://github.com/netdata/netdata/blob/master/docs/configure/start-stop-restart.md) with `sudo systemctl restart netdata` to see the new
min/max/average temperature chart with multiple dimensions:
![A snapshot of the modified chart](https://i.imgur.com/g7E8lnG.png)
@@ -459,7 +459,7 @@ variables and inform the user about the defaults. For example, take a look at th
[GitHub](https://github.com/netdata/netdata/blob/master/collectors/python.d.plugin/example/example.conf).
You can read more about the configuration file on the [`python.d.plugin`
-documentation](https://learn.netdata.cloud/docs/agent/collectors/python.d.plugin).
+documentation](https://github.com/netdata/netdata/blob/master/collectors/python.d.plugin/README.md).
## What's next?
@@ -470,7 +470,7 @@ Now you are ready to start developing our Netdata python Collector and share it
- If you need help while developing your collector, join our [Netdata
Community](https://community.netdata.cloud/c/agent-development/9) to chat about it.
- Follow the
- [checklist](https://learn.netdata.cloud/docs/agent/collectors/python.d.plugin#pull-request-checklist-for-python-plugins)
+ [checklist](https://github.com/netdata/netdata/blob/master/collectors/python.d.plugin/README.md#pull-request-checklist-for-python-plugins)
to contribute the collector to the Netdata Agent [repository](https://github.com/netdata/netdata).
- Check out the [example](https://github.com/netdata/netdata/tree/master/collectors/python.d.plugin/example) Python
collector, which is a minimal example collector you could also use as a starting point. Once comfortable with that,
diff --git a/docs/guides/step-by-step/step-00.md b/docs/guides/step-by-step/step-00.md
index 9f0fecac..2f83ee9b 100644
--- a/docs/guides/step-by-step/step-00.md
+++ b/docs/guides/step-by-step/step-00.md
@@ -18,7 +18,7 @@ completely new to Netdata, or have never tried health monitoring/performance tro
guide is perfect for you.
If you have monitoring experience, or would rather get straight into configuring Netdata to your needs, you can jump
-straight into code and configurations with our [getting started guide](/docs/get-started.mdx).
+straight into code and configurations with our [getting started guide](https://github.com/netdata/netdata/blob/master/docs/get-started.mdx).
> This guide contains instructions for Netdata installed on a Linux system. Many of the instructions will work on
> other supported operating systems, like FreeBSD and macOS, but we can't make any guarantees.
@@ -44,7 +44,7 @@ The easiest way to install Netdata on a Linux system is our `kickstart.sh` one-l
and let it take care of the rest.
This script will install Netdata from source, keep it up to date with nightly releases, connects to the Netdata
-[registry](/registry/README.md), and sends [_anonymous statistics_](/docs/anonymous-statistics.md) about how you use
+[registry](https://github.com/netdata/netdata/blob/master/registry/README.md), and sends [_anonymous statistics_](https://github.com/netdata/netdata/blob/master/docs/anonymous-statistics.md) about how you use
Netdata. We use this information to better understand how we can improve the Netdata experience for all our users.
To install Netdata, run the following as your normal user:
@@ -60,7 +60,7 @@ Once finished, you'll have Netdata installed, and you'll be set up to get _night
improvements, and bugfixes.
If this method doesn't work for you, or you want to use a different process, visit our [installation
-documentation](/packaging/installer/README.md) for details.
+documentation](https://github.com/netdata/netdata/blob/master/packaging/installer/README.md) for details.
## Netdata fundamentals
diff --git a/docs/guides/step-by-step/step-01.md b/docs/guides/step-by-step/step-01.md
index f5430e3a..e60bb076 100644
--- a/docs/guides/step-by-step/step-01.md
+++ b/docs/guides/step-by-step/step-01.md
@@ -139,7 +139,7 @@ easy!
We'll cover this quickly, as you're probably eager to get on with using Netdata itself.
We don't want to lock you in to using Netdata by itself, and forever. By supporting [archiving to
-external databases](/exporting/README.md) like Graphite, Prometheus, OpenTSDB, MongoDB, and others, you can use Netdata _in
+external databases](https://github.com/netdata/netdata/blob/master/exporting/README.md) like Graphite, Prometheus, OpenTSDB, MongoDB, and others, you can use Netdata _in
conjunction_ with software that might seem like our competitors.
We don't want to "wage war" with another monitoring solution, whether it's commercial, open-source, or anything in
diff --git a/docs/guides/step-by-step/step-02.md b/docs/guides/step-by-step/step-02.md
index 4b802ffd..535f3cfa 100644
--- a/docs/guides/step-by-step/step-02.md
+++ b/docs/guides/step-by-step/step-02.md
@@ -11,7 +11,7 @@ working with the dashboard directly.
This step-by-step guide assumes you've already installed Netdata on a system of yours. If you haven't yet, hop back over
to ["step 0"](step-00.md#before-we-get-started) for information about our one-line installer script. Or, view the
-[installation docs](/packaging/installer/README.md) to learn more. Once you have Netdata installed, you can hop back
+[installation docs](https://github.com/netdata/netdata/blob/master/packaging/installer/README.md) to learn more. Once you have Netdata installed, you can hop back
over here and dig in.
## What you'll learn in this step
@@ -56,7 +56,7 @@ what it's collecting. If you run Netdata on many different systems using differe
menus and submenus may look a little different for each one.
To learn more about menus, see our documentation about [navigating the standard
-dashboard](/web/gui/README.md#metrics-menus).
+dashboard](https://github.com/netdata/netdata/blob/master/web/gui/README.md#metrics-menus).
> ❗ By default, Netdata only creates and displays charts if the metrics are _not zero_. So, you may be missing some
> charts, menus, and submenus if those charts have zero metrics. You can change this by changing the **Which dimensions
@@ -106,7 +106,7 @@ looking at its name or hovering over the chart's date.
It's important to understand these differences, as Netdata uses charts, dimensions, families, and contexts to create
health alarms and configure collectors. To read even more about the differences between all these elements of the
dashboard, and how they affect other parts of Netdata, read our [dashboards
-documentation](/web/README.md#charts-contexts-families).
+documentation](https://github.com/netdata/netdata/blob/master/web/README.md#charts-contexts-families).
## Interact with charts
@@ -148,7 +148,7 @@ chart to its original height, double-click the same icon.
![Animated GIF of resizing a chart and resetting it to the default
height](https://user-images.githubusercontent.com/1153921/80842459-7d41e280-8bb6-11ea-9488-1bc29f94d7f2.gif)
-To learn more about other options and chart interactivity, read our [dashboard documentation](/web/README.md).
+To learn more about other options and chart interactivity, read our [dashboard documentation](https://github.com/netdata/netdata/blob/master/web/README.md).
## See raised alarms and the alarm log
diff --git a/docs/guides/step-by-step/step-03.md b/docs/guides/step-by-step/step-03.md
index c1d283ba..3204765b 100644
--- a/docs/guides/step-by-step/step-03.md
+++ b/docs/guides/step-by-step/step-03.md
@@ -14,7 +14,7 @@ You might be thinking, "So, now I have to remember all these IP addresses, and t
manually, to move from one system to another? Maybe I should just make a bunch of bookmarks. What's a few more tabs
on top of the hundred I have already?"
-We get it. That's why we built [Netdata Cloud](https://learn.netdata.cloud/docs/cloud/), which connects many distributed
+We get it. That's why we built [Netdata Cloud](https://github.com/netdata/netdata/blob/master/docs/cloud/cloud.mdx), which connects many distributed
agents for a seamless experience when monitoring an entire infrastructure of Netdata-monitored nodes.
![Animated GIF of Netdata
@@ -24,13 +24,16 @@ Cloud](https://user-images.githubusercontent.com/1153921/80828986-1ebb3b00-8b9b-
In this step of the Netdata guide, we'll talk about the following:
-- [Why you should use Netdata Cloud](#why-use-netdata-cloud)
-- [Get started with Netdata Cloud](#get-started-with-netdata-cloud)
-- [Navigate between dashboards with Visited Nodes](#navigate-between-dashboards-with-visited-nodes)
+- [Step 3. Monitor more than one system with Netdata](#step-3-monitor-more-than-one-system-with-netdata)
+ - [What you'll learn in this step](#what-youll-learn-in-this-step)
+ - [Why use Netdata Cloud?](#why-use-netdata-cloud)
+ - [Get started with Netdata Cloud](#get-started-with-netdata-cloud)
+ - [Navigate between dashboards with Visited Nodes](#navigate-between-dashboards-with-visited-nodes)
+ - [What's next?](#whats-next)
## Why use Netdata Cloud?
-Our [Cloud documentation](https://learn.netdata.cloud/docs/cloud/) does a good job (we think!) of explaining why Cloud
+Our [Cloud documentation](https://github.com/netdata/netdata/blob/master/docs/cloud/cloud.mdx) does a good job (we think!) of explaining why Cloud
gives you a ton of value at no cost:
> Netdata Cloud gives you real-time visibility for your entire infrastructure. With Netdata Cloud, you can run all your
@@ -44,7 +47,7 @@ features, new collectors for more applications, and improved UI, so will Cloud.
## Get started with Netdata Cloud
Signing in, onboarding, and connecting your first nodes only takes a few minutes, and we have a [Get started with
-Cloud](https://learn.netdata.cloud/docs/cloud/get-started) guide to help you walk through every step.
+Cloud](https://github.com/netdata/netdata/blob/master/docs/cloud/cloud.mdx) guide to help you walk through every step.
Or, if you're feeling confident, dive right in.
diff --git a/docs/guides/step-by-step/step-04.md b/docs/guides/step-by-step/step-04.md
index 37b4245b..fcd84ce6 100644
--- a/docs/guides/step-by-step/step-04.md
+++ b/docs/guides/step-by-step/step-04.md
@@ -43,7 +43,7 @@ In the system represented by the screenshot, the line reads: `config directory =
`netdata.conf`, and all the other configuration files, can be found at `/etc/netdata`.
> For more details on where your Netdata config directory is, take a look at our [installation
-> instructions](/packaging/installer/README.md).
+> instructions](https://github.com/netdata/netdata/blob/master/packaging/installer/README.md).
For the rest of this guide, we'll assume you're editing files or running scripts from _within_ your **Netdata
configuration directory**.
@@ -96,7 +96,7 @@ section and give it the value of `1`.
```
Restart Netdata with `sudo systemctl restart netdata`, or the [appropriate
-method](/docs/configure/start-stop-restart.md) for your system.
+method](https://github.com/netdata/netdata/blob/master/docs/configure/start-stop-restart.md) for your system.
Now, open up your browser and navigate to `http://HOST:19999/netdata.conf`. You'll see that Netdata has recognized
that our fake option isn't valid and added a notice that Netdata will ignore it.
@@ -124,8 +124,8 @@ Once you're done, restart Netdata and refresh the dashboard. Say hello to your r
netdata.conf](https://user-images.githubusercontent.com/1153921/80994808-1c065300-8df2-11ea-81af-d28dc3ba27c8.gif)
Netdata has dozens upon dozens of options you can change. To see them all, read our [daemon
-configuration](/daemon/config/README.md), or hop into our popular guide on [increasing long-term metrics
-storage](/docs/guides/longer-metrics-storage.md).
+configuration](https://github.com/netdata/netdata/blob/master/daemon/config/README.md), or hop into our popular guide on [increasing long-term metrics
+storage](https://github.com/netdata/netdata/blob/master/docs/guides/longer-metrics-storage.md).
## What's next?
diff --git a/docs/guides/step-by-step/step-05.md b/docs/guides/step-by-step/step-05.md
index 3cd8c5db..3ef498d4 100644
--- a/docs/guides/step-by-step/step-05.md
+++ b/docs/guides/step-by-step/step-05.md
@@ -32,8 +32,7 @@ The first chart you see on any Netdata dashboard is the `system.cpu` chart, whic
across all cores. To figure out which file you need to edit to tune this alarm, click the **Alarms** button at the top
of the dashboard, click on the **All** tab, and find the **system - cpu** alarm entity.
-![The system - cpu alarm
-entity](https://user-images.githubusercontent.com/1153921/67034648-ebb4cc80-f0cc-11e9-9d49-1023629924f5.png)
+![The system - cpu alarm entity](https://user-images.githubusercontent.com/1153921/67034648-ebb4cc80-f0cc-11e9-9d49-1023629924f5.png)
Look at the `source` row in the table. This means the `system.cpu` chart sources its health alarms from
`4@/usr/lib/netdata/conf.d/health.d/cpu.conf`. To tune these alarms, you'll need to edit the alarm file at
@@ -70,10 +69,10 @@ the `warn` and `crit` lines to the values of your choosing. For example:
```
You _can_ restart Netdata with `sudo systemctl restart netdata`, to enable your tune, but you can also reload _only_ the
-health monitoring component using one of the available [methods](/health/QUICKSTART.md#reload-health-configuration).
+health monitoring component using one of the available [methods](https://github.com/netdata/netdata/blob/master/health/QUICKSTART.md#reload-health-configuration).
You can also tune any other aspect of the default alarms. To better understand how each line in a health entity works,
-read our [health documentation](/health/README.md).
+read our [health documentation](https://github.com/netdata/netdata/blob/master/health/README.md).
### Silence an individual alarm
@@ -176,7 +175,7 @@ These lines will trigger a warning if that average RAM usage goes above 80%, and
> ❗ Most default Netdata alarms come with more complicated `warn` and `crit` lines. You may have noticed the line `warn:
> $this > (($status >= $WARNING) ? (75) : (85))` in one of the health entity examples above, which is an example of
-> using the [conditional operator for hysteresis](/health/REFERENCE.md#special-use-of-the-conditional-operator).
+> using the [conditional operator for hysteresis](https://github.com/netdata/netdata/blob/master/health/REFERENCE.md#special-use-of-the-conditional-operator).
> Hysteresis is used to keep Netdata from triggering a ton of alerts if the metric being tracked quickly goes above and
> then falls below the threshold. For this very simple example, we'll skip hysteresis, but recommend implementing it in
> your future health entities.
@@ -215,7 +214,7 @@ stress -m 1 --vm-bytes 8G --vm-keep
```
Netdata is capable of understanding much more complicated entities. To better understand how they work, read the [health
-documentation](/health/README.md), look at some [examples](/health/REFERENCE.md#example-alarms), and open the files
+documentation](https://github.com/netdata/netdata/blob/master/health/README.md), look at some [examples](https://github.com/netdata/netdata/blob/master/health/REFERENCE.md#example-alarms), and open the files
containing the default entities on your system.
## Enable Netdata's notification systems
@@ -224,7 +223,7 @@ Health alarms, while great on their own, are pretty useless without some way of
That's why Netdata comes with a notification system that supports more than a dozen services, such as email, Slack,
Discord, PagerDuty, Twilio, Amazon SNS, and much more.
-To see all the supported systems, visit our [notifications documentation](/health/notifications/README.md).
+To see all the supported systems, visit our [notifications documentation](https://github.com/netdata/netdata/blob/master/health/notifications/README.md).
We'll cover email and Slack notifications here, but with this knowledge you should be able to enable any other type of
notifications instead of or in addition to these.
@@ -330,9 +329,9 @@ applications.
To further configure your email or Slack notification setup, or to enable other notification systems, check out the
following documentation:
-- [Email notifications](/health/notifications/email/README.md)
-- [Slack notifications](/health/notifications/slack/README.md)
-- [Netdata's notification system](/health/notifications/README.md)
+- [Email notifications](https://github.com/netdata/netdata/blob/master/health/notifications/email/README.md)
+- [Slack notifications](https://github.com/netdata/netdata/blob/master/health/notifications/slack/README.md)
+- [Netdata's notification system](https://github.com/netdata/netdata/blob/master/health/notifications/README.md)
## What's next?
diff --git a/docs/guides/step-by-step/step-06.md b/docs/guides/step-by-step/step-06.md
index f04098fc..b951a76b 100644
--- a/docs/guides/step-by-step/step-06.md
+++ b/docs/guides/step-by-step/step-06.md
@@ -8,13 +8,13 @@ custom_edit_url: https://github.com/netdata/netdata/edit/master/docs/guides/step
When Netdata _starts_, it auto-detects dozens of **data sources**, such as database servers, web servers, and more.
To auto-detect and collect metrics from a source you just installed, you need to restart Netdata using `sudo systemctl
-restart netdata`, or the [appropriate method](/docs/configure/start-stop-restart.md) for your system.
+restart netdata`, or the [appropriate method](https://github.com/netdata/netdata/blob/master/docs/configure/start-stop-restart.md) for your system.
However, auto-detection only works if you installed the source using its standard installation
procedure. If Netdata isn't collecting metrics after a restart, your source probably isn't configured
correctly.
-Check out the [collectors that come pre-installed with Netdata](/collectors/COLLECTORS.md) to find the module for the
+Check out the [collectors that come pre-installed with Netdata](https://github.com/netdata/netdata/blob/master/collectors/COLLECTORS.md) to find the module for the
source you want to monitor.
## What you'll learn in this step
@@ -37,8 +37,8 @@ are organized and manged by plugins. **Internal** plugins collect system metrics
non-system metrics, and **orchestrator** plugins group individual collectors together based on the programming language
they were built in.
-These modules are primarily written in [Go](https://learn.netdata.cloud/docs/agent/collectors/go.d.plugin/) (`go.d`) and
-[Python](/collectors/python.d.plugin/README.md), although some use [Bash](/collectors/charts.d.plugin/README.md)
+These modules are primarily written in [Go](https://github.com/netdata/go.d.plugin/blob/master/README.md) (`go.d`) and
+[Python](https://github.com/netdata/netdata/blob/master/collectors/python.d.plugin/README.md), although some use [Bash](https://github.com/netdata/netdata/blob/master/collectors/charts.d.plugin/README.md)
(`charts.d`).
## Enable and disable plugins
@@ -100,7 +100,7 @@ Next, edit your `/etc/nginx/sites-enabled/default` file to include a `location`
```
Restart Netdata using `sudo systemctl restart netdata`, or the [appropriate
-method](/docs/configure/start-stop-restart.md) for your system, and Netdata will auto-detect metrics from your Nginx web
+method](https://github.com/netdata/netdata/blob/master/docs/configure/start-stop-restart.md) for your system, and Netdata will auto-detect metrics from your Nginx web
server!
While not necessary for most auto-detection and collection purposes, you can also configure the Nginx collector itself
diff --git a/docs/guides/step-by-step/step-07.md b/docs/guides/step-by-step/step-07.md
index 17a02cd4..8c5c21be 100644
--- a/docs/guides/step-by-step/step-07.md
+++ b/docs/guides/step-by-step/step-07.md
@@ -9,7 +9,7 @@ custom_edit_url: https://github.com/netdata/netdata/edit/master/docs/guides/step
Welcome to the seventh step of the Netdata guide!
This step of the guide aims to get you more familiar with the features of the dashboard not previously mentioned in
-[step 2](/docs/guides/step-by-step/step-02.md).
+[step 2](https://github.com/netdata/netdata/blob/master/docs/guides/step-by-step/step-02.md).
## What you'll learn in this step
@@ -53,9 +53,9 @@ You can always check if there is an update available from the **Update** area of
If an update is available, you'll see a modal similar to the one above.
-When you use the [automatic one-line installer script](/packaging/installer/README.md) attempt to update every day. If
-you choose to update it manually, there are [several well-documented methods](/packaging/installer/UPDATE.md) to achieve
-that. However, it is best practice for you to first go over the [changelog](/CHANGELOG.md).
+When you use the [automatic one-line installer script](https://github.com/netdata/netdata/blob/master/packaging/installer/README.md) attempt to update every day. If
+you choose to update it manually, there are [several well-documented methods](https://github.com/netdata/netdata/blob/master/packaging/installer/UPDATE.md) to achieve
+that. However, it is best practice for you to first go over the [changelog](https://github.com/netdata/netdata/blob/master/CHANGELOG.md).
## Export and import a snapshot
diff --git a/docs/guides/step-by-step/step-08.md b/docs/guides/step-by-step/step-08.md
index e9c0f902..7a8d417f 100644
--- a/docs/guides/step-by-step/step-08.md
+++ b/docs/guides/step-by-step/step-08.md
@@ -145,7 +145,7 @@ charts on a single page.
### The chart unique ID (required)
You need to specify the unique ID of a chart to show it on your custom dashboard. If you forgot how to find the unique
-ID, head back over to [step 2](/docs/guides/step-by-step/step-02.md#understand-charts-dimensions-families-and-contexts)
+ID, head back over to [step 2](https://github.com/netdata/netdata/blob/master/docs/guides/step-by-step/step-02.md#understand-charts-dimensions-families-and-contexts)
for a re-introduction.
You can then put this unique ID into a `<div>` element with the `data-netdata` attribute. Put this in the `<body>` of
@@ -385,11 +385,11 @@ In this guide, you learned the fundamentals of building a custom Netdata dashboa
charts to your `custom-dashboard.html`, change the charts that are already there, and size them according to your needs.
Of course, the custom dashboarding features covered here are just the beginning. Be sure to read up on our [custom
-dashboard documentation](/web/gui/custom/README.md) for details on how you can use other chart libraries, pull metrics
+dashboard documentation](https://github.com/netdata/netdata/blob/master/web/gui/custom/README.md) for details on how you can use other chart libraries, pull metrics
from multiple Netdata agents, and choose which dimensions a given chart shows.
Next, you'll learn how to store long-term historical metrics in Netdata!
-[Next: Long-term metrics storage &rarr;](/docs/guides/step-by-step/step-09.md)
+[Next: Long-term metrics storage &rarr;](https://github.com/netdata/netdata/blob/master/docs/guides/step-by-step/step-09.md)
diff --git a/docs/guides/step-by-step/step-09.md b/docs/guides/step-by-step/step-09.md
index 8aacd751..839115a2 100644
--- a/docs/guides/step-by-step/step-09.md
+++ b/docs/guides/step-by-step/step-09.md
@@ -5,7 +5,7 @@ custom_edit_url: https://github.com/netdata/netdata/edit/master/docs/guides/step
# Step 9. Long-term metrics storage
-By default, Netdata stores metrics in a custom database we call the [database engine](/database/engine/README.md), which
+By default, Netdata stores metrics in a custom database we call the [database engine](https://github.com/netdata/netdata/blob/master/database/engine/README.md), which
stores recent metrics in your system's RAM and "spills" historical metrics to disk. By using both RAM and disk, the
database engine helps you store a much larger dataset than the amount of RAM your system has.
@@ -51,7 +51,7 @@ the database engine to use. The higher those values, the more metrics Netdata wi
512, respectively, the database engine should store about four day's worth of data on a system collecting 2,000 metrics
every second.
-[**See our database engine calculator**](/docs/store/change-metrics-storage.md) to help you correctly set `dbengine disk
+[**See our database engine calculator**](https://github.com/netdata/netdata/blob/master/docs/store/change-metrics-storage.md) to help you correctly set `dbengine disk
space` based on your needs. The calculator gives an accurate estimate based on how many child nodes you have, how many
metrics your Agent collects, and more.
@@ -63,7 +63,7 @@ metrics your Agent collects, and more.
```
After you've made your changes, restart Netdata using `sudo systemctl restart netdata`, or the [appropriate
-method](/docs/configure/start-stop-restart.md) for your system.
+method](https://github.com/netdata/netdata/blob/master/docs/configure/start-stop-restart.md) for your system.
To confirm the database engine is working, go to your Netdata dashboard and click on the **Netdata Monitoring** menu on
the right-hand side. You can find `dbengine` metrics after `queries`.
@@ -77,7 +77,7 @@ You can archive all the metrics collected by Netdata to **external databases**.
include Graphite, OpenTSDB, Prometheus, AWS Kinesis Data Streams, Google Cloud Pub/Sub, MongoDB, and the list is always
growing.
-As we said in [step 1](/docs/guides/step-by-step/step-01.md), we have only complimentary systems, not competitors! We're
+As we said in [step 1](https://github.com/netdata/netdata/blob/master/docs/guides/step-by-step/step-01.md), we have only complimentary systems, not competitors! We're
happy to support these archiving methods and are always working to improve them.
A lot of Netdata users archive their metrics to one of these databases for long-term storage or further analysis. Since
@@ -117,7 +117,7 @@ use netdata
db.createCollection("netdata_metrics")
```
-Next, Netdata needs to be [reinstalled](/packaging/installer/REINSTALL.md) in order to detect that the required
+Next, Netdata needs to be [reinstalled](https://github.com/netdata/netdata/blob/master/packaging/installer/REINSTALL.md) in order to detect that the required
libraries to make this exporting connection exist. Since you most likely installed Netdata using the one-line installer
script, all you have to do is run that script again. Don't worry—any configuration changes you made along the way will
be retained!
@@ -140,14 +140,14 @@ Add the following section to the file:
```
Restart Netdata using `sudo systemctl restart netdata`, or the [appropriate
-method](/docs/configure/start-stop-restart.md) for your system, to enable the MongoDB exporting connector. Click on the
+method](https://github.com/netdata/netdata/blob/master/docs/configure/start-stop-restart.md) for your system, to enable the MongoDB exporting connector. Click on the
**Netdata Monitoring** menu and check out the **exporting my mongo instance** sub-menu. You should start seeing these
charts fill up with data about the exporting process!
![image](https://user-images.githubusercontent.com/1153921/70443852-25171200-1a56-11ea-8be3-494544b1c295.png)
If you'd like to try connecting Netdata to another database, such as Prometheus or OpenTSDB, read our [exporting
-documentation](/exporting/README.md).
+documentation](https://github.com/netdata/netdata/blob/master/exporting/README.md).
## What's next?
@@ -157,6 +157,6 @@ metrics to MongoDB for long-term storage.
In the last step of this step-by-step guide, we'll put our sysadmin hat on and use Nginx to proxy traffic to and from
our Netdata dashboard.
-[Next: Set up a proxy &rarr;](/docs/guides/step-by-step/step-10.md)
+[Next: Set up a proxy &rarr;](https://github.com/netdata/netdata/blob/master/docs/guides/step-by-step/step-10.md)
diff --git a/docs/guides/step-by-step/step-10.md b/docs/guides/step-by-step/step-10.md
index c9acf5aa..a24e803f 100644
--- a/docs/guides/step-by-step/step-10.md
+++ b/docs/guides/step-by-step/step-10.md
@@ -219,9 +219,9 @@ You're a real sysadmin now!
If you want to configure your Nginx proxy further, check out the following:
-- [Running Netdata behind Nginx](/docs/Running-behind-nginx.md)
-- [How to optimize Netdata's performance](/docs/guides/configure/performance.md)
-- [Enabling TLS on Netdata's dashboard](/web/server/README.md#enabling-tls-support)
+- [Running Netdata behind Nginx](https://github.com/netdata/netdata/blob/master/docs/Running-behind-nginx.md)
+- [How to optimize Netdata's performance](https://github.com/netdata/netdata/blob/master/docs/guides/configure/performance.md)
+- [Enabling TLS on Netdata's dashboard](https://github.com/netdata/netdata/blob/master/web/server/README.md#enabling-tls-support)
And... you're _almost_ done with the Netdata guide.
diff --git a/docs/guides/troubleshoot/monitor-debug-applications-ebpf.md b/docs/guides/troubleshoot/monitor-debug-applications-ebpf.md
index 3ebca542..c79a038c 100644
--- a/docs/guides/troubleshoot/monitor-debug-applications-ebpf.md
+++ b/docs/guides/troubleshoot/monitor-debug-applications-ebpf.md
@@ -9,7 +9,7 @@ custom_edit_url: https://github.com/netdata/netdata/edit/master/docs/guides/trou
When trying to troubleshoot or debug a finicky application, there's no such thing as too much information. At Netdata,
we developed programs that connect to the [_extended Berkeley Packet Filter_ (eBPF) virtual
-machine](/collectors/ebpf.plugin/README.md) to help you see exactly how specific applications are interacting with the
+machine](https://github.com/netdata/netdata/blob/master/collectors/ebpf.plugin/README.md) to help you see exactly how specific applications are interacting with the
Linux kernel. With these charts, you can root out bugs, discover optimizations, diagnose memory leaks, and much more.
This means you can see exactly how often, and in what volume, the application creates processes, opens files, writes to
@@ -26,7 +26,7 @@ To start troubleshooting an application with eBPF metrics, you need to ensure yo
displays those metrics independent from any other process.
You can use the `apps_groups.conf` file to configure which applications appear in charts generated by
-[`apps.plugin`](/collectors/apps.plugin/README.md). Once you edit this file and create a new group for the application
+[`apps.plugin`](https://github.com/netdata/netdata/blob/master/collectors/apps.plugin/README.md). Once you edit this file and create a new group for the application
you want to monitor, you can see how it's interacting with the Linux kernel via real-time eBPF metrics.
Let's assume you have an application that runs on the process `custom-app`. To monitor eBPF metrics for that application
@@ -58,12 +58,12 @@ dev: custom-app
```
Restart Netdata with `sudo systemctl restart netdata`, or the [appropriate
-method](/docs/configure/start-stop-restart.md) for your system, to begin seeing metrics for this particular
+method](https://github.com/netdata/netdata/blob/master/docs/configure/start-stop-restart.md) for your system, to begin seeing metrics for this particular
group+process. You can also add additional processes to the same group.
You can set up `apps_groups.conf` to more show more precise eBPF metrics for any application or service running on your
system, even if it's a standard package like Redis, Apache, or any other [application/service Netdata collects
-from](/collectors/COLLECTORS.md).
+from](https://github.com/netdata/netdata/blob/master/collectors/COLLECTORS.md).
```conf
# -----------------------------------------------------------------------------
@@ -107,7 +107,7 @@ Replace `entry` with `return`:
```
Restart Netdata with `sudo systemctl restart netdata`, or the [appropriate
-method](/docs/configure/start-stop-restart.md) for your system.
+method](https://github.com/netdata/netdata/blob/master/docs/configure/start-stop-restart.md) for your system.
## Get familiar with per-application eBPF metrics and charts
@@ -119,7 +119,7 @@ Pay particular attention to the charts in the **ebpf file**, **ebpf syscall**, *
sub-sections. These charts are populated by low-level Linux kernel metrics thanks to eBPF, and showcase the volume of
calls to open/close files, call functions like `do_fork`, IO activity on the VFS, and much more.
-See the [eBPF collector documentation](/collectors/ebpf.plugin/README.md#integration-with-appsplugin) for the full list
+See the [eBPF collector documentation](https://github.com/netdata/netdata/blob/master/collectors/ebpf.plugin/README.md#integration-with-appsplugin) for the full list
of per-application charts.
Let's show some examples of how you can first identify normal eBPF patterns, then use that knowledge to identify
@@ -236,17 +236,17 @@ same application on multiple systems and want to correlate how it performs on ea
findings with someone else on your team.
If you don't already have a Netdata Cloud account, go [sign in](https://app.netdata.cloud) and get started for free.
-Read the [get started with Cloud guide](https://learn.netdata.cloud/docs/cloud/get-started) for a walkthrough of
+Read the [get started with Cloud guide](https://github.com/netdata/netdata/blob/master/docs/cloud/get-started.mdx) for a walkthrough of
connecting nodes to and other fundamentals.
Once you've added one or more nodes to a Space in Netdata Cloud, you can see aggregated eBPF metrics in the [Overview
-dashboard](/docs/visualize/overview-infrastructure.md) under the same **Applications** or **eBPF** sections that you
-find on the local Agent dashboard. Or, [create new dashboards](/docs/visualize/create-dashboards.md) using eBPF metrics
+dashboard](https://github.com/netdata/netdata/blob/master/docs/visualize/overview-infrastructure.md) under the same **Applications** or **eBPF** sections that you
+find on the local Agent dashboard. Or, [create new dashboards](https://github.com/netdata/netdata/blob/master/docs/visualize/create-dashboards.md) using eBPF metrics
from any number of distributed nodes to see how your application interacts with multiple Linux kernels on multiple Linux
systems.
Now that you can see eBPF metrics in Netdata Cloud, you can [invite your
-team](https://learn.netdata.cloud/docs/cloud/manage/invite-your-team) and share your findings with others.
+team](https://github.com/netdata/netdata/blob/master/docs/cloud/manage/invite-your-team.md) and share your findings with others.
## What's next?
@@ -257,8 +257,8 @@ interacts with the Linux kernel.
If you're still trying to wrap your head around what we offer, be sure to read up on our accompanying documentation and
other resources on eBPF monitoring with Netdata:
-- [eBPF collector](/collectors/ebpf.plugin/README.md)
-- [eBPF's integration with `apps.plugin`](/collectors/apps.plugin/README.md#integration-with-ebpf)
+- [eBPF collector](https://github.com/netdata/netdata/blob/master/collectors/ebpf.plugin/README.md)
+- [eBPF's integration with `apps.plugin`](https://github.com/netdata/netdata/blob/master/collectors/apps.plugin/README.md#integration-with-ebpf)
- [Linux eBPF monitoring with Netdata](https://www.netdata.cloud/blog/linux-ebpf-monitoring-with-netdata/)
The scenarios described above are just the beginning when it comes to troubleshooting with eBPF metrics. We're excited
diff --git a/docs/guides/troubleshoot/troubleshooting-agent-with-cloud-connection.md b/docs/guides/troubleshoot/troubleshooting-agent-with-cloud-connection.md
index 3bb5ace6..138182e0 100644
--- a/docs/guides/troubleshoot/troubleshooting-agent-with-cloud-connection.md
+++ b/docs/guides/troubleshoot/troubleshooting-agent-with-cloud-connection.md
@@ -51,7 +51,7 @@ and you must do it manually, using the following steps:
:::note
In some cases a simple restart of the Agent can fix the issue.
-Read more about [Starting, Stopping and Restarting the Agent](/docs/configure/start-stop-restart.md).
+Read more about [Starting, Stopping and Restarting the Agent](https://github.com/netdata/netdata/blob/master/docs/configure/start-stop-restart.md).
:::
@@ -59,7 +59,7 @@ Read more about [Starting, Stopping and Restarting the Agent](/docs/configure/st
Make sure that you are using the latest version of Netdata if you are using the [Claiming script](https://learn.netdata.cloud/docs/agent/claim#claiming-script).
-With the introduction of our new architecture, Agents running versions lower than `v1.32.0` can face claiming problems, so we recommend you [update the Netdata Agent](https://learn.netdata.cloud/docs/agent/packaging/installer/update) to the latest stable version.
+With the introduction of our new architecture, Agents running versions lower than `v1.32.0` can face claiming problems, so we recommend you [update the Netdata Agent](https://github.com/netdata/netdata/blob/master/packaging/installer/UPDATE.md) to the latest stable version.
## Network issues while connecting to the Cloud
diff --git a/docs/guides/using-host-labels.md b/docs/guides/using-host-labels.md
index 7a5381e9..7937d589 100644
--- a/docs/guides/using-host-labels.md
+++ b/docs/guides/using-host-labels.md
@@ -27,7 +27,7 @@ sudo ./edit-config netdata.conf
```
Create a new `[host labels]` section defining a new host label and its value for the system in question. Make sure not
-to violate any of the [host label naming rules](/docs/configure/common-changes.md#organize-nodes-with-host-labels).
+to violate any of the [host label naming rules](https://github.com/netdata/netdata/blob/master/docs/configure/common-changes.md#organize-nodes-with-host-labels).
```conf
[host labels]
@@ -101,9 +101,9 @@ child system. It's a vastly simplified way of accessing critical information abo
> ⚠️ Because automatic labels for child nodes are accessible via API calls, and contain sensitive information like
> kernel and operating system versions, you should secure streaming connections with SSL. See the [streaming
-> documentation](/streaming/README.md#securing-streaming-communications) for details. You may also want to use
-> [access lists](/web/server/README.md#access-lists) or [expose the API only to LAN/localhost
-> connections](/docs/netdata-security.md#expose-netdata-only-in-a-private-lan).
+> documentation](https://github.com/netdata/netdata/blob/master/streaming/README.md#securing-streaming-communications) for details. You may also want to use
+> [access lists](https://github.com/netdata/netdata/blob/master/web/server/README.md#access-lists) or [expose the API only to LAN/localhost
+> connections](https://github.com/netdata/netdata/blob/master/docs/netdata-security.md#expose-netdata-only-in-a-private-lan).
You can also use `_is_parent`, `_is_child`, and any other host labels in both health entities and metrics
exporting. Speaking of which...
@@ -154,11 +154,11 @@ Or when ephemeral Docker nodes are involved:
```
Of course, there are many more possibilities for intuitively organizing your systems with host labels. See the [health
-documentation](/health/REFERENCE.md#alarm-line-host-labels) for more details, and then get creative!
+documentation](https://github.com/netdata/netdata/blob/master/health/REFERENCE.md#alarm-line-host-labels) for more details, and then get creative!
## Host labels in metrics exporting
-If you have enabled any metrics exporting via our experimental [exporters](/exporting/README.md), any new host
+If you have enabled any metrics exporting via our experimental [exporters](https://github.com/netdata/netdata/blob/master/exporting/README.md), any new host
labels you created manually are sent to the destination database alongside metrics. You can change this behavior by
editing `exporting.conf`, and you can even send automatically-generated labels on with exported metrics.
@@ -183,7 +183,7 @@ send automatic labels = yes
```
By applying labels to exported metrics, you can more easily parse historical metrics with the labels applied. To learn
-more about exporting, read the [documentation](/exporting/README.md).
+more about exporting, read the [documentation](https://github.com/netdata/netdata/blob/master/exporting/README.md).
## What's next?
@@ -195,15 +195,15 @@ the Netdata team first kicked off this work.
It should be noted that while the Netdata dashboard does not expose either user-configured or automatic host labels, API
queries _do_ showcase this information. As always, we recommend you secure Netdata
-- [Expose Netdata only in a private LAN](/docs/netdata-security.md#expose-netdata-only-in-a-private-lan)
-- [Enable TLS/SSL for web/API requests](/web/server/README.md#enabling-tls-support)
+- [Expose Netdata only in a private LAN](https://github.com/netdata/netdata/blob/master/docs/netdata-security.md#expose-netdata-only-in-a-private-lan)
+- [Enable TLS/SSL for web/API requests](https://github.com/netdata/netdata/blob/master/web/server/README.md#enabling-tls-support)
- Put Netdata behind a proxy
- [Use an authenticating web server in proxy
- mode](/docs/netdata-security.md#use-an-authenticating-web-server-in-proxy-mode)
- - [Nginx proxy](/docs/Running-behind-nginx.md)
- - [Apache proxy](/docs/Running-behind-apache.md)
- - [Lighttpd](/docs/Running-behind-lighttpd.md)
- - [Caddy](/docs/Running-behind-caddy.md)
+ mode](https://github.com/netdata/netdata/blob/master/docs/netdata-security.md#use-an-authenticating-web-server-in-proxy-mode)
+ - [Nginx proxy](https://github.com/netdata/netdata/blob/master/docs/Running-behind-nginx.md)
+ - [Apache proxy](https://github.com/netdata/netdata/blob/master/docs/Running-behind-apache.md)
+ - [Lighttpd](https://github.com/netdata/netdata/blob/master/docs/Running-behind-lighttpd.md)
+ - [Caddy](https://github.com/netdata/netdata/blob/master/docs/Running-behind-caddy.md)
If you have issues or questions around using host labels, don't hesitate to [file an
issue](https://github.com/netdata/netdata/issues/new?assignees=&labels=bug%2Cneeds+triage&template=BUG_REPORT.yml) on GitHub. We're
diff --git a/docs/metrics-storage-management/enable-streaming.mdx b/docs/metrics-storage-management/enable-streaming.mdx
index a737b07b..3bcf19b4 100644
--- a/docs/metrics-storage-management/enable-streaming.mdx
+++ b/docs/metrics-storage-management/enable-streaming.mdx
@@ -1,8 +1,15 @@
---
title: "Enable streaming between nodes"
-description: "With metrics streaming enabled, you can not only replicate metrics data into a second database, but also view dashboards and trigger alarm notifications for multiple nodes in parallel."
-type: how-to
-custom_edit_url: https://github.com/netdata/netdata/edit/master/docs/metrics-storage-management/enable-streaming.mdx
+description: >-
+ "With metrics streaming enabled, you can not only replicate metrics data
+ into a second database, but also view dashboards and trigger alarm notifications
+ for multiple nodes in parallel."
+type: "how-to"
+custom_edit_url: "https://github.com/netdata/netdata/blob/master/docs/metrics-storage-management/enable-streaming.mdx"
+sidebar_label: "Enable streaming between nodes"
+learn_status: "Published"
+learn_topic_type: "Tasks"
+learn_rel_path: "Setup"
---
# Enable streaming between nodes
@@ -13,7 +20,7 @@ parent node, and both nodes retain metrics in their own databases.
To configure replication, you need two nodes, each running Netdata. First you'll first enable streaming on your parent
node, then enable streaming on your child node. When you're finished, you'll be able to see the child node's metrics in
the parent node's dashboard, quickly switch between the two dashboards, and be able to serve [alarm
-notifications](/docs/monitor/enable-notifications.md) from either or both nodes.
+notifications](https://github.com/netdata/netdata/blob/master/docs/monitor/enable-notifications.md) from either or both nodes.
## Enable streaming on the parent node
@@ -24,8 +31,8 @@ itself while initiating a streaming connection. Copy that into a separate text f
> Find out how to [install `uuidgen`](https://command-not-found.com/uuidgen) on your node if you don't already have it.
-Next, open `stream.conf` using [`edit-config`](/docs/configure/nodes.md#use-edit-config-to-edit-configuration-files)
-from within the [Netdata config directory](/docs/configure/nodes.md#the-netdata-config-directory).
+Next, open `stream.conf` using [`edit-config`](https://github.com/netdata/netdata/blob/master/docs/configure/nodes.md#use-edit-config-to-edit-configuration-files)
+from within the [Netdata config directory](https://github.com/netdata/netdata/blob/master/docs/configure/nodes.md#the-netdata-config-directory).
```bash
cd /etc/netdata
@@ -49,7 +56,7 @@ simplified version of the configuration, minus the commented lines, looks like t
```
Save the file and close it, then restart Netdata with `sudo systemctl restart netdata`, or the [appropriate
-method](/docs/configure/start-stop-restart.md) for your system.
+method](https://github.com/netdata/netdata/blob/master/docs/configure/start-stop-restart.md) for your system.
## Enable streaming on the child node
@@ -70,7 +77,7 @@ looks like the following:
```
Save the file and close it, then restart Netdata with `sudo systemctl restart netdata`, or the [appropriate
-method](/docs/configure/start-stop-restart.md) for your system.
+method](https://github.com/netdata/netdata/blob/master/docs/configure/start-stop-restart.md) for your system.
## Enable TLS/SSL on streaming (optional)
@@ -90,7 +97,7 @@ sudo chown netdata:netdata /etc/netdata/ssl/cert.pem /etc/netdata/ssl/key.pem
Next, enforce TLS/SSL on the web server. Open `netdata.conf`, scroll down to the `[web]` section, and look for the `bind
to` setting. Add `^SSL=force` to turn on TLS/SSL. See the [web server
-reference](/web/server/README.md#enabling-tls-support) for other TLS/SSL options.
+reference](https://github.com/netdata/netdata/blob/master/web/server/README.md#enabling-tls-support) for other TLS/SSL options.
```conf
[web]
@@ -110,7 +117,7 @@ self-signed certificates.
```
Restart both the parent and child nodes with `sudo systemctl restart netdata`, or the [appropriate
-method](/docs/configure/start-stop-restart.md) for your system, to stream encrypted metrics using TLS/SSL.
+method](https://github.com/netdata/netdata/blob/master/docs/configure/start-stop-restart.md) for your system, to stream encrypted metrics using TLS/SSL.
## View streamed metrics in Netdata's dashboard
@@ -135,17 +142,17 @@ Now that you have a basic streaming setup with replication, you may want to twea
child database, disable the child dashboard, or enable SSL on the streaming connection between the parent and child.
See the [streaming reference
-doc](/docs/metrics-storage-management/reference-streaming.mdx#examples) for details about
+doc](https://github.com/netdata/netdata/blob/master/docs/metrics-storage-management/reference-streaming.mdx#examples) for details about
other possible configurations.
When using Netdata's default TSDB (`dbengine`), the parent node maintains separate, parallel databases for itself and
every child node streaming to it. Each instance is sized identically based on the `dbengine multihost disk space`
-setting in `netdata.conf`. See our doc on [changing metrics retention](/docs/store/change-metrics-storage.md) for
+setting in `netdata.conf`. See our doc on [changing metrics retention](https://github.com/netdata/netdata/blob/master/docs/store/change-metrics-storage.md) for
details.
### Related information & further reading
- Streaming
- - [How Netdata streams metrics](/docs/metrics-storage-management/how-streaming-works.mdx)
- - **[Enable streaming between nodes](/docs/metrics-storage-management/enable-streaming.mdx)**
- - [Streaming reference](/docs/metrics-storage-management/reference-streaming.mdx)
+ - [How Netdata streams metrics](https://github.com/netdata/netdata/blob/master/docs/metrics-storage-management/how-streaming-works.mdx)
+ - **[Enable streaming between nodes](https://github.com/netdata/netdata/blob/master/docs/metrics-storage-management/enable-streaming.mdx)**
+ - [Streaming reference](https://github.com/netdata/netdata/blob/master/docs/metrics-storage-management/reference-streaming.mdx)
diff --git a/docs/metrics-storage-management/how-streaming-works.mdx b/docs/metrics-storage-management/how-streaming-works.mdx
index ecbce39b..f181d376 100644
--- a/docs/metrics-storage-management/how-streaming-works.mdx
+++ b/docs/metrics-storage-management/how-streaming-works.mdx
@@ -1,8 +1,15 @@
---
title: "How metrics streaming works"
-description: "Netdata's real-time streaming allows you to replicate metrics data across multiple nodes, or centralize all your metrics data into a single time-series database (TSDB)."
-type: explanation
-custom_edit_url: https://github.com/netdata/netdata/edit/master/docs/metrics-storage-management/how-streaming-works.mdx
+description: >-
+ "Netdata's real-time streaming allows you to replicate metrics data
+ across multiple nodes, or centralize all your metrics data into a single
+ time-series database (TSDB)."
+type: "explanation"
+custom_edit_url: "https://github.com/netdata/netdata/blob/master/docs/metrics-storage-management/how-streaming-works.mdx"
+sidebar_label: "How metrics streaming works"
+learn_status: "Published"
+learn_topic_type: "Concepts"
+learn_rel_path: "Concepts"
---
# How metrics streaming works
@@ -12,13 +19,13 @@ replicate metrics data across multiple nodes, or centralize all your metrics dat
(TSDB).
When one node streams metrics to another, the node receiving metrics can visualize them on the
-[dashboard](/docs/visualize/interact-dashboards-charts.md), run health checks to [trigger
-alarms](/docs/monitor/view-active-alarms.md) and [send notifications](/docs/monitor/enable-notifications.md), and
-[export](/docs/export/external-databases.md) all metrics to an external TSDB. When Netdata streams metrics to another
+[dashboard](https://github.com/netdata/netdata/blob/master/docs/visualize/interact-dashboards-charts.md), run health checks to [trigger
+alarms](https://github.com/netdata/netdata/blob/master/docs/monitor/view-active-alarms.md) and [send notifications](https://github.com/netdata/netdata/blob/master/docs/monitor/enable-notifications.md), and
+[export](https://github.com/netdata/netdata/blob/master/docs/export/external-databases.md) all metrics to an external TSDB. When Netdata streams metrics to another
Netdata, the receiving one is able to perform everything a Netdata instance is capable of.
Streaming lets you decide exactly how you want to store and maintain metrics data. While we believe Netdata's
-[distributed architecture](/docs/store/distributed-data-architecture.md) is ideal for speed and scale, streaming
+[distributed architecture](https://github.com/netdata/netdata/blob/master/docs/store/distributed-data-architecture.md) is ideal for speed and scale, streaming
provides centralization options for those who want to maintain only a single TSDB instance.
## Streaming basics
@@ -68,7 +75,7 @@ Here are a few example streaming configurations:
Parent nodes feature a **Replicated Nodes** section in the left-hand panel, which opens with the hamburger icon
![Hamburger icon](https://raw.githubusercontent.com/netdata/netdata-ui/master/src/components/icon/assets/hamburger.svg)
in the top navigation. The parent node, plus any child nodes, appear here. Click on any of the hostnames to switch
-between parent and child dashboards, all served by the parent's [web server](/web/server/README.md).
+between parent and child dashboards, all served by the parent's [web server](https://github.com/netdata/netdata/blob/master/web/server/README.md).
![Switching between
](https://user-images.githubusercontent.com/1153921/110043346-761ec000-7d04-11eb-8e58-77670ba39161.gif)
@@ -79,14 +86,14 @@ Each child dashboard is also available directly at the following URL pattern:
## What's next?
Now that you understand the fundamentals of streaming metrics between nodes, go ahead and [enable
-streaming](/docs/metrics-storage-management/enable-streaming.mdx) using a simple `parent-child` relationship. For all
-the details, see the [streaming reference](/docs/metrics-storage-management/reference-streaming.mdx) doc.
+streaming](https://github.com/netdata/netdata/blob/master/docs/metrics-storage-management/enable-streaming.mdx) using a simple `parent-child` relationship. For all
+the details, see the [streaming reference](https://github.com/netdata/netdata/blob/master/docs/metrics-storage-management/reference-streaming.mdx) doc.
-Take your streaming setup even further by [exporting metrics](/docs/export/external-databases.md) to an external TSDB.
+Take your streaming setup even further by [exporting metrics](https://github.com/netdata/netdata/blob/master/docs/export/external-databases.md) to an external TSDB.
### Related information & further reading
- Streaming
- - **[How Netdata streams metrics](/docs/metrics-storage-management/how-streaming-works.mdx)**
- - [Enable streaming between nodes](/docs/metrics-storage-management/enable-streaming.mdx)
- - [Streaming reference](/docs/metrics-storage-management/reference-streaming.mdx) \ No newline at end of file
+ - **[How Netdata streams metrics](https://github.com/netdata/netdata/blob/master/docs/metrics-storage-management/how-streaming-works.mdx)**
+ - [Enable streaming between nodes](https://github.com/netdata/netdata/blob/master/docs/metrics-storage-management/enable-streaming.mdx)
+ - [Streaming reference](https://github.com/netdata/netdata/blob/master/docs/metrics-storage-management/reference-streaming.mdx) \ No newline at end of file
diff --git a/docs/metrics-storage-management/reference-streaming.mdx b/docs/metrics-storage-management/reference-streaming.mdx
index c77ceb37..58c89863 100644
--- a/docs/metrics-storage-management/reference-streaming.mdx
+++ b/docs/metrics-storage-management/reference-streaming.mdx
@@ -1,24 +1,28 @@
---
title: "Streaming reference"
description: "Each node running Netdata can stream the metrics it collects, in real time, to another node. See all of the available settings in this reference document."
-type: reference
-custom_edit_url: https://github.com/netdata/netdata/edit/master/docs/metrics-storage-management/reference-streaming.mdx
+type: "reference"
+custom_edit_url: "https://github.com/netdata/netdata/edit/master/docs/metrics-storage-management/reference-streaming.mdx"
+sidebar_label: "Streaming reference"
+learn_status: "Published"
+learn_topic_type: "References"
+learn_rel_path: "References/Configuration"
---
# Streaming reference
Each node running Netdata can stream the metrics it collects, in real time, to another node. To learn more, read about
-[how streaming works](/docs/metrics-storage-management/how-streaming-works.mdx).
+[how streaming works](https://github.com/netdata/netdata/blob/master/docs/metrics-storage-management/how-streaming-works.mdx).
For a quickstart guide for enabling a simple `parent-child` streaming relationship, see our [stream metrics between
-nodes](/docs/metrics-storage-management/enable-streaming.mdx) doc. All other configuration options and scenarios are
+nodes](https://github.com/netdata/netdata/blob/master/docs/metrics-storage-management/enable-streaming.mdx) doc. All other configuration options and scenarios are
covered in the sections below.
## Configuration
There are two files responsible for configuring Netdata's streaming capabilities: `stream.conf` and `netdata.conf`.
-From within your Netdata config directory (typically `/etc/netdata`), [use `edit-config`](/docs/configure/nodes.md) to
+From within your Netdata config directory (typically `/etc/netdata`), [use `edit-config`](https://github.com/netdata/netdata/blob/master/docs/configure/nodes.md) to
open either `stream.conf` or `netdata.conf`.
```
@@ -53,7 +57,7 @@ node**. This file is automatically generated by Netdata the first time it is sta
| `api key` | ` ` | The `API_KEY` to use as the child node. |
| `timeout seconds` | `60` | The timeout to connect and send metrics to a parent. |
| `default port` | `19999` | The port to use if `destination` does not specify one. |
-| [`send charts matching`](#send-charts-matching) | `*` | A space-separated list of [Netdata simple patterns](/libnetdata/simple_pattern/README.md) to filter which charts are streamed. [Read more &rarr;](#send-charts-matching) |
+| [`send charts matching`](#send-charts-matching) | `*` | A space-separated list of [Netdata simple patterns](https://github.com/netdata/netdata/blob/master/libnetdata/simple_pattern/README.md) to filter which charts are streamed. [Read more &rarr;](#send-charts-matching) |
| `buffer size bytes` | `10485760` | The size of the buffer to use when sending metrics. The default `10485760` equals a buffer of 10MB, which is good for 60 seconds of data. Increase this if you expect latencies higher than that. The buffer is flushed on reconnect. |
| `reconnect delay seconds` | `5` | How long to wait until retrying to connect to the parent node. |
| `initial clock resync iterations` | `60` | Sync the clock of charts for how many seconds when starting. |
@@ -63,9 +67,9 @@ node**. This file is automatically generated by Netdata the first time it is sta
| Setting | Default | Description |
| :---------------------------------------------- | :------------------------ | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `enabled` | `no` | Whether this API KEY enabled or disabled. |
-| [`allow from`](#allow-from) | `*` | A space-separated list of [Netdata simple patterns](/libnetdata/simple_pattern/README.md) matching the IPs of nodes that will stream metrics using this API key. [Read more &rarr;](#allow-from) |
+| [`allow from`](#allow-from) | `*` | A space-separated list of [Netdata simple patterns](https://github.com/netdata/netdata/blob/master/libnetdata/simple_pattern/README.md) matching the IPs of nodes that will stream metrics using this API key. [Read more &rarr;](#allow-from) |
| `default history` | `3600` | The default amount of child metrics history to retain when using the `save`, `map`, or `ram` memory modes. |
-| [`default memory mode`](#default-memory-mode) | `ram` | The [database](/database/README.md) to use for all nodes using this `API_KEY`. Valid settings are `dbengine`, `map`, `save`, `ram`, or `none`. [Read more &rarr;](#default-memory-mode) |
+| [`default memory mode`](#default-memory-mode) | `ram` | The [database](https://github.com/netdata/netdata/blob/master/database/README.md) to use for all nodes using this `API_KEY`. Valid settings are `dbengine`, `map`, `save`, `ram`, or `none`. [Read more &rarr;](#default-memory-mode) |
| `health enabled by default` | `auto` | Whether alarms and notifications should be enabled for nodes using this `API_KEY`. `auto` enables alarms when the child is connected. `yes` enables alarms always, and `no` disables alarms. |
| `default postpone alarms on connect seconds` | `60` | Postpone alarms and notifications for a period of time after the child connects. |
| `default proxy enabled` | ` ` | Route metrics through a proxy. |
@@ -94,7 +98,7 @@ To enable TCP streaming to a parent node at `203.0.113.0` on port `20000` and wi
#### `send charts matching`
-A space-separated list of [Netdata simple patterns](/libnetdata/simple_pattern/README.md) to filter which charts are streamed.
+A space-separated list of [Netdata simple patterns](https://github.com/netdata/netdata/blob/master/libnetdata/simple_pattern/README.md) to filter which charts are streamed.
The default is a single wildcard `*`, which streams all charts.
@@ -115,7 +119,7 @@ To send all but a few charts, use `!` to create a negative match. To send _all_
#### `allow from`
-A space-separated list of [Netdata simple patterns](/libnetdata/simple_pattern/README.md) matching the IPs of nodes that
+A space-separated list of [Netdata simple patterns](https://github.com/netdata/netdata/blob/master/libnetdata/simple_pattern/README.md) matching the IPs of nodes that
will stream metrics using this API key. The order is important, left to right, as the first positive or negative match is used.
The default is `*`, which accepts all requests including the `API_KEY`.
@@ -139,7 +143,7 @@ To allow all IPs starting with `10.*`, except `10.1.2.3`:
#### `default memory mode`
-The [database](/database/README.md) to use for all nodes using this `API_KEY`. Valid settings are `dbengine`, `ram`,
+The [database](https://github.com/netdata/netdata/blob/master/database/README.md) to use for all nodes using this `API_KEY`. Valid settings are `dbengine`, `ram`,
`save`, `map`, or `none`.
- `dbengine`: The default, recommended time-series database (TSDB) for Netdata. Stores recent metrics in memory, then
@@ -152,7 +156,7 @@ The [database](/database/README.md) to use for all nodes using this `API_KEY`. V
- `none`: No database.
When using `default memory mode = dbengine`, the parent node creates a separate instance of the TSDB to store metrics
-from child nodes. The [size of _each_ instance is configurable](/docs/store/change-metrics-storage.md) with the `page
+from child nodes. The [size of _each_ instance is configurable](https://github.com/netdata/netdata/blob/master/docs/store/change-metrics-storage.md) with the `page
cache size` and `dbengine multihost disk space` settings in the `[global]` section in `netdata.conf`.
### `netdata.conf`
@@ -160,9 +164,9 @@ cache size` and `dbengine multihost disk space` settings in the `[global]` secti
| Setting | Default | Description |
| :----------------------------------------- | :---------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`[global]` section** | | |
-| `memory mode` | `dbengine` | Determines the [database type](/database/README.md) to be used on that node. Other options settings include `none`, `ram`, `save`, and `map`. `none` disables the database at this host. This also disables alarms and notifications, as those can't run without a database. |
+| `memory mode` | `dbengine` | Determines the [database type](https://github.com/netdata/netdata/blob/master/database/README.md) to be used on that node. Other options settings include `none`, `ram`, `save`, and `map`. `none` disables the database at this host. This also disables alarms and notifications, as those can't run without a database. |
| **`[web]` section** | | |
-| `mode` | `static-threaded` | Determines the [web server](/web/server/README.md) type. The other option is `none`, which disables the dashboard, API, and registry. |
+| `mode` | `static-threaded` | Determines the [web server](https://github.com/netdata/netdata/blob/master/web/server/README.md) type. The other option is `none`, which disables the dashboard, API, and registry. |
| `accept a streaming request every seconds` | `0` | Set a limit on how often a parent node accepts streaming requests from child nodes. `0` equals no limit. If this is set, you may see `... too busy to accept new streaming request. Will be allowed in X secs` in Netdata's `error.log`. |
## Examples
@@ -191,7 +195,7 @@ default `dbengine` as specified by the `API_KEY`, and alarms are disabled.
### Securing streaming with TLS/SSL
Netdata does not activate TLS encryption by default. To encrypt streaming connections, you first need to [enable TLS
-support](/web/server/README.md#enabling-tls-support) on the parent. With encryption enabled on the receiving side, you
+support](https://github.com/netdata/netdata/blob/master/web/server/README.md#enabling-tls-support) on the parent. With encryption enabled on the receiving side, you
need to instruct the child to use TLS/SSL as well. On the child's `stream.conf`, configure the destination as follows:
```
@@ -450,7 +454,7 @@ ERROR : STREAM_SENDER[CHILD HOSTNAME] : STREAM child HOSTNAME [send to PARENT HO
Chart data needs to be consistent between child and parent nodes. If there are differences between chart data on
a parent and a child, such as gaps in metrics collection, it most often means your child's `memory mode`
does not match the parent's. To learn more about the different ways Netdata can store metrics, and thus keep chart
-data consistent, read our [memory mode documentation](/database/README.md).
+data consistent, read our [memory mode documentation](https://github.com/netdata/netdata/blob/master/database/README.md).
### Forbidding access
diff --git a/docs/monitor/configure-alarms.md b/docs/monitor/configure-alarms.md
index ac458115..4b5b8134 100644
--- a/docs/monitor/configure-alarms.md
+++ b/docs/monitor/configure-alarms.md
@@ -1,7 +1,11 @@
<!--
title: "Configure health alarms"
description: "Netdata's health monitoring watchdog is incredibly adaptable to your infrastructure's unique needs, with configurable health alarms."
-custom_edit_url: https://github.com/netdata/netdata/edit/master/docs/monitor/configure-alarms.md
+custom_edit_url: "https://github.com/netdata/netdata/edit/master/docs/monitor/configure-alarms.md"
+sidebar_label: "Configure health alarms"
+learn_status: "Published"
+learn_topic_type: "Tasks"
+learn_rel_path: "Setup"
-->
# Configure health alarms
@@ -10,19 +14,19 @@ Netdata's health watchdog is highly configurable, with support for dynamic thres
more. You can tweak any of the existing alarms based on your infrastructure's topology or specific monitoring needs, or
create new entities.
-You can use health alarms in conjunction with any of Netdata's [collectors](/docs/collect/how-collectors-work.md) (see
-the [supported collector list](/collectors/COLLECTORS.md)) to monitor the health of your systems, containers, and
+You can use health alarms in conjunction with any of Netdata's [collectors](https://github.com/netdata/netdata/blob/master/docs/collect/how-collectors-work.md) (see
+the [supported collector list](https://github.com/netdata/netdata/blob/master/collectors/COLLECTORS.md)) to monitor the health of your systems, containers, and
applications in real time.
While you can see active alarms both on the local dashboard and Netdata Cloud, all health alarms are configured _per
node_ via individual Netdata Agents. If you want to deploy a new alarm across your
-[infrastructure](/docs/quickstart/infrastructure.md), you must configure each node with the same health configuration
+[infrastructure](https://github.com/netdata/netdata/blob/master/docs/quickstart/infrastructure.md), you must configure each node with the same health configuration
files.
## Edit health configuration files
-All of Netdata's [health configuration files](/health/REFERENCE.md#health-configuration-files) are in Netdata's config
-directory, inside the `health.d/` directory. Navigate to your [Netdata config directory](/docs/configure/nodes.md) and
+All of Netdata's [health configuration files](https://github.com/netdata/netdata/blob/master/health/REFERENCE.md#health-configuration-files) are in Netdata's config
+directory, inside the `health.d/` directory. Navigate to your [Netdata config directory](https://github.com/netdata/netdata/blob/master/docs/configure/nodes.md) and
use `edit-config` to make changes to any of these files.
For example, to edit the `cpu.conf` health configuration file, run:
@@ -73,10 +77,10 @@ one line in a given health entity. To silence any single alarm, change the `to:`
While tuning existing alarms may work in some cases, you may need to write entirely new health entities based on how
your systems, containers, and applications work.
-Read Netdata's [health reference](/health/REFERENCE.md#health-entity-reference) for a full listing of the format,
+Read Netdata's [health reference](https://github.com/netdata/netdata/blob/master/health/REFERENCE.md#health-entity-reference) for a full listing of the format,
syntax, and functionality of health entities.
-To write a new health entity into a new file, navigate to your [Netdata config directory](/docs/configure/nodes.md),
+To write a new health entity into a new file, navigate to your [Netdata config directory](https://github.com/netdata/netdata/blob/master/docs/configure/nodes.md),
then use `touch` to create a new file in the `health.d/` directory. Use `edit-config` to start editing the file.
As an example, let's create a `ram-usage.conf` file.
@@ -117,7 +121,7 @@ Let's look into each of the lines to see how they create a working health entity
- `every`: How often to perform the `lookup` calculation to decide whether or not to trigger this alarm.
- `warn`/`crit`: The value at which Netdata should trigger a warning or critical alarm. This example uses simple
syntax, but most pre-configured health entities use
- [hysteresis](/health/REFERENCE.md#special-use-of-the-conditional-operator) to avoid superfluous notifications.
+ [hysteresis](https://github.com/netdata/netdata/blob/master/health/REFERENCE.md#special-use-of-the-conditional-operator) to avoid superfluous notifications.
- `info`: A description of the alarm, which will appear in the dashboard and notifications.
In human-readable format:
@@ -140,9 +144,9 @@ without restarting all of Netdata, run `netdatacli reload-health` or `killall -U
## What's next?
With your health entities configured properly, it's time to [enable
-notifications](/docs/monitor/enable-notifications.md) to get notified whenever a node reaches a warning or critical
+notifications](https://github.com/netdata/netdata/blob/master/docs/monitor/enable-notifications.md) to get notified whenever a node reaches a warning or critical
state.
-To build complex, dynamic alarms, read our guide on [dimension templates](/docs/guides/monitor/dimension-templates.md).
+To build complex, dynamic alarms, read our guide on [dimension templates](https://github.com/netdata/netdata/blob/master/docs/guides/monitor/dimension-templates.md).
diff --git a/docs/monitor/enable-notifications.md b/docs/monitor/enable-notifications.md
index 438eef39..99c24b64 100644
--- a/docs/monitor/enable-notifications.md
+++ b/docs/monitor/enable-notifications.md
@@ -1,7 +1,11 @@
<!--
title: "Enable alarm notifications"
description: "Send Netdata alarms from a centralized place with Netdata Cloud, or configure nodes individually, to enable incident response and faster resolution."
-custom_edit_url: https://github.com/netdata/netdata/edit/master/docs/monitor/enable-notifications.md
+custom_edit_url: "https://github.com/netdata/netdata/edit/master/docs/monitor/enable-notifications.md"
+sidebar_label: "Enable alarm notifications"
+learn_status: "Published"
+learn_topic_type: "Tasks"
+learn_rel_path: "Setup"
-->
# Enable alarm notifications
@@ -10,7 +14,7 @@ Netdata offers two ways to receive alarm notifications on external platforms. Th
parallel, which means you can enable both at the same time to send alarm notifications to any number of endpoints.
Both methods use a node's health alarms to generate the content of alarm notifications. Read the doc on [configuring
-alarms](/docs/monitor/configure-alarms.md) to change the preconfigured thresholds or to create tailored alarms for your
+alarms](https://github.com/netdata/netdata/blob/master/docs/monitor/configure-alarms.md) to change the preconfigured thresholds or to create tailored alarms for your
infrastructure.
Netdata Cloud offers [centralized alarm notifications](#netdata-cloud) via email, which leverages the health status
@@ -26,7 +30,7 @@ response process.
## Netdata Cloud
Netdata Cloud's [centralized alarm
-notifications](https://learn.netdata.cloud/docs/cloud/alerts-notifications/notifications) is a zero-configuration way to
+notifications](https://github.com/netdata/netdata/blob/master/docs/cloud/alerts-notifications/notifications.mdx) is a zero-configuration way to
get notified when an anomaly or incident strikes any node or application in your infrastructure. The advantage of using
centralized alarm notifications from Netdata Cloud is that you don't have to worry about configuring each node in your
infrastructure.
@@ -41,13 +45,13 @@ choose what types of notifications to receive from each War Room.
![Enabling and configuring alarm notifications in Netdata
Cloud](https://user-images.githubusercontent.com/1153921/101936280-93c50900-3b9d-11eb-9ba0-d6927fa872b7.gif)
-See the [centralized alarm notifications](https://learn.netdata.cloud/docs/cloud/alerts-notifications/notifications)
+See the [centralized alarm notifications](https://github.com/netdata/netdata/blob/master/docs/cloud/alerts-notifications/notifications.mdx)
reference doc for further details about what information is conveyed in an email notification, flood protection, and
more.
## Netdata Agent
-The Netdata Agent's [notification system](/health/notifications/README.md) runs on every node and dispatches
+The Netdata Agent's [notification system](https://github.com/netdata/netdata/blob/master/health/notifications/README.md) runs on every node and dispatches
notifications based on configured endpoints and roles. You can enable multiple endpoints on any one node _and_ use Agent
notifications in parallel with centralized alarm notifications in Netdata Cloud.
@@ -59,33 +63,33 @@ notification platform.
### Supported notification endpoints
-- [**alerta.io**](/health/notifications/alerta/README.md)
-- [**Amazon SNS**](/health/notifications/awssns/README.md)
-- [**Custom endpoint**](/health/notifications/custom/README.md)
-- [**Discord**](/health/notifications/discord/README.md)
-- [**Dynatrace**](/health/notifications/dynatrace/README.md)
-- [**Email**](/health/notifications/email/README.md)
-- [**Flock**](/health/notifications/flock/README.md)
-- [**Google Hangouts**](/health/notifications/hangouts/README.md)
-- [**Gotify**](/health/notifications/gotify/README.md)
-- [**IRC**](/health/notifications/irc/README.md)
-- [**Kavenegar**](/health/notifications/kavenegar/README.md)
-- [**Matrix**](/health/notifications/matrix/README.md)
-- [**Messagebird**](/health/notifications/messagebird/README.md)
-- [**Microsoft Teams**](/health/notifications/msteams/README.md)
-- [**Netdata Agent dashboard**](/health/notifications/web/README.md)
-- [**Opsgenie**](/health/notifications/opsgenie/README.md)
-- [**PagerDuty**](/health/notifications/pagerduty/README.md)
-- [**Prowl**](/health/notifications/prowl/README.md)
-- [**PushBullet**](/health/notifications/pushbullet/README.md)
-- [**PushOver**](/health/notifications/pushover/README.md)
-- [**Rocket.Chat**](/health/notifications/rocketchat/README.md)
-- [**Slack**](/health/notifications/slack/README.md)
-- [**SMS Server Tools 3**](/health/notifications/smstools3/README.md)
-- [**StackPulse**](/health/notifications/stackpulse/README.md)
-- [**Syslog**](/health/notifications/syslog/README.md)
-- [**Telegram**](/health/notifications/telegram/README.md)
-- [**Twilio**](/health/notifications/twilio/README.md)
+- [**alerta.io**](https://github.com/netdata/netdata/blob/master/health/notifications/alerta/README.md)
+- [**Amazon SNS**](https://github.com/netdata/netdata/blob/master/health/notifications/awssns/README.md)
+- [**Custom endpoint**](https://github.com/netdata/netdata/blob/master/health/notifications/custom/README.md)
+- [**Discord**](https://github.com/netdata/netdata/blob/master/health/notifications/discord/README.md)
+- [**Dynatrace**](https://github.com/netdata/netdata/blob/master/health/notifications/dynatrace/README.md)
+- [**Email**](https://github.com/netdata/netdata/blob/master/health/notifications/email/README.md)
+- [**Flock**](https://github.com/netdata/netdata/blob/master/health/notifications/flock/README.md)
+- [**Google Hangouts**](https://github.com/netdata/netdata/blob/master/health/notifications/hangouts/README.md)
+- [**Gotify**](https://github.com/netdata/netdata/blob/master/health/notifications/gotify/README.md)
+- [**IRC**](https://github.com/netdata/netdata/blob/master/health/notifications/irc/README.md)
+- [**Kavenegar**](https://github.com/netdata/netdata/blob/master/health/notifications/kavenegar/README.md)
+- [**Matrix**](https://github.com/netdata/netdata/blob/master/health/notifications/matrix/README.md)
+- [**Messagebird**](https://github.com/netdata/netdata/blob/master/health/notifications/messagebird/README.md)
+- [**Microsoft Teams**](https://github.com/netdata/netdata/blob/master/health/notifications/msteams/README.md)
+- [**Netdata Agent dashboard**](https://github.com/netdata/netdata/blob/master/health/notifications/web/README.md)
+- [**Opsgenie**](https://github.com/netdata/netdata/blob/master/health/notifications/opsgenie/README.md)
+- [**PagerDuty**](https://github.com/netdata/netdata/blob/master/health/notifications/pagerduty/README.md)
+- [**Prowl**](https://github.com/netdata/netdata/blob/master/health/notifications/prowl/README.md)
+- [**PushBullet**](https://github.com/netdata/netdata/blob/master/health/notifications/pushbullet/README.md)
+- [**PushOver**](https://github.com/netdata/netdata/blob/master/health/notifications/pushover/README.md)
+- [**Rocket.Chat**](https://github.com/netdata/netdata/blob/master/health/notifications/rocketchat/README.md)
+- [**Slack**](https://github.com/netdata/netdata/blob/master/health/notifications/slack/README.md)
+- [**SMS Server Tools 3**](https://github.com/netdata/netdata/blob/master/health/notifications/smstools3/README.md)
+- [**StackPulse**](https://github.com/netdata/netdata/blob/master/health/notifications/stackpulse/README.md)
+- [**Syslog**](https://github.com/netdata/netdata/blob/master/health/notifications/syslog/README.md)
+- [**Telegram**](https://github.com/netdata/netdata/blob/master/health/notifications/telegram/README.md)
+- [**Twilio**](https://github.com/netdata/netdata/blob/master/health/notifications/twilio/README.md)
### Enable Slack notifications
@@ -95,7 +99,7 @@ want to see alarm notifications from Netdata. Click the green **Add to Slack** b
On the following page, you'll receive a **Webhook URL**. That's what you'll need to configure Netdata, so keep it handy.
-Navigate to your [Netdata config directory](/docs/configure/nodes.md#the-netdata-config-directory) and use `edit-config` to
+Navigate to your [Netdata config directory](https://github.com/netdata/netdata/blob/master/docs/configure/nodes.md#the-netdata-config-directory) and use `edit-config` to
open the `health_alarm_notify.conf` file:
```bash
@@ -130,7 +134,7 @@ Next, run the `alarm-notify` script using the `test` option.
You should receive three notifications in your Slack channel for each health status change: `WARNING`, `CRITICAL`, and
`CLEAR`.
-See the [Agent Slack notifications](/health/notifications/slack/README.md) doc for more options and information.
+See the [Agent Slack notifications](https://github.com/netdata/netdata/blob/master/health/notifications/slack/README.md) doc for more options and information.
## What's next?
@@ -138,10 +142,10 @@ Now that you have health entities configured to your infrastructure's needs and
or incidents, your health monitoring setup is complete.
To make your dashboards most useful during root cause analysis, use Netdata's [distributed data
-architecture](/docs/store/distributed-data-architecture.md) for the best-in-class performance and scalability.
+architecture](https://github.com/netdata/netdata/blob/master/docs/store/distributed-data-architecture.md) for the best-in-class performance and scalability.
### Related reference documentation
-- [Netdata Cloud · Alarm notifications](https://learn.netdata.cloud/docs/cloud/alerts-notifications/notifications)
-- [Netdata Agent · Notifications](/health/notifications/README.md)
+- [Netdata Cloud · Alarm notifications](https://github.com/netdata/netdata/blob/master/docs/cloud/alerts-notifications/notifications.mdx)
+- [Netdata Agent · Notifications](https://github.com/netdata/netdata/blob/master/health/notifications/README.md)
diff --git a/docs/monitor/view-active-alarms.md b/docs/monitor/view-active-alarms.md
index be218268..07c22fe1 100644
--- a/docs/monitor/view-active-alarms.md
+++ b/docs/monitor/view-active-alarms.md
@@ -1,7 +1,11 @@
<!--
title: "View active health alarms"
description: "View active alarms and their rich data to discover and resolve anomalies and performance issues across your infrastructure."
-custom_edit_url: https://github.com/netdata/netdata/edit/master/docs/monitor/view-active-alarms.md
+custom_edit_url: "https://github.com/netdata/netdata/edit/master/docs/monitor/view-active-alarms.md"
+sidebar_label: "View active health alarms"
+learn_status: "Published"
+learn_topic_type: "Concepts"
+learn_rel_path: "Operations/Alerts"
-->
# View active health alarms
@@ -14,7 +18,7 @@ performance issue affects your node or the applications it runs.
A War Room's [alarms indicator](https://learn.netdata.cloud/docs/cloud/war-rooms#indicators) displays the number of
active `critical` (red) and `warning` (yellow) alerts for the nodes in this War Room. Click on either the critical or
warning badges to open a pre-filtered modal displaying only those types of [active
-alarms](https://learn.netdata.cloud/docs/cloud/alerts-notifications/view-active-alerts).
+alarms](https://github.com/netdata/netdata/blob/master/docs/cloud/alerts-notifications/view-active-alerts.mdx).
![The Alarms panel in Netdata
Cloud](https://user-images.githubusercontent.com/1153921/108564747-d2bfbb00-72c0-11eb-97b9-5863ad3324eb.png)
@@ -61,15 +65,15 @@ With the three icons beneath that and the **role** designation, you can:
3. Copy the code to embed the badge onto another web page using an `<embed>` element.
The table on the right-hand side displays information about the health entity that triggered the alarm, which you can
-use as a reference to [configure alarms](/docs/monitor/configure-alarms.md).
+use as a reference to [configure alarms](https://github.com/netdata/netdata/blob/master/docs/monitor/configure-alarms.md).
## What's next?
With the information that appears on Netdata Cloud and the local dashboard about active alarms, you can [configure
-alarms](/docs/monitor/configure-alarms.md) to match your infrastructure's needs or your team's goals.
+alarms](https://github.com/netdata/netdata/blob/master/docs/monitor/configure-alarms.md) to match your infrastructure's needs or your team's goals.
If you're happy with the pre-configured alarms, skip ahead to [enable
-notifications](/docs/monitor/enable-notifications.md) to use Netdata Cloud's centralized alarm notifications and/or
+notifications](https://github.com/netdata/netdata/blob/master/docs/monitor/enable-notifications.md) to use Netdata Cloud's centralized alarm notifications and/or
per-node notifications to endpoints like Slack, PagerDuty, Twilio, and more.
diff --git a/docs/netdata-for-IoT.md b/docs/netdata-for-IoT.md
index 8d5bb21b..87b307b9 100644
--- a/docs/netdata-for-IoT.md
+++ b/docs/netdata-for-IoT.md
@@ -10,22 +10,23 @@ custom_edit_url: https://github.com/netdata/netdata/edit/master/docs/netdata-for
> New to Netdata? Check its demo: **<https://my-netdata.io/>**
>
>[![User
->Base](https://registry.my-netdata.io/api/v1/badge.svg?chart=netdata.registry_entries&dimensions=persons&label=user%20base&units=null&value_color=blue&precision=0&v41)](https://registry.my-netdata.io/#netdata_registry)
->[![Monitored
->Servers](https://registry.my-netdata.io/api/v1/badge.svg?chart=netdata.registry_entries&dimensions=machines&label=servers%20monitored&units=null&value_color=orange&precision=0&v41)](https://registry.my-netdata.io/#netdata_registry)
->[![Sessions
->Served](https://registry.my-netdata.io/api/v1/badge.svg?chart=netdata.registry_sessions&label=sessions%20served&units=null&value_color=yellowgreen&precision=0&v41)](https://registry.my-netdata.io/#netdata_registry)
+> Base](https://registry.my-netdata.io/api/v1/badge.svg?chart=netdata.registry_entries&dimensions=persons&label=user%20base&units=null&value_color=blue&precision=0&v41)](https://registry.my-netdata.io/#netdata_registry)
+> [![Monitored
+> Servers](https://registry.my-netdata.io/api/v1/badge.svg?chart=netdata.registry_entries&dimensions=machines&label=servers%20monitored&units=null&value_color=orange&precision=0&v41)](https://registry.my-netdata.io/#netdata_registry)
+> [![Sessions
+> Served](https://registry.my-netdata.io/api/v1/badge.svg?chart=netdata.registry_sessions&label=sessions%20served&units=null&value_color=yellowgreen&precision=0&v41)](https://registry.my-netdata.io/#netdata_registry)
>
>[![New Users
->Today](https://registry.my-netdata.io/api/v1/badge.svg?chart=netdata.registry_entries&dimensions=persons&after=-86400&options=unaligned&group=incremental-sum&label=new%20users%20today&units=null&value_color=blue&precision=0&v40)](https://registry.my-netdata.io/#netdata_registry)
->[![New Machines
->Today](https://registry.my-netdata.io/api/v1/badge.svg?chart=netdata.registry_entries&dimensions=machines&group=incremental-sum&after=-86400&options=unaligned&label=servers%20added%20today&units=null&value_color=orange&precision=0&v40)](https://registry.my-netdata.io/#netdata_registry)
->[![Sessions
->Today](https://registry.my-netdata.io/api/v1/badge.svg?chart=netdata.registry_sessions&after=-86400&group=incremental-sum&options=unaligned&label=sessions%20served%20today&units=null&value_color=yellowgreen&precision=0&v40)](https://registry.my-netdata.io/#netdata_registry)
+> Today](https://registry.my-netdata.io/api/v1/badge.svg?chart=netdata.registry_entries&dimensions=persons&after=-86400&options=unaligned&group=incremental-sum&label=new%20users%20today&units=null&value_color=blue&precision=0&v40)](https://registry.my-netdata.io/#netdata_registry)
+> [![New Machines
+> Today](https://registry.my-netdata.io/api/v1/badge.svg?chart=netdata.registry_entries&dimensions=machines&group=incremental-sum&after=-86400&options=unaligned&label=servers%20added%20today&units=null&value_color=orange&precision=0&v40)](https://registry.my-netdata.io/#netdata_registry)
+> [![Sessions
+> Today](https://registry.my-netdata.io/api/v1/badge.svg?chart=netdata.registry_sessions&after=-86400&group=incremental-sum&options=unaligned&label=sessions%20served%20today&units=null&value_color=yellowgreen&precision=0&v40)](https://registry.my-netdata.io/#netdata_registry)
---
-Netdata is a [very efficient](/docs/guides/configure/performance.md) server performance monitoring solution. When running in server hardware, it can collect
+Netdata is a [very efficient](https://github.com/netdata/netdata/blob/master/docs/guides/configure/performance.md)
+server performance monitoring solution. When running in server hardware, it can collect
thousands of system and application metrics **per second** with just 1% CPU utilization of a single core. Its web server
responds to most data requests in about **half a millisecond** making its web dashboards spontaneous, amazingly fast!
@@ -43,8 +44,8 @@ provider so it can directly be used by google sheets, google charts, google widg
![sensors](https://cloud.githubusercontent.com/assets/2662304/15339745/8be84540-1c8e-11e6-9e9a-106dea7539b6.gif)
Although Netdata has been significantly optimized to lower the CPU and RAM resources it consumes, the plethora of data
-collection plugins may be inappropriate for weak IoT devices. Please follow the [Netdata Agent performance
-guide](/docs/guides/configure/performance.md)
+collection plugins may be inappropriate for weak IoT devices. Please follow
+the [Netdata Agent performance guide](https://github.com/netdata/netdata/blob/master/docs/guides/configure/performance.md)
## Monitoring RPi temperature
diff --git a/docs/netdata-security.md b/docs/netdata-security.md
index 9bb26ad2..511bc772 100644
--- a/docs/netdata-security.md
+++ b/docs/netdata-security.md
@@ -200,12 +200,12 @@ Of course, there are many more methods you could use to protect Netdata:
### Registry or how to not send any information to a third party server
-The default configuration uses a public registry under registry.my-netdata.io (more information about the registry here: [mynetdata-menu-item](/registry/README.md) ). Please be aware that if you use that public registry, you submit the following information to a third party server:
+The default configuration uses a public registry under registry.my-netdata.io (more information about the registry here: [mynetdata-menu-item](https://github.com/netdata/netdata/blob/master/registry/README.md) ). Please be aware that if you use that public registry, you submit the following information to a third party server:
- The url where you open the web-ui in the browser (via http request referrer)
- The hostnames of the Netdata servers
-If sending this information to the central Netdata registry violates your security policies, you can configure Netdata to [run your own registry](/registry/README.md#run-your-own-registry).
+If sending this information to the central Netdata registry violates your security policies, you can configure Netdata to [run your own registry](https://github.com/netdata/netdata/blob/master/registry/README.md#run-your-own-registry).
### Opt-out of anonymous statistics
diff --git a/docs/overview/netdata-monitoring-stack.md b/docs/overview/netdata-monitoring-stack.md
index ae925227..36f5b5f0 100644
--- a/docs/overview/netdata-monitoring-stack.md
+++ b/docs/overview/netdata-monitoring-stack.md
@@ -22,7 +22,7 @@ Here are a few ways to enrich your existing monitoring and troubleshooting stack
## Collect metrics from Prometheus endpoints
Netdata automatically detects 600 popular endpoints and collects per-second metrics from them via the [generic
-Prometheus collector](https://learn.netdata.cloud/docs/agent/collectors/go.d.plugin/modules/prometheus). This even
+Prometheus collector](https://github.com/netdata/go.d.plugin/blob/master/modules/prometheus/README.md). This even
includes support for Windows 10 via [`windows_exporter`](https://github.com/prometheus-community/windows_exporter).
This collector is installed and enabled on all Agent installations by default, so you don't need to waste time
@@ -35,8 +35,8 @@ troubleshoot anomalies.
Netdata can send its per-second metrics to external time-series databases, such as InfluxDB, Prometheus, Graphite,
TimescaleDB, ElasticSearch, AWS Kinesis Data Streams, Google Cloud Pub/Sub Service, and many others.
-To [export metrics to external time-series databases](/docs/export/external-databases.md), you configure an [exporting
-_connector_](/docs/export/enable-connector.md). These connectors support filtering and resampling for granular control
+To [export metrics to external time-series databases](https://github.com/netdata/netdata/blob/master/docs/export/external-databases.md), you configure an [exporting
+_connector_](https://github.com/netdata/netdata/blob/master/docs/export/enable-connector.md). These connectors support filtering and resampling for granular control
over which metrics you export, and at what volume. You can export resampled metrics as collected, as averages, or the
sum of interpolated values based on your needs and other monitoring tools.
@@ -57,6 +57,6 @@ charts, or use Netdata's health watchdog to send notifications whenever an anoma
## What's next?
Whether you're using Netdata standalone or as part of a larger monitoring stack, the next step is the same: [**Get
-Netdata**](/docs/get-started.mdx).
+Netdata**](https://github.com/netdata/netdata/blob/master/docs/get-started.mdx).
diff --git a/docs/overview/what-is-netdata.md b/docs/overview/what-is-netdata.md
index 3df1d949..f8e67159 100644
--- a/docs/overview/what-is-netdata.md
+++ b/docs/overview/what-is-netdata.md
@@ -18,7 +18,8 @@ Netdata's distributed monitoring Agent collects thousands of metrics from system
configuration. It runs permanently on all your physical/virtual servers, containers, cloud deployments, and edge/IoT
devices.
-You can [install](/docs/get-started.mdx) Netdata on most Linux distributions (Ubuntu, Debian, CentOS, and more),
+You can [install](https://github.com/netdata/netdata/blob/master/docs/get-started.mdx) Netdata on most Linux
+distributions (Ubuntu, Debian, CentOS, and more),
container/microservice platforms (Kubernetes clusters, Docker), and many other operating systems (FreeBSD, macOS), with
no `sudo` required.
@@ -46,29 +47,30 @@ you're viewing the Netdata Cloud interface.
Netdata is designed to be both simple to use and flexible for every monitoring, visualization, and troubleshooting use
case:
-- **Collect**: Netdata collects all available metrics from your system and applications with 300+ collectors,
- Kubernetes service discovery, and in-depth container monitoring, all while using only 1% CPU and a few MB of RAM. It
- even collects metrics from Windows machines.
-- **Visualize**: The dashboard meaningfully presents charts to help you understand the relationships between your
- hardware, operating system, running apps/services, and the rest of your infrastructure. Add nodes to Netdata Cloud
- for a complete view of your infrastructure from a single pane of glass.
-- **Monitor**: Netdata's health watchdog uses hundreds of preconfigured alarms to notify you via Slack, email,
- PagerDuty and more when an anomaly strikes. Customize with dynamic thresholds, hysteresis, alarm templates, and
- role-based notifications.
-- **Troubleshoot**: 1s granularity helps you detect and analyze anomalies other monitoring platforms might have
- missed. Interactive visualizations reduce your reliance on the console, and historical metrics help you trace issues
- back to their root cause.
-- **Store**: Netdata's efficient database engine efficiently stores per-second metrics for days, weeks, or even
- months. Every distributed node stores metrics locally, simplifying deployment, slashing costs, and enriching
- Netdata's interactive dashboards.
-- **Export**: Integrate per-second metrics with other time-series databases like Graphite, Prometheus, InfluxDB,
- TimescaleDB, and more with Netdata's interoperable and extensible core.
-- **Stream**: Aggregate metrics from any number of distributed nodes in one place for in-depth analysis, including
- ephemeral nodes in a Kubernetes cluster.
+- **Collect**: Netdata collects all available metrics from your system and applications with 300+ collectors,
+ Kubernetes service discovery, and in-depth container monitoring, all while using only 1% CPU and a few MB of RAM. It
+ even collects metrics from Windows machines.
+- **Visualize**: The dashboard meaningfully presents charts to help you understand the relationships between your
+ hardware, operating system, running apps/services, and the rest of your infrastructure. Add nodes to Netdata Cloud
+ for a complete view of your infrastructure from a single pane of glass.
+- **Monitor**: Netdata's health watchdog uses hundreds of preconfigured alarms to notify you via Slack, email,
+ PagerDuty and more when an anomaly strikes. Customize with dynamic thresholds, hysteresis, alarm templates, and
+ role-based notifications.
+- **Troubleshoot**: 1s granularity helps you detect and analyze anomalies other monitoring platforms might have
+ missed. Interactive visualizations reduce your reliance on the console, and historical metrics help you trace issues
+ back to their root cause.
+- **Store**: Netdata's efficient database engine efficiently stores per-second metrics for days, weeks, or even
+ months. Every distributed node stores metrics locally, simplifying deployment, slashing costs, and enriching
+ Netdata's interactive dashboards.
+- **Export**: Integrate per-second metrics with other time-series databases like Graphite, Prometheus, InfluxDB,
+ TimescaleDB, and more with Netdata's interoperable and extensible core.
+- **Stream**: Aggregate metrics from any number of distributed nodes in one place for in-depth analysis, including
+ ephemeral nodes in a Kubernetes cluster.
## What's next?
-Learn more about [why you should use Netdata](/docs/overview/why-netdata.md), or [how Netdata works with your existing
-monitoring stack](/docs/overview/netdata-monitoring-stack.md).
+Learn more
+about [why you should use Netdata](https://github.com/netdata/netdata/blob/master/docs/overview/why-netdata.md),
+or [how Netdata works with your existing monitoring stack](https://github.com/netdata/netdata/blob/master/docs/overview/netdata-monitoring-stack.md).
diff --git a/docs/overview/why-netdata.md b/docs/overview/why-netdata.md
index 9a308f25..158bc50d 100644
--- a/docs/overview/why-netdata.md
+++ b/docs/overview/why-netdata.md
@@ -58,6 +58,6 @@ open-source tools.
Whether you already have a monitoring stack you want to integrate Netdata into, or are building something from the
ground-up, you should read more on how Netdata can work either [standalone or as an interoperable part of a monitoring
-stack](/docs/overview/netdata-monitoring-stack.md).
+stack](https://github.com/netdata/netdata/blob/master/docs/overview/netdata-monitoring-stack.md).
diff --git a/docs/quickstart/infrastructure.md b/docs/quickstart/infrastructure.md
index 9db66c05..23986b00 100644
--- a/docs/quickstart/infrastructure.md
+++ b/docs/quickstart/infrastructure.md
@@ -12,7 +12,7 @@ nodes running the Netdata Agent. A node is any system in your infrastructure tha
physical or virtual machine (VM), container, cloud deployment, or edge/IoT device.
The Netdata Agent uses zero-configuration collectors to gather metrics from every application and container instantly,
-and uses Netdata's [distributed data architecture](/docs/store/distributed-data-architecture.md) to store metrics
+and uses Netdata's [distributed data architecture](https://github.com/netdata/netdata/blob/master/docs/store/distributed-data-architecture.md) to store metrics
locally. Without a slow and troublesome centralized data lake for your infrastructure's metrics, you reduce the
resources you need to invest in, and the complexity of, monitoring your infrastructure.
@@ -27,12 +27,12 @@ your nodes to maximize the value you get from Netdata.
This quickstart assumes you've installed the Netdata Agent on more than one node in your infrastructure, and connected
those nodes to your Space in Netdata Cloud. If you haven't yet, see the [Netdata
-Cloud](https://learn.netdata.cloud/docs/cloud) docs for details on signing up for Netdata Cloud, installation, and
+Cloud](https://github.com/netdata/netdata/blob/master/docs/cloud/cloud.mdx) docs for details on signing up for Netdata Cloud, installation, and
connection process.
> If you want to monitor a Kubernetes cluster with Netdata, see our [k8s installation
-> doc](/packaging/installer/methods/kubernetes.md) for setup details, and then read our guide, [_Monitor a Kubernetes
-> cluster with Netdata_](/docs/guides/monitor/kubernetes-k8s-netdata.md).
+> doc](https://github.com/netdata/netdata/blob/master/packaging/installer/methods/kubernetes.md) for setup details, and then read our guide, [_Monitor a Kubernetes
+> cluster with Netdata_](https://github.com/netdata/netdata/blob/master/docs/guides/monitor/kubernetes-k8s-netdata.md).
## Set up your Netdata Cloud experience
@@ -49,11 +49,11 @@ SRE team for the user-facing SaaS application, and a second IT team for managing
don't monitor the same nodes, they can work in separate Spaces and then further organize their nodes into War Rooms.
Next, set up War Rooms. Netdata Cloud creates dashboards and visualizations based on the nodes added to a given War
-Room. You can [organize War Rooms](https://learn.netdata.cloud/docs/cloud/war-rooms#war-room-organization) in any way
+Room. You can [organize War Rooms](https://github.com/netdata/netdata/blob/master/docs/cloud/war-rooms.md#war-room-organization) in any way
you want, such as by the application type, for end-to-end application monitoring, or as an incident response tool.
-Learn more about [Spaces](https://learn.netdata.cloud/docs/cloud/spaces) and [War
-Rooms](https://learn.netdata.cloud/docs/cloud/war-rooms), including how to manage each, in their respective reference
+Learn more about [Spaces](https://github.com/netdata/netdata/blob/master/docs/cloud/spaces.md) and [War
+Rooms](https://github.com/netdata/netdata/blob/master/docs/cloud/war-rooms.md), including how to manage each, in their respective reference
documentation.
### Invite your team
@@ -63,25 +63,25 @@ inviting others, you can better synchronize with your team or colleagues to unde
When something goes wrong, you'll be ready to collaboratively troubleshoot complex performance problems from a single
pane of glass.
-To [invite new users](https://learn.netdata.cloud/docs/cloud/manage/invite-your-team), click on **Invite Users** in the
+To [invite new users](https://github.com/netdata/netdata/blob/master/docs/cloud/manage/invite-your-team.md), click on **Invite Users** in the
Space management Area. Choose which War Rooms to add this user to, then click **Send**.
If your team members have trouble signing in, direct them to the [Netdata Cloud sign
-in](https://learn.netdata.cloud/docs/cloud/manage/sign-in) doc.
+in](https://github.com/netdata/netdata/blob/master/docs/cloud/manage/sign-in.mdx) doc.
### See an overview of your infrastructure
The default way to visualize the health and performance of an infrastructure with Netdata Cloud is the
-[**Overview**](/docs/visualize/overview-infrastructure.md), which is the default interface of every War Room. The
+[**Overview**](https://github.com/netdata/netdata/blob/master/docs/visualize/overview-infrastructure.md), which is the default interface of every War Room. The
Overview features composite charts, which display aggregated metrics from every node in a given War Room. These metrics
are streamed on-demand from individual nodes and composited onto a single, familiar dashboard.
![The War Room
Overview](https://user-images.githubusercontent.com/1153921/108732681-09791980-74eb-11eb-9ba2-98cb1b6608de.png)
-Read more about the Overview in the [infrastructure overview](/docs/visualize/overview-infrastructure.md) doc.
+Read more about the Overview in the [infrastructure overview](https://github.com/netdata/netdata/blob/master/docs/visualize/overview-infrastructure.md) doc.
-Netdata Cloud also features the [**Nodes view**](https://learn.netdata.cloud/docs/cloud/visualize/nodes), which you can
+Netdata Cloud also features the [**Nodes view**](https://github.com/netdata/netdata/blob/master/docs/cloud/visualize/nodes.md), which you can
use to configure and see a few key metrics from every node in the War Room, view health status, and more.
### Drill down to specific nodes
@@ -91,8 +91,8 @@ single-node dashboards in Netdata Cloud to drill down on specific issues, scrub
historical data, and see like metrics presented meaningfully to help you troubleshoot performance problems.
Read about the process in the [infrastructure
-overview](/docs/visualize/overview-infrastructure.md#drill-down-with-single-node-dashboards) doc, then learn about [interacting with
-dashboards and charts](/docs/visualize/interact-dashboards-charts.md) to get the most from all of Netdata's real-time
+overview](https://github.com/netdata/netdata/blob/master/docs/visualize/overview-infrastructure.md#drill-down-with-single-node-dashboards) doc, then learn about [interacting with
+dashboards and charts](https://github.com/netdata/netdata/blob/master/docs/visualize/interact-dashboards-charts.md) to get the most from all of Netdata's real-time
metrics.
### Create new dashboards
@@ -104,7 +104,7 @@ from every node in your infrastructure on a single dashboard.
![An example system CPU
dashboard](https://user-images.githubusercontent.com/1153921/108732974-4b09c480-74eb-11eb-87a2-c67e569c08b6.png)
-Read more about [creating new dashboards](/docs/visualize/create-dashboards.md) for more details about the process and
+Read more about [creating new dashboards](https://github.com/netdata/netdata/blob/master/docs/visualize/create-dashboards.md) for more details about the process and
additional tips on best leveraging the feature to help you troubleshoot complex performance problems.
## Set up your nodes
@@ -131,25 +131,25 @@ cd /etc/netdata
sudo ./edit-config netdata.conf
```
-Our [configuration basics doc](/docs/configure/nodes.md) contains more information about `netdata.conf`, `edit-config`,
+Our [configuration basics doc](https://github.com/netdata/netdata/blob/master/docs/configure/nodes.md) contains more information about `netdata.conf`, `edit-config`,
along with simple examples to get you familiar with editing your node's configuration.
-After you've learned the basics, you should [secure your infrastructure's nodes](/docs/configure/secure-nodes.md) using
+After you've learned the basics, you should [secure your infrastructure's nodes](https://github.com/netdata/netdata/blob/master/docs/configure/secure-nodes.md) using
one of our recommended methods. These security best practices ensure no untrusted parties gain access to the metrics
collected on any of your nodes.
### Collect metrics from systems and applications
-Netdata has [300+ pre-installed collectors](/collectors/COLLECTORS.md) that gather thousands of metrics with zero
+Netdata has [300+ pre-installed collectors](https://github.com/netdata/netdata/blob/master/collectors/COLLECTORS.md) that gather thousands of metrics with zero
configuration. Collectors search each of your nodes in default locations and ports to find running applications and
gather as many metrics as they can without you having to configure them individually.
Most collectors work without configuration, but you should read up on [how collectors
-work](/docs/collect/how-collectors-work.md) and [how to enable/configure](/docs/collect/enable-configure.md) them so
+work](https://github.com/netdata/netdata/blob/master/docs/collect/how-collectors-work.md) and [how to enable/configure](https://github.com/netdata/netdata/blob/master/docs/collect/enable-configure.md) them so
that you can see metrics from those applications in Netdata Cloud.
-In addition, find detailed information about which [system](/docs/collect/system-metrics.md),
-[container](/docs/collect/container-metrics.md), and [application](/docs/collect/application-metrics.md) metrics you can
+In addition, find detailed information about which [system](https://github.com/netdata/netdata/blob/master/docs/collect/system-metrics.md),
+[container](https://github.com/netdata/netdata/blob/master/docs/collect/container-metrics.md), and [application](https://github.com/netdata/netdata/blob/master/docs/collect/application-metrics.md) metrics you can
collect from across your infrastructure with Netdata.
## What's next?
@@ -158,28 +158,28 @@ Netdata has many features that help you monitor the health of your nodes and tro
Once you have a handle on configuration and are collecting all the right metrics, try out some of Netdata's other
infrastructure-focused features:
-- [See an overview of your infrastructure](/docs/visualize/overview-infrastructure.md) using Netdata Cloud's composite
+- [See an overview of your infrastructure](https://github.com/netdata/netdata/blob/master/docs/visualize/overview-infrastructure.md) using Netdata Cloud's composite
charts and real-time visualizations.
-- [Create new dashboards](/docs/visualize/create-dashboards.md) from any number of nodes and metrics in Netdata Cloud.
+- [Create new dashboards](https://github.com/netdata/netdata/blob/master/docs/visualize/create-dashboards.md) from any number of nodes and metrics in Netdata Cloud.
To change how the Netdata Agent runs on each node, dig in to configuration files:
-- [Change how long nodes in your infrastructure retain metrics](/docs/store/change-metrics-storage.md) based on how
+- [Change how long nodes in your infrastructure retain metrics](https://github.com/netdata/netdata/blob/master/docs/store/change-metrics-storage.md) based on how
many metrics each node collects, your preferred retention period, and the resources you want to dedicate toward
long-term metrics retention.
-- [Create new alarms](/docs/monitor/configure-alarms.md), or tweak some of the pre-configured alarms, to stay on top
+- [Create new alarms](https://github.com/netdata/netdata/blob/master/docs/monitor/configure-alarms.md), or tweak some of the pre-configured alarms, to stay on top
of anomalies.
-- [Enable notifications](/docs/monitor/enable-notifications.md) to Slack, PagerDuty, email, and 30+ other services.
-- [Export metrics](/docs/export/external-databases.md) to an external time-series database to use Netdata alongside
+- [Enable notifications](https://github.com/netdata/netdata/blob/master/docs/monitor/enable-notifications.md) to Slack, PagerDuty, email, and 30+ other services.
+- [Export metrics](https://github.com/netdata/netdata/blob/master/docs/export/external-databases.md) to an external time-series database to use Netdata alongside
other monitoring and troubleshooting tools.
### Related reference documentation
-- [Netdata Cloud · Spaces](https://learn.netdata.cloud/docs/cloud/spaces)
-- [Netdata Cloud · War Rooms](https://learn.netdata.cloud/docs/cloud/war-rooms)
-- [Netdata Cloud · Invite your team](https://learn.netdata.cloud/docs/cloud/manage/invite-your-team)
+- [Netdata Cloud · Spaces](https://github.com/netdata/netdata/blob/master/docs/cloud/spaces.md)
+- [Netdata Cloud · War Rooms](https://github.com/netdata/netdata/blob/master/docs/cloud/war-rooms.md)
+- [Netdata Cloud · Invite your team](https://github.com/netdata/netdata/blob/master/docs/cloud/manage/invite-your-team.md)
- [Netdata Cloud · Sign in or sign up with email, Google, or
- GitHub](https://learn.netdata.cloud/docs/cloud/manage/sign-in)
-- [Netdata Cloud · Nodes view](https://learn.netdata.cloud/docs/cloud/visualize/nodes)
+ GitHub](https://github.com/netdata/netdata/blob/master/docs/cloud/manage/sign-in.mdx)
+- [Netdata Cloud · Nodes view](https://github.com/netdata/netdata/blob/master/docs/cloud/visualize/nodes.md)
diff --git a/docs/quickstart/single-node.md b/docs/quickstart/single-node.md
index 7855a487..29373191 100644
--- a/docs/quickstart/single-node.md
+++ b/docs/quickstart/single-node.md
@@ -36,7 +36,7 @@ To see a node's dashboard in Netdata Cloud, [sign in](https://app.netdata.cloud)
dashboard](https://user-images.githubusercontent.com/1153921/87457036-9b678e00-c5bc-11ea-977d-ad561a73beef.png)
Once you've decided which dashboard you prefer, learn about [interacting with dashboards and
-charts](/docs/visualize/interact-dashboards-charts.md) to get the most from Netdata's real-time metrics.
+charts](https://github.com/netdata/netdata/blob/master/docs/visualize/interact-dashboards-charts.md) to get the most from Netdata's real-time metrics.
## Configure your node
@@ -50,26 +50,26 @@ cd /etc/netdata
sudo ./edit-config netdata.conf
```
-Our [configuration basics doc](/docs/configure/nodes.md) contains more information about `netdata.conf`, `edit-config`,
+Our [configuration basics doc](https://github.com/netdata/netdata/blob/master/docs/configure/nodes.md) contains more information about `netdata.conf`, `edit-config`,
along with simple examples to get you familiar with editing your node's configuration.
-After you've learned the basics, you should [secure your node](/docs/configure/secure-nodes.md) using one of our
+After you've learned the basics, you should [secure your node](https://github.com/netdata/netdata/blob/master/docs/configure/secure-nodes.md) using one of our
recommended methods. These security best practices ensure no untrusted parties gain access to your dashboard or its
metrics.
## Collect metrics from your system and applications
-Netdata has [300+ pre-installed collectors](/collectors/COLLECTORS.md) that gather thousands of metrics with zero
+Netdata has [300+ pre-installed collectors](https://github.com/netdata/netdata/blob/master/collectors/COLLECTORS.md) that gather thousands of metrics with zero
configuration. Collectors search your node in default locations and ports to find running applications and gather as
many metrics as possible without you having to configure them individually.
These metrics enrich both the local and Netdata Cloud dashboards.
Most collectors work without configuration, but you should read up on [how collectors
-work](/docs/collect/how-collectors-work.md) and [how to enable/configure](/docs/collect/enable-configure.md) them.
+work](https://github.com/netdata/netdata/blob/master/docs/collect/how-collectors-work.md) and [how to enable/configure](https://github.com/netdata/netdata/blob/master/docs/collect/enable-configure.md) them.
-In addition, find detailed information about which [system](/docs/collect/system-metrics.md),
-[container](/docs/collect/container-metrics.md), and [application](/docs/collect/application-metrics.md) metrics you can
+In addition, find detailed information about which [system](https://github.com/netdata/netdata/blob/master/docs/collect/system-metrics.md),
+[container](https://github.com/netdata/netdata/blob/master/docs/collect/container-metrics.md), and [application](https://github.com/netdata/netdata/blob/master/docs/collect/application-metrics.md) metrics you can
collect from across your infrastructure with Netdata.
## What's next?
@@ -78,15 +78,15 @@ Netdata has many features that help you monitor the health of your node and trou
Once you understand configuration, and are certain Netdata is collecting all the important metrics from your node, try
out some of Netdata's other visualization and health monitoring features:
-- [Build new dashboards](/docs/visualize/create-dashboards.md) to put disparate but relevant metrics onto a single
+- [Build new dashboards](https://github.com/netdata/netdata/blob/master/docs/visualize/create-dashboards.md) to put disparate but relevant metrics onto a single
interface.
-- [Create new alarms](/docs/monitor/configure-alarms.md), or tweak some of the pre-configured alarms, to stay on top
+- [Create new alarms](https://github.com/netdata/netdata/blob/master/docs/monitor/configure-alarms.md), or tweak some of the pre-configured alarms, to stay on top
of anomalies.
-- [Enable notifications](/docs/monitor/enable-notifications.md) to Slack, PagerDuty, email, and 30+ other services.
-- [Change how long your node stores metrics](/docs/store/change-metrics-storage.md) based on how many metrics it
+- [Enable notifications](https://github.com/netdata/netdata/blob/master/docs/monitor/enable-notifications.md) to Slack, PagerDuty, email, and 30+ other services.
+- [Change how long your node stores metrics](https://github.com/netdata/netdata/blob/master/docs/store/change-metrics-storage.md) based on how many metrics it
collects, your preferred retention period, and the resources you want to dedicate toward long-term metrics
retention.
-- [Export metrics](/docs/export/external-databases.md) to an external time-series database to use Netdata alongside
+- [Export metrics](https://github.com/netdata/netdata/blob/master/docs/export/external-databases.md) to an external time-series database to use Netdata alongside
other monitoring and troubleshooting tools.
diff --git a/docs/store/change-metrics-storage.md b/docs/store/change-metrics-storage.md
index c4b77d9a..e82393a6 100644
--- a/docs/store/change-metrics-storage.md
+++ b/docs/store/change-metrics-storage.md
@@ -1,12 +1,16 @@
<!--
title: "Change how long Netdata stores metrics"
description: "With a single configuration change, the Netdata Agent can store days, weeks, or months of metrics at its famous per-second granularity."
-custom_edit_url: https://github.com/netdata/netdata/edit/master/docs/store/change-metrics-storage.md
+custom_edit_url: "https://github.com/netdata/netdata/edit/master/docs/store/change-metrics-storage.md"
+sidebar_label: "Change how long Netdata stores metrics"
+learn_status: "Published"
+learn_topic_type: "Tasks"
+learn_rel_path: "Setup"
-->
# Change how long Netdata stores metrics
-The Netdata Agent uses a custom made time-series database (TSDB), named the [`dbengine`](/database/engine/README.md), to store metrics.
+The Netdata Agent uses a custom made time-series database (TSDB), named the [`dbengine`](https://github.com/netdata/netdata/blob/master/database/engine/README.md), to store metrics.
The default settings retain approximately two day's worth of metrics on a system collecting 2,000 metrics every second,
but the Netdata Agent is highly configurable if you want your nodes to store days, weeks, or months worth of per-second
@@ -39,7 +43,7 @@ if you want to store more metrics _specifically in memory_, you can increase the
:::tip
-We advise you to visit the [tiering mechanism](/database/engine/README.md#tiering) reference. This will help you
+We advise you to visit the [tiering mechanism](https://github.com/netdata/netdata/blob/master/database/engine/README.md#tiering) reference. This will help you
configure the Agent to retain metrics for longer periods.
:::
@@ -57,7 +61,7 @@ data retention according to your preferences.
## Edit `netdata.conf` with recommended database engine settings
Now that you have a recommended setting for your Agent's `dbengine`, open `netdata.conf` with
-[`edit-config`](/docs/configure/nodes.md#use-edit-config-to-edit-configuration-files) and look for the `[db]`
+[`edit-config`](https://github.com/netdata/netdata/blob/master/docs/configure/nodes.md#use-edit-config-to-edit-configuration-files) and look for the `[db]`
subsection. Change it to the recommended values you calculated from the calculator. For example:
```conf
@@ -76,23 +80,23 @@ subsection. Change it to the recommended values you calculated from the calculat
```
Save the file and restart the Agent with `sudo systemctl restart netdata`, or
-the [appropriate method](/docs/configure/start-stop-restart.md) for your system, to change the database engine's size.
+the [appropriate method](https://github.com/netdata/netdata/blob/master/docs/configure/start-stop-restart.md) for your system, to change the database engine's size.
## What's next?
If you have multiple nodes with the Netdata Agent installed, you
-can [stream metrics](/docs/metrics-storage-management/how-streaming-works.mdx) from any number of _child_ nodes to a _
+can [stream metrics](https://github.com/netdata/netdata/blob/master/docs/metrics-storage-management/how-streaming-works.mdx) from any number of _child_ nodes to a _
parent_ node and store metrics using a centralized time-series database. Streaming allows you to centralize your data,
run Agents as headless collectors, replicate data, and more.
Storing metrics with the database engine is completely interoperable
-with [exporting to other time-series databases](/docs/export/external-databases.md). With exporting, you can use the
-node's resources to surface metrics when [viewing dashboards](/docs/visualize/interact-dashboards-charts.md), while also
+with [exporting to other time-series databases](https://github.com/netdata/netdata/blob/master/docs/export/external-databases.md). With exporting, you can use the
+node's resources to surface metrics when [viewing dashboards](https://github.com/netdata/netdata/blob/master/docs/visualize/interact-dashboards-charts.md), while also
archiving metrics elsewhere for further analysis, visualization, or correlation with other tools.
### Related reference documentation
-- [Netdata Agent · Database engine](/database/engine/README.md)
-- [Netdata Agent · Database engine configuration option](/daemon/config/README.md#[db]-section-options)
+- [Netdata Agent · Database engine](https://github.com/netdata/netdata/blob/master/database/engine/README.md)
+- [Netdata Agent · Database engine configuration option](https://github.com/netdata/netdata/blob/master/daemon/config/README.md#[db]-section-options)
diff --git a/docs/store/distributed-data-architecture.md b/docs/store/distributed-data-architecture.md
index 62933cfe..96ae4d99 100644
--- a/docs/store/distributed-data-architecture.md
+++ b/docs/store/distributed-data-architecture.md
@@ -1,7 +1,11 @@
<!--
title: "Distributed data architecture"
description: "Netdata's distributed data architecture stores metrics on individual nodes for high performance and scalability using all your granular metrics."
-custom_edit_url: https://github.com/netdata/netdata/edit/master/docs/store/distributed-data-architecture.md
+custom_edit_url: "https://github.com/netdata/netdata/edit/master/docs/store/distributed-data-architecture.md"
+sidebar_label: "Distributed data architecture"
+learn_status: "Published"
+learn_topic_type: "Concepts"
+learn_rel_path: "Concepts"
-->
# Distributed data architecture
@@ -10,7 +14,7 @@ Netdata uses a distributed data architecture to help you collect and store per-s
Every node in your infrastructure, whether it's one or a thousand, stores the metrics it collects.
Netdata Cloud bridges the gap between many distributed databases by _centralizing the interface_ you use to query and
-visualize your nodes' metrics. When you [look at charts in Netdata Cloud](/docs/visualize/interact-dashboards-charts.md)
+visualize your nodes' metrics. When you [look at charts in Netdata Cloud](https://github.com/netdata/netdata/blob/master/docs/visualize/interact-dashboards-charts.md)
, the metrics values are queried directly from that node's database and securely streamed to Netdata Cloud, which
proxies them to your browser.
@@ -18,7 +22,7 @@ Netdata's distributed data architecture has a number of benefits:
- **Performance**: Every query to a node's database takes only a few milliseconds to complete for responsiveness when
viewing dashboards or using features
- like [Metric Correlations](https://learn.netdata.cloud/docs/cloud/insights/metric-correlations).
+ like [Metric Correlations](https://github.com/netdata/netdata/blob/master/docs/cloud/insights/metric-correlations.md).
- **Scalability**: As your infrastructure scales, install the Netdata Agent on every new node to immediately add it to
your monitoring solution without adding cost or complexity.
- **1-second granularity**: Without an expensive centralized data lake, you can store all of your nodes' per-second
@@ -53,17 +57,17 @@ of the Netdata Agent, without affecting disk space or memory requirements.
Any node running the Netdata Agent can store long-term metrics for any retention period, given you allocate the
appropriate amount of RAM and disk space.
-Read our document on changing [how long Netdata stores metrics](/docs/store/change-metrics-storage.md) on your nodes for
+Read our document on changing [how long Netdata stores metrics](https://github.com/netdata/netdata/blob/master/docs/store/change-metrics-storage.md) on your nodes for
details.
-You can also stream between nodes using [streaming](/streaming/README.md), allowing to replicate databases and create
+You can also stream between nodes using [streaming](https://github.com/netdata/netdata/blob/master/streaming/README.md), allowing to replicate databases and create
your own centralized data lake of metrics, if you choose to do so.
While a distributed data architecture is the default when monitoring infrastructure with Netdata, you can also configure
its behavior based on your needs or the type of infrastructure you manage.
To archive metrics to an external time-series database, such as InfluxDB, Graphite, OpenTSDB, Elasticsearch,
-TimescaleDB, and many others, see details on [integrating Netdata via exporting](/docs/export/external-databases.md).
+TimescaleDB, and many others, see details on [integrating Netdata via exporting](https://github.com/netdata/netdata/blob/master/docs/export/external-databases.md).
When you use the database engine to store your metrics, you can always perform a quick backup of a node's
`/var/cache/netdata/dbengine/` folder using the tool of your choice.
@@ -72,7 +76,7 @@ When you use the database engine to store your metrics, you can always perform a
Netdata Cloud does not store metric values.
-To enable certain features, such as [viewing active alarms](/docs/monitor/view-active-alarms.md)
+To enable certain features, such as [viewing active alarms](https://github.com/netdata/netdata/blob/master/docs/monitor/view-active-alarms.md)
or [filtering by hostname/service](https://learn.netdata.cloud/docs/cloud/war-rooms#node-filter), Netdata Cloud does
store configured alarms, their status, and a list of active collectors.
@@ -81,7 +85,7 @@ Netdata does not and never will sell your personal data or data about your deplo
## What's next?
You can configure the Netdata Agent to store days, weeks, or months worth of distributed, per-second data by
-[configuring the database engine](/docs/store/change-metrics-storage.md). Use our calculator to determine the system
+[configuring the database engine](https://github.com/netdata/netdata/blob/master/docs/store/change-metrics-storage.md). Use our calculator to determine the system
resources required to retain your desired amount of metrics, and expand or contract the database by editing a single
setting.
diff --git a/docs/visualize/create-dashboards.md b/docs/visualize/create-dashboards.md
index 696cd1a7..f4306f33 100644
--- a/docs/visualize/create-dashboards.md
+++ b/docs/visualize/create-dashboards.md
@@ -14,16 +14,18 @@ In the War Room you want to monitor with this dashboard, click on your War Room'
Add** button next to **Dashboards**. In the panel, give your new dashboard a name, and click **+ Add**.
Click the **Add Chart** button to add your first chart card. From the dropdown, select the node you want to add the
-chart from, then the context. Netdata Cloud shows you a preview of the chart before you finish adding it.
+chart from, then the context. Netdata Cloud shows you a preview of the chart before you finish adding it.
The **Add Text** button creates a new card with user-defined text, which you can use to describe or document a
particular dashboard's meaning and purpose. Enrich the dashboards you create with documentation or procedures on how to
-respond
+respond
![A bird's eye dashboard for a single
node](https://user-images.githubusercontent.com/1153921/102650776-a654ba80-4128-11eb-9a65-4f9801b03d4b.png)
-Charts in dashboards are [fully interactive](/docs/visualize/interact-dashboards-charts.md) and synchronized. You can
+Charts in dashboards
+are [fully interactive](https://github.com/netdata/netdata/blob/master/docs/visualize/interact-dashboards-charts.md) and
+synchronized. You can
pan through time, zoom, highlight specific timeframes, and more.
Move any card by clicking on their top panel and dragging them to a new location. Other cards re-sort to the grid system
@@ -38,7 +40,8 @@ more detail when troubleshooting an issue. Quickly jump to any node's dashboard
of any card to open a menu. Hit the **Go to Chart** item.
Netdata Cloud takes you to the same chart on that node's dashboard. You can now navigate all that node's metrics and
-[interact with charts](/docs/visualize/interact-dashboards-charts.md) to further investigate anomalies or troubleshoot
+[interact with charts](https://github.com/netdata/netdata/blob/master/docs/visualize/interact-dashboards-charts.md) to
+further investigate anomalies or troubleshoot
complex performance problems.
When viewing a single-node Cloud dashboard, you can also click on the add to dashboard icon <img
@@ -58,7 +61,9 @@ faster.
## What's next?
While it's useful to see real-time metrics on flexible dashboards, you need ways to know precisely when an anomaly
-strikes. Every Netdata Agent comes with a health watchdog that uses [alarms](/docs/monitor/configure-alarms.md) and
-[notifications](/docs/monitor/enable-notifications.md) to notify you of issues seconds after they strike.
+strikes. Every Netdata Agent comes with a health watchdog that
+uses [alarms](https://github.com/netdata/netdata/blob/master/docs/monitor/configure-alarms.md) and
+[notifications](https://github.com/netdata/netdata/blob/master/docs/monitor/enable-notifications.md) to notify you of
+issues seconds after they strike.
diff --git a/docs/visualize/interact-dashboards-charts.md b/docs/visualize/interact-dashboards-charts.md
index 5dbbf19a..bf6d7a01 100644
--- a/docs/visualize/interact-dashboards-charts.md
+++ b/docs/visualize/interact-dashboards-charts.md
@@ -6,30 +6,30 @@ custom_edit_url: https://github.com/netdata/netdata/edit/master/docs/visualize/i
# Interact with dashboards and charts
-> ⚠️ There is a new version of charts that is currently **only** available on [Netdata Cloud](https://learn.netdata.cloud/docs/cloud/visualize/interact-new-charts). We didn't
+> ⚠️ There is a new version of charts that is currently **only** available on [Netdata Cloud](https://github.com/netdata/netdata/blob/master/docs/cloud/visualize/interact-new-charts.md). We didn't
> want to keep this valuable feature from you, so after we get this into your hands on the Cloud, we will collect and implement your feedback to make sure we are providing the best possible version of the feature on the Netdata Agent dashboard as quickly as possible.
You can find Netdata's dashboards in two places: locally served at `http://NODE:19999` by the Netdata Agent, and in
Netdata Cloud. While you access these dashboards differently, they have similar interfaces, identical charts and
metrics, and you interact with both of them the same way.
-> If you're not sure which option is best for you, see our [single-node](/docs/quickstart/single-node.md) and
-> [infrastructure](/docs/quickstart/infrastructure.md) quickstart guides.
+> If you're not sure which option is best for you, see our [single-node](https://github.com/netdata/netdata/blob/master/docs/quickstart/single-node.md) and
+> [infrastructure](https://github.com/netdata/netdata/blob/master/docs/quickstart/infrastructure.md) quickstart guides.
Netdata dashboards are single, scrollable pages with many charts stacked on top of one another. As you scroll up or
down, charts appearing in your browser's viewport automatically load and update every second.
The dashboard is broken up into multiple **sections**, such as **System Overview**, **CPU**, **Disk**, which are
-automatically generated based on which [collectors](/docs/collect/how-collectors-work.md) begin collecting metrics when
+automatically generated based on which [collectors](https://github.com/netdata/netdata/blob/master/docs/collect/how-collectors-work.md) begin collecting metrics when
Netdata starts up. Sections also appear in the right-hand **menu**, along with submenus based on the contexts and
families Netdata creates for your node.
## Choose timeframes to visualize
Both the local Agent dashboard and Netdata Cloud feature time & date pickers to help you visualize specific points in
-time. In Netdata Cloud, the picker appears in the [Overview](/docs/visualize/overview-infrastructure.md), [Nodes
-view](https://learn.netdata.cloud/docs/cloud/visualize/nodes), [new
-dashboards](https://learn.netdata.cloud/docs/cloud/visualize/dashboards), and any single-node dashboards you visit.
+time. In Netdata Cloud, the picker appears in the [Overview](https://github.com/netdata/netdata/blob/master/docs/visualize/overview-infrastructure.md), [Nodes
+view](https://github.com/netdata/netdata/blob/master/docs/cloud/visualize/nodes.md), [new
+dashboards](https://github.com/netdata/netdata/blob/master/docs/cloud/visualize/dashboards.md), and any single-node dashboards you visit.
Local Agent dashboard:
@@ -45,8 +45,8 @@ Their behavior is identical. Use the Quick Selector to visualize generic timefra
select days, hours, minutes or seconds. Click **Apply** to re-render all visualizations with new metrics data, or
**Clear** to restore the default timeframe.
-See reference documentation for the [local Agent dashboard](/web/gui/README.md#time--date-picker) and [Netdata
-Cloud](https://learn.netdata.cloud/docs/cloud/war-rooms#time--date-picker) for additional context about how the time &
+See reference documentation for the [local Agent dashboard](https://github.com/netdata/netdata/blob/master/web/gui/README.md#time--date-picker) and [Netdata
+Cloud](https://github.com/netdata/netdata/blob/master/docs/cloud/war-rooms.md#time--date-picker) for additional context about how the time &
date picker behaves in each environment.
## Charts, dimensions, families, and contexts
@@ -68,7 +68,7 @@ A **context** groups several charts based on the types of metrics being collecte
this context to create individual charts and then groups them by family. You can always see the context of any chart by
looking at its name or hovering over the chart's date.
-See our [dashboard docs](/web/README.md#charts-contexts-families) for more information about the above distinctions
+See our [dashboard docs](https://github.com/netdata/netdata/blob/master/web/README.md#charts-contexts-families) for more information about the above distinctions
and how they're used across Netdata to meaningfully organize and present metrics.
## Interact with charts
@@ -107,25 +107,25 @@ height](https://user-images.githubusercontent.com/1153921/102652691-24b25c00-412
Netdata Cloud now supports composite charts in the Overview interface. Composite charts come with a few additional UI
elements and varied interactions, such as the location of dimensions and a utility bar for configuring the state of
individual composite charts. All of these details are covered in the [Overview
-reference](https://learn.netdata.cloud/docs/cloud/visualize/overview) doc.
+reference](https://github.com/netdata/netdata/blob/master/docs/cloud/visualize/overview.md) doc.
## What's next?
-Netdata Cloud users can [build new dashboards](/docs/visualize/create-dashboards.md) in just a few clicks. By
+Netdata Cloud users can [build new dashboards](https://github.com/netdata/netdata/blob/master/docs/visualize/create-dashboards.md) in just a few clicks. By
aggregating relevant metrics from any number of nodes onto a single interface, you can respond faster to anomalies,
perform more targeted troubleshooting, or keep tabs on a bird's eye view of your infrastructure.
If you're finished with dashboards for now, skip to Netdata's health watchdog for information on [creating or
-configuring](/docs/monitor/configure-alarms.md) alarms, and [send notifications](/docs/monitor/enable-notifications.md)
+configuring](https://github.com/netdata/netdata/blob/master/docs/monitor/configure-alarms.md) alarms, and [send notifications](https://github.com/netdata/netdata/blob/master/docs/monitor/enable-notifications.md)
to get informed when something goes wrong in your infrastructure.
### Related reference documentation
-- [Netdata Agent · Web dashboards overview](/web/README.md)
-- [Netdata Cloud · Interact with new charts](https://learn.netdata.cloud/docs/cloud/visualize/interact-new-charts)
-- [Netdata Cloud · War Rooms](https://learn.netdata.cloud/docs/cloud/war-rooms)
-- [Netdata Cloud · Overview](https://learn.netdata.cloud/docs/cloud/visualize/overview)
-- [Netdata Cloud · Nodes](https://learn.netdata.cloud/docs/cloud/visualize/nodes)
-- [Netdata Cloud · Build new dashboards](https://learn.netdata.cloud/docs/cloud/visualize/dashboards)
+- [Netdata Agent · Web dashboards overview](https://github.com/netdata/netdata/blob/master/web/README.md)
+- [Netdata Cloud · Interact with new charts](https://github.com/netdata/netdata/blob/master/docs/cloud/visualize/interact-new-charts.md)
+- [Netdata Cloud · War Rooms](https://github.com/netdata/netdata/blob/master/docs/cloud/war-rooms.md)
+- [Netdata Cloud · Overview](https://github.com/netdata/netdata/blob/master/docs/cloud/visualize/overview.md)
+- [Netdata Cloud · Nodes](https://github.com/netdata/netdata/blob/master/docs/cloud/visualize/nodes.md)
+- [Netdata Cloud · Build new dashboards](https://github.com/netdata/netdata/blob/master/docs/cloud/visualize/dashboards.md)
diff --git a/docs/visualize/overview-infrastructure.md b/docs/visualize/overview-infrastructure.md
index 4edbb0f3..0daddd97 100644
--- a/docs/visualize/overview-infrastructure.md
+++ b/docs/visualize/overview-infrastructure.md
@@ -7,7 +7,7 @@ custom_edit_url: https://github.com/netdata/netdata/edit/master/docs/visualize/o
# See an overview of your infrastructure
In Netdata Cloud, your nodes are organized into War Rooms. One of the two available views for a War Room is the
-[**Overview**](https://learn.netdata.cloud/docs/cloud/visualize/overview), which uses composite charts to display
+[**Overview**](https://github.com/netdata/netdata/blob/master/docs/cloud/visualize/overview.md), which uses composite charts to display
real-time, aggregated metrics from all the nodes (or a filtered selection) in a given War Room.
With Overview's composite charts, you can see your infrastructure from a single pane of glass, discover trends or
@@ -15,7 +15,7 @@ anomalies, then drill down with filtering or single-node dashboards to see more.
each chart visualizes average or sum metrics values from across 5 distributed nodes.
Netdata also supports robust Kubernetes monitoring using the Overview. Read our [deployment
-doc](/packaging/installer/methods/kubernetes.md) for details on visualizing Kubernetes metrics in Netdata Cloud.
+doc](https://github.com/netdata/netdata/blob/master/packaging/installer/methods/kubernetes.md) for details on visualizing Kubernetes metrics in Netdata Cloud.
![The War Room
Overview](https://user-images.githubusercontent.com/1153921/108732681-09791980-74eb-11eb-9ba2-98cb1b6608de.png)
@@ -32,8 +32,8 @@ Let's walk through some examples of using the Overview to monitor and troublesho
### Filter nodes and pick relevant times
While not exclusive to Overview, you can use two important features, [node
-filtering](https://learn.netdata.cloud/docs/cloud/war-rooms#node-filter) and the [time &amp; date
-picker](https://learn.netdata.cloud/docs/cloud/war-rooms#time--date-picker), to widen or narrow your infrastructure
+filtering](https://github.com/netdata/netdata/blob/master/docs/cloud/war-rooms.md#node-filter) and the [time &amp; date
+picker](https://github.com/netdata/netdata/blob/master/docs/cloud/war-rooms.md#time--date-picker), to widen or narrow your infrastructure
monitoring focus.
By default, the Overview shows composite charts aggregated from every node in the War Room, but you can change that
@@ -48,7 +48,7 @@ establishing a baseline of infrastructure performance or targeted root cause ana
For example, use the **Quick Selector** options to pick the 12-hour option first thing in the morning to check your
infrastructure for any odd behavior overnight. Use the 7-day option to observe trends between various days of the week.
-See the [War Rooms](https://learn.netdata.cloud/docs/cloud/war-rooms) docs for more details on both features.
+See the [War Rooms](https://github.com/netdata/netdata/blob/master/docs/cloud/war-rooms.md) docs for more details on both features.
### Configure composite charts to identify problems
@@ -60,7 +60,7 @@ affects a single node, a subset of nodes, or an entire infrastructure.
![Composite charts showing available and committed RAM across an
infrastructure](https://user-images.githubusercontent.com/1153921/99314892-0bae4680-281f-11eb-823e-071a1da25dc7.png)
-Use [_group by node_](https://learn.netdata.cloud/docs/cloud/visualize/overview#group-by-dimension-or-node) to visualize
+Use [_group by node_](https://github.com/netdata/netdata/blob/master/docs/cloud/visualize/overview.md#group-by-dimension-or-node) to visualize
a single metric across all contributing nodes. If the composite chart has 5 contributing nodes, there will be 5
lines/areas, one for the most relevant dimension from each node.
@@ -80,32 +80,32 @@ given node to quickly _jump to the same chart in that node's single-node dashboa
You can use single-node dashboards in Netdata Cloud to drill down on specific issues, scrub backward in time to
investigate historical data, and see like metrics presented meaningfully to help you troubleshoot performance problems.
-All of the familiar [interactions](/docs/visualize/interact-dashboards-charts.md) are available, as is adding any chart
-to a [new dashboard](/docs/visualize/create-dashboards.md).
+All of the familiar [interactions](https://github.com/netdata/netdata/blob/master/docs/visualize/interact-dashboards-charts.md) are available, as is adding any chart
+to a [new dashboard](https://github.com/netdata/netdata/blob/master/docs/visualize/create-dashboards.md).
## Nodes view
You can also use the **Nodes view** to monitor the health status and user-configurable key metrics from multiple nodes
-in a War Room. Read the [Nodes view doc](https://learn.netdata.cloud/docs/cloud/visualize/nodes) for details.
+in a War Room. Read the [Nodes view doc](https://github.com/netdata/netdata/blob/master/docs/cloud/visualize/nodes.md) for details.
![The Nodes view](https://user-images.githubusercontent.com/1153921/108733066-5fe65800-74eb-11eb-98e0-abaccd36deaf.png)
## What's next?
To troubleshoot complex performance issues using Netdata, you need to understand how to interact with its meaningful
-visualizations. Learn more about [interaction](/docs/visualize/interact-dashboards-charts.md) to see historical metrics,
+visualizations. Learn more about [interaction](https://github.com/netdata/netdata/blob/master/docs/visualize/interact-dashboards-charts.md) to see historical metrics,
highlight timeframes for targeted analysis, and more.
If you're a Kubernetes user, read about Netdata's [Kubernetes
-visualizations](https://learn.netdata.cloud/docs/cloud/visualize/kubernetes) for details about the health map and
+visualizations](https://github.com/netdata/netdata/blob/master/docs/cloud/visualize/kubernetes.md) for details about the health map and
time-series k8s charts, and our tutorial, [_Kubernetes monitoring with Netdata: Overview and
-visualizations_](/docs/guides/monitor/kubernetes-k8s-netdata.md), for a full walkthrough.
+visualizations_](https://github.com/netdata/netdata/blob/master/docs/guides/monitor/kubernetes-k8s-netdata.md), for a full walkthrough.
### Related reference documentation
-- [Netdata Cloud · War Rooms](https://learn.netdata.cloud/docs/cloud/war-rooms)
-- [Netdata Cloud · Overview](https://learn.netdata.cloud/docs/cloud/visualize/overview)
-- [Netdata Cloud · Nodes view](https://learn.netdata.cloud/docs/cloud/visualize/nodes)
-- [Netdata Cloud · Kubernetes visualizations](https://learn.netdata.cloud/docs/cloud/visualize/kubernetes)
+- [Netdata Cloud · War Rooms](https://github.com/netdata/netdata/blob/master/docs/cloud/war-rooms.md)
+- [Netdata Cloud · Overview](https://github.com/netdata/netdata/blob/master/docs/cloud/visualize/overview.md)
+- [Netdata Cloud · Nodes view](https://github.com/netdata/netdata/blob/master/docs/cloud/visualize/nodes.md)
+- [Netdata Cloud · Kubernetes visualizations](https://github.com/netdata/netdata/blob/master/docs/cloud/visualize/kubernetes.md)
diff --git a/docs/why-netdata/README.md b/docs/why-netdata/README.md
index c482ee94..9c3af5e7 100644
--- a/docs/why-netdata/README.md
+++ b/docs/why-netdata/README.md
@@ -11,19 +11,19 @@ custom_edit_url: https://github.com/netdata/netdata/edit/master/docs/why-netdata
Netdata is built around 4 principles:
-1. **[Per second data collection for all metrics.](/docs/why-netdata/1s-granularity.md)**
+1. **[Per second data collection for all metrics.](https://github.com/netdata/netdata/blob/master/docs/why-netdata/1s-granularity.md)**
_It is impossible to monitor a 2 second SLA, with 10 second metrics._
-2. **[Collect and visualize all the metrics from all possible sources.](/docs/why-netdata/unlimited-metrics.md)**
+2. **[Collect and visualize all the metrics from all possible sources.](https://github.com/netdata/netdata/blob/master/docs/why-netdata/unlimited-metrics.md)**
_To troubleshoot slowdowns, we need all the available metrics. The console should not provide more metrics._
-3. **[Meaningful presentation, optimized for visual anomaly detection.](/docs/why-netdata/meaningful-presentation.md)**
+3. **[Meaningful presentation, optimized for visual anomaly detection.](https://github.com/netdata/netdata/blob/master/docs/why-netdata/meaningful-presentation.md)**
_Metrics are a lot more than name-value pairs over time. The monitoring tool should know all the metrics. Users should not!_
-4. **[Immediate results, just install and use.](/docs/why-netdata/immediate-results.md)**
+4. **[Immediate results, just install and use.](https://github.com/netdata/netdata/blob/master/docs/why-netdata/immediate-results.md)**
_Most of our infrastructure is standardized. There is no point to configure everything metric by metric._
diff --git a/exporting/README.md b/exporting/README.md
index 60028a38..bc3ca1c7 100644
--- a/exporting/README.md
+++ b/exporting/README.md
@@ -1,8 +1,12 @@
<!--
title: "Exporting reference"
description: "With the exporting engine, you can archive your Netdata metrics to multiple external databases for long-term storage or further analysis."
-sidebar_label: Exporting reference
-custom_edit_url: https://github.com/netdata/netdata/edit/master/exporting/README.md
+sidebar_label: "Exporting reference"
+custom_edit_url: "https://github.com/netdata/netdata/edit/master/exporting/README.md"
+learn_status: "Published"
+learn_topic_type: "References"
+learn_rel_path: "References/Configuration"
+learn_doc_purpose: "Explain the exporting engine options and all of our the exporting connectors options"
-->
# Exporting reference
@@ -12,13 +16,13 @@ configuring, and monitoring Netdata's exporting engine, which allows you to send
databases.
For a quick introduction to the exporting engine's features, read our doc on [exporting metrics to time-series
-databases](/docs/export/external-databases.md), or jump in to [enabling a connector](/docs/export/enable-connector.md).
+databases](https://github.com/netdata/netdata/blob/master/docs/export/external-databases.md), or jump in to [enabling a connector](https://github.com/netdata/netdata/blob/master/docs/export/enable-connector.md).
The exporting engine has a modular structure and supports metric exporting via multiple exporting connector instances at
the same time. You can have different update intervals and filters configured for every exporting connector instance.
When you enable the exporting engine and a connector, the Netdata Agent exports metrics _beginning from the time you
-restart its process_, not the entire [database of long-term metrics](/docs/store/change-metrics-storage.md).
+restart its process_, not the entire [database of long-term metrics](https://github.com/netdata/netdata/blob/master/docs/store/change-metrics-storage.md).
Since Netdata collects thousands of metrics per server per second, which would easily congest any database server when
several Netdata servers are sending data to it, Netdata allows sending metrics at a lower frequency, by resampling them.
@@ -31,27 +35,27 @@ X seconds (though, it can send them per second if you need it to).
### Integration
The exporting engine uses a number of connectors to send Netdata metrics to external time-series databases. See our
-[list of supported databases](/docs/export/external-databases.md#supported-databases) for information on which
+[list of supported databases](https://github.com/netdata/netdata/blob/master/docs/export/external-databases.md#supported-databases) for information on which
connector to enable and configure for your database of choice.
-- [**AWS Kinesis Data Streams**](/exporting/aws_kinesis/README.md): Metrics are sent to the service in `JSON`
+- [**AWS Kinesis Data Streams**](https://github.com/netdata/netdata/blob/master/exporting/aws_kinesis/README.md): Metrics are sent to the service in `JSON`
format.
-- [**Google Cloud Pub/Sub Service**](/exporting/pubsub/README.md): Metrics are sent to the service in `JSON`
+- [**Google Cloud Pub/Sub Service**](https://github.com/netdata/netdata/blob/master/exporting/pubsub/README.md): Metrics are sent to the service in `JSON`
format.
-- [**Graphite**](/exporting/graphite/README.md): A plaintext interface. Metrics are sent to the database server as
+- [**Graphite**](https://github.com/netdata/netdata/blob/master/exporting/graphite/README.md): A plaintext interface. Metrics are sent to the database server as
`prefix.hostname.chart.dimension`. `prefix` is configured below, `hostname` is the hostname of the machine (can
also be configured). Learn more in our guide to [export and visualize Netdata metrics in
- Graphite](/docs/guides/export/export-netdata-metrics-graphite.md).
-- [**JSON** document databases](/exporting/json/README.md)
-- [**OpenTSDB**](/exporting/opentsdb/README.md): Use a plaintext or HTTP interfaces. Metrics are sent to
+ Graphite](https://github.com/netdata/netdata/blob/master/docs/guides/export/export-netdata-metrics-graphite.md).
+- [**JSON** document databases](https://github.com/netdata/netdata/blob/master/exporting/json/README.md)
+- [**OpenTSDB**](https://github.com/netdata/netdata/blob/master/exporting/opentsdb/README.md): Use a plaintext or HTTP interfaces. Metrics are sent to
OpenTSDB as `prefix.chart.dimension` with tag `host=hostname`.
-- [**MongoDB**](/exporting/mongodb/README.md): Metrics are sent to the database in `JSON` format.
-- [**Prometheus**](/exporting/prometheus/README.md): Use an existing Prometheus installation to scrape metrics
+- [**MongoDB**](https://github.com/netdata/netdata/blob/master/exporting/mongodb/README.md): Metrics are sent to the database in `JSON` format.
+- [**Prometheus**](https://github.com/netdata/netdata/blob/master/exporting/prometheus/README.md): Use an existing Prometheus installation to scrape metrics
from node using the Netdata API.
-- [**Prometheus remote write**](/exporting/prometheus/remote_write/README.md). A binary snappy-compressed protocol
+- [**Prometheus remote write**](https://github.com/netdata/netdata/blob/master/exporting/prometheus/remote_write/README.md). A binary snappy-compressed protocol
buffer encoding over HTTP. Supports many [storage
providers](https://prometheus.io/docs/operating/integrations/#remote-endpoints-and-storage).
-- [**TimescaleDB**](/exporting/TIMESCALE.md): Use a community-built connector that takes JSON streams from a
+- [**TimescaleDB**](https://github.com/netdata/netdata/blob/master/exporting/TIMESCALE.md): Use a community-built connector that takes JSON streams from a
Netdata client and writes them to a TimescaleDB table.
### Chart filtering
@@ -292,7 +296,7 @@ Configure individual connectors and override any global settings with the follow
Netdata can send metrics to external databases using the TLS/SSL protocol. Unfortunately, some of
them does not support encrypted connections, so you will have to configure a reverse proxy to enable
HTTPS communication between Netdata and an external database. You can set up a reverse proxy with
-[Nginx](/docs/Running-behind-nginx.md).
+[Nginx](https://github.com/netdata/netdata/blob/master/docs/Running-behind-nginx.md).
## Exporting engine monitoring
diff --git a/exporting/TIMESCALE.md b/exporting/TIMESCALE.md
index 07aa1b7a..2bd6db8c 100644
--- a/exporting/TIMESCALE.md
+++ b/exporting/TIMESCALE.md
@@ -1,8 +1,12 @@
<!--
title: "Writing metrics to TimescaleDB"
description: "Send Netdata metrics to TimescaleDB for long-term archiving and further analysis."
-custom_edit_url: https://github.com/netdata/netdata/edit/master/exporting/TIMESCALE.md
-sidebar_label: Writing metrics to TimescaleDB
+custom_edit_url: "https://github.com/netdata/netdata/edit/master/exporting/TIMESCALE.md"
+sidebar_label: "Writing metrics to TimescaleDB"
+learn_status: "Published"
+learn_topic_type: "Tasks"
+learn_rel_path: "Setup/Exporting connectors"
+learn_autogeneration_metadata: "{'part_of_cloud': False, 'part_of_agent': True}"
-->
# Writing metrics to TimescaleDB
diff --git a/exporting/WALKTHROUGH.md b/exporting/WALKTHROUGH.md
index 0612b298..5afd2604 100644
--- a/exporting/WALKTHROUGH.md
+++ b/exporting/WALKTHROUGH.md
@@ -1,8 +1,11 @@
<!--
title: "Exporting to Netdata, Prometheus, Grafana stack"
description: "Using Netdata in conjunction with Prometheus and Grafana."
-custom_edit_url: https://github.com/netdata/netdata/edit/master/exporting/WALKTHROUGH.md
-sidebar_label: Netdata, Prometheus, Grafana stack
+custom_edit_url: "https://github.com/netdata/netdata/edit/master/exporting/WALKTHROUGH.md"
+sidebar_label: "Netdata, Prometheus, Grafana stack"
+learn_status: "Published"
+learn_topic_type: "Tasks"
+learn_rel_path: "Setup/Exporting connectors"
-->
# Netdata, Prometheus, Grafana stack
@@ -64,7 +67,7 @@ command to run (`/bin/bash`) and then chooses the base container images (`centos
be sitting inside the shell of the container.
After we have entered the shell we can install Netdata. This process could not be easier. If you take a look at [this
-link](/packaging/installer/README.md), the Netdata devs give us several one-liners to install Netdata. I have not had
+link](https://github.com/netdata/netdata/blob/master/packaging/installer/README.md), the Netdata devs give us several one-liners to install Netdata. I have not had
any issues with these one liners and their bootstrapping scripts so far (If you guys run into anything do share). Run
the following command in your container.
@@ -223,7 +226,7 @@ the `chart` dimension. If you'd like you can combine the `chart` and `instance`
Let's give this a try: `netdata_system_cpu_percentage_average{chart="system.cpu", instance="netdata:19999"}`
This is the basics of using Prometheus to query Netdata. I'd advise everyone at this point to read [this
-page](/exporting/prometheus/README.md#using-netdata-with-prometheus). The key point here is that Netdata can export metrics from
+page](https://github.com/netdata/netdata/blob/master/exporting/prometheus/README.md#using-netdata-with-prometheus). The key point here is that Netdata can export metrics from
its internal DB or can send metrics _as-collected_ by specifying the `source=as-collected` URL parameter like so.
<http://localhost:19999/api/v1/allmetrics?format=prometheus&help=yes&types=yes&source=as-collected> If you choose to use
this method you will need to use Prometheus's set of functions here: <https://prometheus.io/docs/querying/functions/> to
diff --git a/exporting/aws_kinesis/README.md b/exporting/aws_kinesis/README.md
index 29dd3438..7921a265 100644
--- a/exporting/aws_kinesis/README.md
+++ b/exporting/aws_kinesis/README.md
@@ -1,8 +1,12 @@
<!--
title: "Export metrics to AWS Kinesis Data Streams"
description: "Archive your Agent's metrics to AWS Kinesis Data Streams for long-term storage, further analysis, or correlation with data from other sources."
-custom_edit_url: https://github.com/netdata/netdata/edit/master/exporting/aws_kinesis/README.md
-sidebar_label: AWS Kinesis Data Streams
+custom_edit_url: "https://github.com/netdata/netdata/edit/master/exporting/aws_kinesis/README.md"
+sidebar_label: "AWS Kinesis Data Streams"
+learn_status: "Published"
+learn_topic_type: "Tasks"
+learn_rel_path: "Setup/Exporting connectors"
+learn_autogeneration_metadata: "{'part_of_cloud': False, 'part_of_agent': True}"
-->
# Export metrics to AWS Kinesis Data Streams
@@ -50,7 +54,8 @@ Set AWS credentials and stream name:
stream name = your_stream_name
```
-Alternatively, you can set AWS credentials for the `netdata` user using AWS SDK for C++ [standard methods](https://docs.aws.amazon.com/sdk-for-cpp/v1/developer-guide/credentials.html).
+Alternatively, you can set AWS credentials for the `netdata` user using AWS SDK for
+C++ [standard methods](https://docs.aws.amazon.com/sdk-for-cpp/v1/developer-guide/credentials.html).
Netdata automatically computes a partition key for every record with the purpose to distribute records across
available shards evenly.
diff --git a/exporting/aws_kinesis/aws_kinesis.c b/exporting/aws_kinesis/aws_kinesis.c
index 1d89cc79..c7d7a9d3 100644
--- a/exporting/aws_kinesis/aws_kinesis.c
+++ b/exporting/aws_kinesis/aws_kinesis.c
@@ -52,7 +52,7 @@ int init_aws_kinesis_instance(struct instance *instance)
instance->prepare_header = NULL;
instance->check_response = NULL;
- instance->buffer = (void *)buffer_create(0);
+ instance->buffer = (void *)buffer_create(0, &netdata_buffers_statistics.buffers_exporters);
if (!instance->buffer) {
error("EXPORTING: cannot create buffer for AWS Kinesis exporting connector instance %s", instance->config.name);
return 1;
diff --git a/exporting/exporting_engine.c b/exporting/exporting_engine.c
index fd16d982..2ad8cdd9 100644
--- a/exporting/exporting_engine.c
+++ b/exporting/exporting_engine.c
@@ -197,7 +197,7 @@ void *exporting_main(void *ptr)
heartbeat_t hb;
heartbeat_init(&hb);
- while (!netdata_exit) {
+ while (service_running(SERVICE_EXPORTERS)) {
heartbeat_next(&hb, step_ut);
engine->now = now_realtime_sec();
diff --git a/exporting/graphite/README.md b/exporting/graphite/README.md
index 6c96c78c..afcdf798 100644
--- a/exporting/graphite/README.md
+++ b/exporting/graphite/README.md
@@ -1,14 +1,19 @@
<!--
title: "Export metrics to Graphite providers"
description: "Archive your Agent's metrics to a any Graphite database provider for long-term storage, further analysis, or correlation with data from other sources."
-custom_edit_url: https://github.com/netdata/netdata/edit/master/exporting/graphite/README.md
-sidebar_label: Graphite
+custom_edit_url: "https://github.com/netdata/netdata/edit/master/exporting/graphite/README.md"
+sidebar_label: "Graphite"
+learn_status: "Published"
+learn_topic_type: "Tasks"
+learn_rel_path: "Setup/Exporting connectors"
+learn_autogeneration_metadata: "{'part_of_cloud': False, 'part_of_agent': True}"
-->
# Export metrics to Graphite providers
-You can use the Graphite connector for the [exporting engine](/exporting/README.md) to archive your agent's metrics to
-Graphite providers for long-term storage, further analysis, or correlation with data from other sources.
+You can use the Graphite connector for
+the [exporting engine](https://github.com/netdata/netdata/blob/master/exporting/README.md) to archive your agent's
+metrics to Graphite providers for long-term storage, further analysis, or correlation with data from other sources.
## Configuration
@@ -21,7 +26,8 @@ directory and set the following options:
destination = localhost:2003
```
-Add `:http` or `:https` modifiers to the connector type if you need to use other than a plaintext protocol. For example: `graphite:http:my_graphite_instance`,
+Add `:http` or `:https` modifiers to the connector type if you need to use other than a plaintext protocol. For
+example: `graphite:http:my_graphite_instance`,
`graphite:https:my_graphite_instance`. You can set basic HTTP authentication credentials using
```conf
@@ -29,7 +35,7 @@ Add `:http` or `:https` modifiers to the connector type if you need to use other
password = my_password
```
-The Graphite connector is further configurable using additional settings. See the [exporting reference
-doc](/exporting/README.md#options) for details.
+The Graphite connector is further configurable using additional settings. See
+the [exporting reference doc](https://github.com/netdata/netdata/blob/master/exporting/README.md#options) for details.
diff --git a/exporting/graphite/graphite.c b/exporting/graphite/graphite.c
index 0b33f642..f1964f3e 100644
--- a/exporting/graphite/graphite.c
+++ b/exporting/graphite/graphite.c
@@ -48,7 +48,7 @@ int init_graphite_instance(struct instance *instance)
instance->check_response = exporting_discard_response;
- instance->buffer = (void *)buffer_create(0);
+ instance->buffer = (void *)buffer_create(0, &netdata_buffers_statistics.buffers_exporters);
if (!instance->buffer) {
error("EXPORTING: cannot create buffer for graphite exporting connector instance %s", instance->config.name);
return 1;
@@ -96,7 +96,7 @@ void sanitize_graphite_label_value(char *dst, const char *src, size_t len)
int format_host_labels_graphite_plaintext(struct instance *instance, RRDHOST *host)
{
if (!instance->labels_buffer)
- instance->labels_buffer = buffer_create(1024);
+ instance->labels_buffer = buffer_create(1024, &netdata_buffers_statistics.buffers_exporters);
if (unlikely(!sending_labels_configured(instance)))
return 0;
diff --git a/exporting/init_connectors.c b/exporting/init_connectors.c
index bfb6525e..15e1951f 100644
--- a/exporting/init_connectors.c
+++ b/exporting/init_connectors.c
@@ -171,8 +171,8 @@ void simple_connector_init(struct instance *instance)
if (connector_specific_data->first_buffer)
return;
- connector_specific_data->header = buffer_create(0);
- connector_specific_data->buffer = buffer_create(0);
+ connector_specific_data->header = buffer_create(0, &netdata_buffers_statistics.buffers_exporters);
+ connector_specific_data->buffer = buffer_create(0, &netdata_buffers_statistics.buffers_exporters);
// create a ring buffer
struct simple_connector_buffer *first_buffer = NULL;
@@ -195,7 +195,7 @@ void simple_connector_init(struct instance *instance)
connector_specific_data->last_buffer = connector_specific_data->first_buffer;
if (*instance->config.username || *instance->config.password) {
- BUFFER *auth_string = buffer_create(0);
+ BUFFER *auth_string = buffer_create(0, &netdata_buffers_statistics.buffers_exporters);
buffer_sprintf(auth_string, "%s:%s", instance->config.username, instance->config.password);
diff --git a/exporting/json/README.md b/exporting/json/README.md
index d129ffbd..23ff555c 100644
--- a/exporting/json/README.md
+++ b/exporting/json/README.md
@@ -1,13 +1,17 @@
<!--
title: "Export metrics to JSON document databases"
description: "Archive your Agent's metrics to a JSON document database for long-term storage, further analysis, or correlation with data from other sources."
-custom_edit_url: https://github.com/netdata/netdata/edit/master/exporting/json/README.md
-sidebar_label: JSON Document Databases
+custom_edit_url: "https://github.com/netdata/netdata/edit/master/exporting/json/README.md"
+sidebar_label: "JSON Document Databases"
+learn_status: "Published"
+learn_topic_type: "Tasks"
+learn_rel_path: "Setup/Exporting connectors"
+learn_autogeneration_metadata: "{'part_of_cloud': False, 'part_of_agent': True}"
-->
# Export metrics to JSON document databases
-You can use the JSON connector for the [exporting engine](/exporting/README.md) to archive your agent's metrics to JSON
+You can use the JSON connector for the [exporting engine](https://github.com/netdata/netdata/blob/master/exporting/README.md) to archive your agent's metrics to JSON
document databases for long-term storage, further analysis, or correlation with data from other sources.
## Configuration
@@ -29,7 +33,7 @@ Add `:http` or `:https` modifiers to the connector type if you need to use other
password = my_password
```
-The JSON connector is further configurable using additional settings. See the [exporting reference
-doc](/exporting/README.md#options) for details.
+The JSON connector is further configurable using additional settings. See
+the [exporting reference doc](https://github.com/netdata/netdata/blob/master/exporting/README.md#options) for details.
diff --git a/exporting/json/json.c b/exporting/json/json.c
index dd53f6f0..4cafd4c0 100644
--- a/exporting/json/json.c
+++ b/exporting/json/json.c
@@ -37,7 +37,7 @@ int init_json_instance(struct instance *instance)
instance->check_response = exporting_discard_response;
- instance->buffer = (void *)buffer_create(0);
+ instance->buffer = (void *)buffer_create(0, &netdata_buffers_statistics.buffers_exporters);
if (!instance->buffer) {
error("EXPORTING: cannot create buffer for json exporting connector instance %s", instance->config.name);
return 1;
@@ -96,7 +96,7 @@ int init_json_http_instance(struct instance *instance)
instance->check_response = exporting_discard_response;
- instance->buffer = (void *)buffer_create(0);
+ instance->buffer = (void *)buffer_create(0, &netdata_buffers_statistics.buffers_exporters);
simple_connector_init(instance);
@@ -119,7 +119,7 @@ int init_json_http_instance(struct instance *instance)
int format_host_labels_json_plaintext(struct instance *instance, RRDHOST *host)
{
if (!instance->labels_buffer)
- instance->labels_buffer = buffer_create(1024);
+ instance->labels_buffer = buffer_create(1024, &netdata_buffers_statistics.buffers_exporters);
if (unlikely(!sending_labels_configured(instance)))
return 0;
diff --git a/exporting/mongodb/README.md b/exporting/mongodb/README.md
index b10d5471..0cbe8f05 100644
--- a/exporting/mongodb/README.md
+++ b/exporting/mongodb/README.md
@@ -1,14 +1,19 @@
<!--
title: "Export metrics to MongoDB"
description: "Archive your Agent's metrics to a MongoDB database for long-term storage, further analysis, or correlation with data from other sources."
-custom_edit_url: https://github.com/netdata/netdata/edit/master/exporting/mongodb/README.md
-sidebar_label: MongoDB
+custom_edit_url: "https://github.com/netdata/netdata/edit/master/exporting/mongodb/README.md"
+sidebar_label: "MongoDB"
+learn_status: "Published"
+learn_topic_type: "Tasks"
+learn_rel_path: "Setup/Exporting connectors"
+learn_autogeneration_metadata: "{'part_of_cloud': False, 'part_of_agent': True}"
-->
# Export metrics to MongoDB
-You can use the MongoDB connector for the [exporting engine](/exporting/README.md) to archive your agent's metrics to a
-MongoDB database for long-term storage, further analysis, or correlation with data from other sources.
+You can use the MongoDB connector for
+the [exporting engine](https://github.com/netdata/netdata/blob/master/exporting/README.md) to archive your agent's
+metrics to a MongoDB database for long-term storage, further analysis, or correlation with data from other sources.
## Prerequisites
diff --git a/exporting/mongodb/mongodb.c b/exporting/mongodb/mongodb.c
index 850d07fb..186a7dcf 100644
--- a/exporting/mongodb/mongodb.c
+++ b/exporting/mongodb/mongodb.c
@@ -106,7 +106,7 @@ int init_mongodb_instance(struct instance *instance)
instance->prepare_header = NULL;
instance->check_response = NULL;
- instance->buffer = (void *)buffer_create(0);
+ instance->buffer = (void *)buffer_create(0, &netdata_buffers_statistics.buffers_exporters);
if (!instance->buffer) {
error("EXPORTING: cannot create buffer for MongoDB exporting connector instance %s", instance->config.name);
return 1;
diff --git a/exporting/opentsdb/README.md b/exporting/opentsdb/README.md
index c9b1ab95..c6069f37 100644
--- a/exporting/opentsdb/README.md
+++ b/exporting/opentsdb/README.md
@@ -1,14 +1,19 @@
<!--
title: "Export metrics to OpenTSDB"
description: "Archive your Agent's metrics to an OpenTSDB database for long-term storage and further analysis."
-custom_edit_url: https://github.com/netdata/netdata/edit/master/exporting/opentsdb/README.md
-sidebar_label: OpenTSDB
+custom_edit_url: "https://github.com/netdata/netdata/edit/master/exporting/opentsdb/README.md"
+sidebar_label: "OpenTSDB"
+learn_status: "Published"
+learn_topic_type: "Tasks"
+learn_rel_path: "Setup/Exporting connectors"
+learn_autogeneration_metadata: "{'part_of_cloud': False, 'part_of_agent': True}"
-->
# Export metrics to OpenTSDB
-You can use the OpenTSDB connector for the [exporting engine](/exporting/README.md) to archive your agent's metrics to OpenTSDB
-databases for long-term storage, further analysis, or correlation with data from other sources.
+You can use the OpenTSDB connector for
+the [exporting engine](https://github.com/netdata/netdata/blob/master/exporting/README.md) to archive your agent's
+metrics to OpenTSDB databases for long-term storage, further analysis, or correlation with data from other sources.
## Configuration
@@ -21,7 +26,8 @@ directory and set the following options:
destination = localhost:4242
```
-Add `:http` or `:https` modifiers to the connector type if you need to use other than a plaintext protocol. For example: `opentsdb:http:my_opentsdb_instance`,
+Add `:http` or `:https` modifiers to the connector type if you need to use other than a plaintext protocol. For
+example: `opentsdb:http:my_opentsdb_instance`,
`opentsdb:https:my_opentsdb_instance`. You can set basic HTTP authentication credentials using
```conf
@@ -29,7 +35,7 @@ Add `:http` or `:https` modifiers to the connector type if you need to use other
password = my_password
```
-The OpenTSDB connector is further configurable using additional settings. See the [exporting reference
-doc](/exporting/README.md#options) for details.
+The OpenTSDB connector is further configurable using additional settings. See
+the [exporting reference doc](https://github.com/netdata/netdata/blob/master/exporting/README.md#options) for details.
diff --git a/exporting/opentsdb/opentsdb.c b/exporting/opentsdb/opentsdb.c
index a974c126..fc01ae46 100644
--- a/exporting/opentsdb/opentsdb.c
+++ b/exporting/opentsdb/opentsdb.c
@@ -45,7 +45,7 @@ int init_opentsdb_telnet_instance(struct instance *instance)
instance->prepare_header = NULL;
instance->check_response = exporting_discard_response;
- instance->buffer = (void *)buffer_create(0);
+ instance->buffer = (void *)buffer_create(0, &netdata_buffers_statistics.buffers_exporters);
if (!instance->buffer) {
error("EXPORTING: cannot create buffer for opentsdb telnet exporting connector instance %s", instance->config.name);
return 1;
@@ -102,7 +102,7 @@ int init_opentsdb_http_instance(struct instance *instance)
instance->prepare_header = opentsdb_http_prepare_header;
instance->check_response = exporting_discard_response;
- instance->buffer = (void *)buffer_create(0);
+ instance->buffer = (void *)buffer_create(0, &netdata_buffers_statistics.buffers_exporters);
if (!instance->buffer) {
error("EXPORTING: cannot create buffer for opentsdb HTTP exporting connector instance %s", instance->config.name);
return 1;
@@ -150,7 +150,7 @@ void sanitize_opentsdb_label_value(char *dst, const char *src, size_t len)
int format_host_labels_opentsdb_telnet(struct instance *instance, RRDHOST *host) {
if(!instance->labels_buffer)
- instance->labels_buffer = buffer_create(1024);
+ instance->labels_buffer = buffer_create(1024, &netdata_buffers_statistics.buffers_exporters);
if (unlikely(!sending_labels_configured(instance)))
return 0;
@@ -283,7 +283,7 @@ void opentsdb_http_prepare_header(struct instance *instance)
int format_host_labels_opentsdb_http(struct instance *instance, RRDHOST *host) {
if (!instance->labels_buffer)
- instance->labels_buffer = buffer_create(1024);
+ instance->labels_buffer = buffer_create(1024, &netdata_buffers_statistics.buffers_exporters);
if (unlikely(!sending_labels_configured(instance)))
return 0;
diff --git a/exporting/process_data.c b/exporting/process_data.c
index fbcda0d9..eb492535 100644
--- a/exporting/process_data.c
+++ b/exporting/process_data.c
@@ -77,8 +77,8 @@ NETDATA_DOUBLE exporting_calculate_value_from_stored_data(
time_t before = instance->before;
// find the edges of the rrd database for this chart
- time_t first_t = rd->tiers[0]->query_ops->oldest_time(rd->tiers[0]->db_metric_handle);
- time_t last_t = rd->tiers[0]->query_ops->latest_time(rd->tiers[0]->db_metric_handle);
+ time_t first_t = rd->tiers[0].query_ops->oldest_time_s(rd->tiers[0].db_metric_handle);
+ time_t last_t = rd->tiers[0].query_ops->latest_time_s(rd->tiers[0].db_metric_handle);
time_t update_every = st->update_every;
struct storage_engine_query_handle handle;
@@ -126,11 +126,11 @@ NETDATA_DOUBLE exporting_calculate_value_from_stored_data(
size_t counter = 0;
NETDATA_DOUBLE sum = 0;
- for (rd->tiers[0]->query_ops->init(rd->tiers[0]->db_metric_handle, &handle, after, before); !rd->tiers[0]->query_ops->is_finished(&handle);) {
- STORAGE_POINT sp = rd->tiers[0]->query_ops->next_metric(&handle);
+ for (rd->tiers[0].query_ops->init(rd->tiers[0].db_metric_handle, &handle, after, before, STORAGE_PRIORITY_LOW); !rd->tiers[0].query_ops->is_finished(&handle);) {
+ STORAGE_POINT sp = rd->tiers[0].query_ops->next_metric(&handle);
points_read++;
- if (unlikely(storage_point_is_empty(sp))) {
+ if (unlikely(storage_point_is_gap(sp))) {
// not collected
continue;
}
@@ -138,7 +138,7 @@ NETDATA_DOUBLE exporting_calculate_value_from_stored_data(
sum += sp.sum;
counter += sp.count;
}
- rd->tiers[0]->query_ops->finalize(&handle);
+ rd->tiers[0].query_ops->finalize(&handle);
global_statistics_exporters_query_completed(points_read);
if (unlikely(!counter)) {
@@ -397,7 +397,7 @@ int simple_connector_end_batch(struct instance *instance)
struct simple_connector_buffer *last_buffer = simple_connector_data->last_buffer;
if (!last_buffer->buffer) {
- last_buffer->buffer = buffer_create(0);
+ last_buffer->buffer = buffer_create(0, &netdata_buffers_statistics.buffers_exporters);
}
if (last_buffer->used) {
@@ -419,7 +419,7 @@ int simple_connector_end_batch(struct instance *instance)
if (last_buffer->header)
buffer_flush(last_buffer->header);
else
- last_buffer->header = buffer_create(0);
+ last_buffer->header = buffer_create(0, &netdata_buffers_statistics.buffers_exporters);
if (instance->prepare_header)
instance->prepare_header(instance);
diff --git a/exporting/prometheus/README.md b/exporting/prometheus/README.md
index ae94867f..97e9c632 100644
--- a/exporting/prometheus/README.md
+++ b/exporting/prometheus/README.md
@@ -1,9 +1,14 @@
<!--
title: "Export metrics to Prometheus"
description: "Export Netdata metrics to Prometheus for archiving and further analysis."
-custom_edit_url: https://github.com/netdata/netdata/edit/master/exporting/prometheus/README.md
+custom_edit_url: "https://github.com/netdata/netdata/edit/master/exporting/prometheus/README.md"
sidebar_label: "Using Netdata with Prometheus"
+learn_status: "Published"
+learn_topic_type: "Tasks"
+learn_rel_path: "Setup/Exporting connectors"
+learn_autogeneration_metadata: "{'part_of_cloud': False, 'part_of_agent': True}"
-->
+
import { OneLineInstallWget, OneLineInstallCurl } from '@site/src/components/OneLineInstall/'
# Using Netdata with Prometheus
@@ -17,7 +22,8 @@ are starting at a fresh ubuntu shell (whether you'd like to follow along in a VM
### Installing Netdata
-There are number of ways to install Netdata according to [Installation](/packaging/installer/README.md). The suggested way
+There are number of ways to install Netdata according to
+[Installation](https://github.com/netdata/netdata/blob/master/packaging/installer/README.md). The suggested way
of installing the latest Netdata and keep it upgrade automatically.
<!-- candidate for reuse -->
@@ -77,24 +83,24 @@ sudo tar -xvf /tmp/prometheus-*linux-amd64.tar.gz -C /opt/prometheus --strip=1
We will use the following `prometheus.yml` file. Save it at `/opt/prometheus/prometheus.yml`.
-Make sure to replace `your.netdata.ip` with the IP or hostname of the host running Netdata.
+Make sure to replace `your.netdata.ip` with the IP or hostname of the host running Netdata.
```yaml
# my global config
global:
- scrape_interval: 5s # Set the scrape interval to every 5 seconds. Default is every 1 minute.
+ scrape_interval: 5s # Set the scrape interval to every 5 seconds. Default is every 1 minute.
evaluation_interval: 5s # Evaluate rules every 5 seconds. The default is every 1 minute.
# scrape_timeout is set to the global default (10s).
# Attach these labels to any time series or alerts when communicating with
# external systems (federation, remote storage, Alertmanager).
external_labels:
- monitor: 'codelab-monitor'
+ monitor: 'codelab-monitor'
# Load rules once and periodically evaluate them according to the global 'evaluation_interval'.
rule_files:
- # - "first.rules"
- # - "second.rules"
+# - "first.rules"
+# - "second.rules"
# A scrape configuration containing exactly one endpoint to scrape:
# Here it's Prometheus itself.
@@ -106,7 +112,7 @@ scrape_configs:
# scheme defaults to 'http'.
static_configs:
- - targets: ['0.0.0.0:9090']
+ - targets: [ '0.0.0.0:9090' ]
- job_name: 'netdata-scrape'
@@ -114,7 +120,7 @@ scrape_configs:
params:
# format: prometheus | prometheus_all_hosts
# You can use `prometheus_all_hosts` if you want Prometheus to set the `instance` to your hostname instead of IP
- format: [prometheus]
+ format: [ prometheus ]
#
# sources: as-collected | raw | average | sum | volume
# default is: average
@@ -126,7 +132,7 @@ scrape_configs:
honor_labels: true
static_configs:
- - targets: ['{your.netdata.ip}:19999']
+ - targets: [ '{your.netdata.ip}:19999' ]
```
#### Install nodes.yml
@@ -202,7 +208,7 @@ sudo systemctl start prometheus
sudo systemctl enable prometheus
```
-Prometheus should now start and listen on port 9090. Attempt to head there with your browser.
+Prometheus should now start and listen on port 9090. Attempt to head there with your browser.
If everything is working correctly when you fetch `http://your.prometheus.ip:9090` you will see a 'Status' tab. Click
this and click on 'targets' We should see the Netdata host as a scraped target.
@@ -219,16 +225,16 @@ Before explaining the changes, we have to understand the key differences between
Each chart in Netdata has several properties (common to all its metrics):
-- `chart_id` - uniquely identifies a chart.
+- `chart_id` - uniquely identifies a chart.
-- `chart_name` - a more human friendly name for `chart_id`, also unique.
+- `chart_name` - a more human friendly name for `chart_id`, also unique.
-- `context` - this is the template of the chart. All disk I/O charts have the same context, all mysql requests charts
- have the same context, etc. This is used for alarm templates to match all the charts they should be attached to.
+- `context` - this is the template of the chart. All disk I/O charts have the same context, all mysql requests charts
+ have the same context, etc. This is used for alarm templates to match all the charts they should be attached to.
-- `family` groups a set of charts together. It is used as the submenu of the dashboard.
+- `family` groups a set of charts together. It is used as the submenu of the dashboard.
-- `units` is the units for all the metrics attached to the chart.
+- `units` is the units for all the metrics attached to the chart.
#### dimensions
@@ -240,44 +246,44 @@ they are both in the same chart).
Netdata can send metrics to Prometheus from 3 data sources:
-- `as collected` or `raw` - this data source sends the metrics to Prometheus as they are collected. No conversion is
- done by Netdata. The latest value for each metric is just given to Prometheus. This is the most preferred method by
- Prometheus, but it is also the harder to work with. To work with this data source, you will need to understand how
- to get meaningful values out of them.
+- `as collected` or `raw` - this data source sends the metrics to Prometheus as they are collected. No conversion is
+ done by Netdata. The latest value for each metric is just given to Prometheus. This is the most preferred method by
+ Prometheus, but it is also the harder to work with. To work with this data source, you will need to understand how
+ to get meaningful values out of them.
+
+ The format of the metrics is: `CONTEXT{chart="CHART",family="FAMILY",dimension="DIMENSION"}`.
- The format of the metrics is: `CONTEXT{chart="CHART",family="FAMILY",dimension="DIMENSION"}`.
+ If the metric is a counter (`incremental` in Netdata lingo), `_total` is appended the context.
- If the metric is a counter (`incremental` in Netdata lingo), `_total` is appended the context.
+ Unlike Prometheus, Netdata allows each dimension of a chart to have a different algorithm and conversion constants
+ (`multiplier` and `divisor`). In this case, that the dimensions of a charts are heterogeneous, Netdata will use this
+ format: `CONTEXT_DIMENSION{chart="CHART",family="FAMILY"}`
- Unlike Prometheus, Netdata allows each dimension of a chart to have a different algorithm and conversion constants
- (`multiplier` and `divisor`). In this case, that the dimensions of a charts are heterogeneous, Netdata will use this
- format: `CONTEXT_DIMENSION{chart="CHART",family="FAMILY"}`
+- `average` - this data source uses the Netdata database to send the metrics to Prometheus as they are presented on
+ the Netdata dashboard. So, all the metrics are sent as gauges, at the units they are presented in the Netdata
+ dashboard charts. This is the easiest to work with.
-- `average` - this data source uses the Netdata database to send the metrics to Prometheus as they are presented on
- the Netdata dashboard. So, all the metrics are sent as gauges, at the units they are presented in the Netdata
- dashboard charts. This is the easiest to work with.
+ The format of the metrics is: `CONTEXT_UNITS_average{chart="CHART",family="FAMILY",dimension="DIMENSION"}`.
- The format of the metrics is: `CONTEXT_UNITS_average{chart="CHART",family="FAMILY",dimension="DIMENSION"}`.
+ When this source is used, Netdata keeps track of the last access time for each Prometheus server fetching the
+ metrics. This last access time is used at the subsequent queries of the same Prometheus server to identify the
+ time-frame the `average` will be calculated.
- When this source is used, Netdata keeps track of the last access time for each Prometheus server fetching the
- metrics. This last access time is used at the subsequent queries of the same Prometheus server to identify the
- time-frame the `average` will be calculated.
+ So, no matter how frequently Prometheus scrapes Netdata, it will get all the database data.
+ To identify each Prometheus server, Netdata uses by default the IP of the client fetching the metrics.
- So, no matter how frequently Prometheus scrapes Netdata, it will get all the database data.
- To identify each Prometheus server, Netdata uses by default the IP of the client fetching the metrics.
-
- If there are multiple Prometheus servers fetching data from the same Netdata, using the same IP, each Prometheus
- server can append `server=NAME` to the URL. Netdata will use this `NAME` to uniquely identify the Prometheus server.
+ If there are multiple Prometheus servers fetching data from the same Netdata, using the same IP, each Prometheus
+ server can append `server=NAME` to the URL. Netdata will use this `NAME` to uniquely identify the Prometheus server.
-- `sum` or `volume`, is like `average` but instead of averaging the values, it sums them.
+- `sum` or `volume`, is like `average` but instead of averaging the values, it sums them.
- The format of the metrics is: `CONTEXT_UNITS_sum{chart="CHART",family="FAMILY",dimension="DIMENSION"}`. All the
- other operations are the same with `average`.
+ The format of the metrics is: `CONTEXT_UNITS_sum{chart="CHART",family="FAMILY",dimension="DIMENSION"}`. All the
+ other operations are the same with `average`.
- To change the data source to `sum` or `as-collected` you need to provide the `source` parameter in the request URL.
- e.g.: `http://your.netdata.ip:19999/api/v1/allmetrics?format=prometheus&help=yes&source=as-collected`
+ To change the data source to `sum` or `as-collected` you need to provide the `source` parameter in the request URL.
+ e.g.: `http://your.netdata.ip:19999/api/v1/allmetrics?format=prometheus&help=yes&source=as-collected`
- Keep in mind that early versions of Netdata were sending the metrics as: `CHART_DIMENSION{}`.
+ Keep in mind that early versions of Netdata were sending the metrics as: `CHART_DIMENSION{}`.
### Querying Metrics
@@ -364,7 +370,7 @@ functionality of Netdata this ignores any upstream hosts - so you should conside
```yaml
metrics_path: '/api/v1/allmetrics'
params:
- format: [prometheus_all_hosts]
+ format: [ prometheus_all_hosts ]
honor_labels: true
```
@@ -389,7 +395,9 @@ To save bandwidth, and because Prometheus does not use them anyway, `# TYPE` and
wanted they can be re-enabled via `types=yes` and `help=yes`, e.g.
`/api/v1/allmetrics?format=prometheus&types=yes&help=yes`
-Note that if enabled, the `# TYPE` and `# HELP` lines are repeated for every occurrence of a metric, which goes against the Prometheus documentation's [specification for these lines](https://github.com/prometheus/docs/blob/master/content/docs/instrumenting/exposition_formats.md#comments-help-text-and-type-information).
+Note that if enabled, the `# TYPE` and `# HELP` lines are repeated for every occurrence of a metric, which goes against
+the Prometheus
+documentation's [specification for these lines](https://github.com/prometheus/docs/blob/master/content/docs/instrumenting/exposition_formats.md#comments-help-text-and-type-information).
### Names and IDs
@@ -408,8 +416,8 @@ The default is controlled in `exporting.conf`:
You can overwrite it from Prometheus, by appending to the URL:
-- `&names=no` to get IDs (the old behaviour)
-- `&names=yes` to get names
+- `&names=no` to get IDs (the old behaviour)
+- `&names=yes` to get names
### Filtering metrics sent to Prometheus
@@ -420,7 +428,8 @@ Netdata can filter the metrics it sends to Prometheus with this setting:
send charts matching = *
```
-This settings accepts a space separated list of [simple patterns](/libnetdata/simple_pattern/README.md) to match the
+This settings accepts a space separated list
+of [simple patterns](https://github.com/netdata/netdata/blob/master/libnetdata/simple_pattern/README.md) to match the
**charts** to be sent to Prometheus. Each pattern can use `*` as wildcard, any number of times (e.g `*a*b*c*` is valid).
Patterns starting with `!` give a negative match (e.g `!*.bad users.* groups.*` will send all the users and groups
except `bad` user and `bad` group). The order is important: the first match (positive or negative) left to right, is
diff --git a/exporting/prometheus/prometheus.c b/exporting/prometheus/prometheus.c
index 294d8ec2..dc675dd3 100644
--- a/exporting/prometheus/prometheus.c
+++ b/exporting/prometheus/prometheus.c
@@ -317,7 +317,7 @@ void format_host_labels_prometheus(struct instance *instance, RRDHOST *host)
return;
if (!instance->labels_buffer)
- instance->labels_buffer = buffer_create(1024);
+ instance->labels_buffer = buffer_create(1024, &netdata_buffers_statistics.buffers_exporters);
struct format_prometheus_label_callback tmp = {
.instance = instance,
diff --git a/exporting/prometheus/remote_write/README.md b/exporting/prometheus/remote_write/README.md
index 54c5d658..9bda02d4 100644
--- a/exporting/prometheus/remote_write/README.md
+++ b/exporting/prometheus/remote_write/README.md
@@ -1,8 +1,11 @@
<!--
title: "Export metrics to Prometheus remote write providers"
description: "Send Netdata metrics to your choice of more than 20 external storage providers for long-term archiving and further analysis."
-custom_edit_url: https://github.com/netdata/netdata/edit/master/exporting/prometheus/remote_write/README.md
-sidebar_label: Prometheus remote write
+custom_edit_url: "https://github.com/netdata/netdata/edit/master/exporting/prometheus/remote_write/README.md"
+sidebar_label: "Prometheus remote write"
+learn_status: "Published"
+learn_topic_type: "Tasks"
+learn_rel_path: "Setup/Exporting connectors"
-->
# Prometheus remote write exporting connector
@@ -15,7 +18,7 @@ than 20 external storage providers for long-term archiving and further analysis.
To use the Prometheus remote write API with [storage
providers](https://prometheus.io/docs/operating/integrations/#remote-endpoints-and-storage), install
[protobuf](https://developers.google.com/protocol-buffers/) and [snappy](https://github.com/google/snappy) libraries.
-Next, [reinstall Netdata](/packaging/installer/REINSTALL.md), which detects that the required libraries and utilities
+Next, [reinstall Netdata](https://github.com/netdata/netdata/blob/master/packaging/installer/REINSTALL.md), which detects that the required libraries and utilities
are now available.
## Configuration
diff --git a/exporting/prometheus/remote_write/remote_write.c b/exporting/prometheus/remote_write/remote_write.c
index 2e2fa3c1..1857ca33 100644
--- a/exporting/prometheus/remote_write/remote_write.c
+++ b/exporting/prometheus/remote_write/remote_write.c
@@ -104,7 +104,7 @@ int init_prometheus_remote_write_instance(struct instance *instance)
instance->prepare_header = prometheus_remote_write_prepare_header;
instance->check_response = process_prometheus_remote_write_response;
- instance->buffer = (void *)buffer_create(0);
+ instance->buffer = (void *)buffer_create(0, &netdata_buffers_statistics.buffers_exporters);
if (uv_mutex_init(&instance->mutex))
return 1;
diff --git a/exporting/pubsub/README.md b/exporting/pubsub/README.md
index 2f9ac83d..10252f16 100644
--- a/exporting/pubsub/README.md
+++ b/exporting/pubsub/README.md
@@ -1,8 +1,12 @@
<!--
title: "Export metrics to Google Cloud Pub/Sub Service"
description: "Export Netdata metrics to the Google Cloud Pub/Sub Service for long-term archiving or analytical processing."
-custom_edit_url: https://github.com/netdata/netdata/edit/master/exporting/pubsub/README.md
-sidebar_label: Google Cloud Pub/Sub Service
+custom_edit_url: "https://github.com/netdata/netdata/edit/master/exporting/pubsub/README.md"
+sidebar_label: "Google Cloud Pub/Sub Service"
+learn_status: "Published"
+learn_topic_type: "Tasks"
+learn_rel_path: "Setup/Exporting connectors"
+learn_autogeneration_metadata: "{'part_of_cloud': False, 'part_of_agent': True}"
-->
# Export metrics to Google Cloud Pub/Sub Service
diff --git a/exporting/pubsub/pubsub.c b/exporting/pubsub/pubsub.c
index b218338f..d65fc2c4 100644
--- a/exporting/pubsub/pubsub.c
+++ b/exporting/pubsub/pubsub.c
@@ -30,7 +30,7 @@ int init_pubsub_instance(struct instance *instance)
instance->prepare_header = NULL;
instance->check_response = NULL;
- instance->buffer = (void *)buffer_create(0);
+ instance->buffer = (void *)buffer_create(0, &netdata_buffers_statistics.buffers_exporters);
if (!instance->buffer) {
error("EXPORTING: cannot create buffer for Pub/Sub exporting connector instance %s", instance->config.name);
return 1;
diff --git a/exporting/send_data.c b/exporting/send_data.c
index 1d20f3b7..045aab6e 100644
--- a/exporting/send_data.c
+++ b/exporting/send_data.c
@@ -64,7 +64,7 @@ void simple_connector_receive_response(int *sock, struct instance *instance)
{
static BUFFER *response = NULL;
if (!response)
- response = buffer_create(4096);
+ response = buffer_create(4096, &netdata_buffers_statistics.buffers_exporters);
struct stats *stats = &instance->stats;
#ifdef ENABLE_HTTPS
diff --git a/exporting/send_internal_metrics.c b/exporting/send_internal_metrics.c
index 515cda3b..e4347964 100644
--- a/exporting/send_internal_metrics.c
+++ b/exporting/send_internal_metrics.c
@@ -65,7 +65,7 @@ void send_internal_metrics(struct instance *instance)
if (!stats->initialized) {
char id[RRD_ID_LENGTH_MAX + 1];
- BUFFER *family = buffer_create(0);
+ BUFFER *family = buffer_create(0, &netdata_buffers_statistics.buffers_exporters);
buffer_sprintf(family, "exporting_%s", instance->config.name);
diff --git a/exporting/tests/test_exporting_engine.c b/exporting/tests/test_exporting_engine.c
index 6ea6b1e5..418be0b0 100644
--- a/exporting/tests/test_exporting_engine.c
+++ b/exporting/tests/test_exporting_engine.c
@@ -612,7 +612,7 @@ static void test_exporting_discard_response(void **state)
{
struct engine *engine = *state;
- BUFFER *response = buffer_create(0);
+ BUFFER *response = buffer_create(0, NULL);
buffer_sprintf(response, "Test response");
assert_int_equal(exporting_discard_response(response, engine->instance_root), 0);
@@ -651,8 +651,8 @@ static void test_simple_connector_send_buffer(void **state)
int sock = 1;
int failures = 3;
size_t buffered_metrics = 1;
- BUFFER *header = buffer_create(0);
- BUFFER *buffer = buffer_create(0);
+ BUFFER *header = buffer_create(0, NULL);
+ BUFFER *buffer = buffer_create(0, NULL);
buffer_strcat(header, "test header\n");
buffer_strcat(buffer, "test buffer\n");
@@ -695,10 +695,10 @@ static void test_simple_connector_worker(void **state)
instance->connector_specific_data = simple_connector_data;
simple_connector_data->last_buffer = callocz(1, sizeof(struct simple_connector_buffer));
simple_connector_data->first_buffer = simple_connector_data->last_buffer;
- simple_connector_data->header = buffer_create(0);
- simple_connector_data->buffer = buffer_create(0);
- simple_connector_data->last_buffer->header = buffer_create(0);
- simple_connector_data->last_buffer->buffer = buffer_create(0);
+ simple_connector_data->header = buffer_create(0, NULL);
+ simple_connector_data->buffer = buffer_create(0, NULL);
+ simple_connector_data->last_buffer->header = buffer_create(0, NULL);
+ simple_connector_data->last_buffer->buffer = buffer_create(0, NULL);
strcpy(simple_connector_data->connected_to, "localhost");
buffer_sprintf(simple_connector_data->last_buffer->header, "test header");
@@ -822,7 +822,7 @@ static void test_flush_host_labels(void **state)
struct engine *engine = *state;
struct instance *instance = engine->instance_root;
- instance->labels_buffer = buffer_create(12);
+ instance->labels_buffer = buffer_create(12, NULL);
buffer_strcat(instance->labels_buffer, "check string");
assert_int_equal(buffer_strlen(instance->labels_buffer), 12);
@@ -1133,7 +1133,7 @@ static void rrd_stats_api_v1_charts_allmetrics_prometheus(void **state)
{
(void)state;
- BUFFER *buffer = buffer_create(0);
+ BUFFER *buffer = buffer_create(0, NULL);
RRDSET *st;
rrdset_foreach_read(st, localhost);
@@ -1241,8 +1241,8 @@ static void test_prometheus_remote_write_prepare_header(void **state)
struct simple_connector_data *simple_connector_data = callocz(1, sizeof(struct simple_connector_data));
instance->connector_specific_data = simple_connector_data;
simple_connector_data->last_buffer = callocz(1, sizeof(struct simple_connector_buffer));
- simple_connector_data->last_buffer->header = buffer_create(0);
- simple_connector_data->last_buffer->buffer = buffer_create(0);
+ simple_connector_data->last_buffer->header = buffer_create(0, NULL);
+ simple_connector_data->last_buffer->buffer = buffer_create(0, NULL);
strcpy(simple_connector_data->connected_to, "localhost");
buffer_sprintf(simple_connector_data->last_buffer->buffer, "test buffer");
@@ -1269,7 +1269,7 @@ static void test_prometheus_remote_write_prepare_header(void **state)
static void test_process_prometheus_remote_write_response(void **state)
{
(void)state;
- BUFFER *buffer = buffer_create(0);
+ BUFFER *buffer = buffer_create(0, NULL);
buffer_sprintf(buffer, "HTTP/1.1 200 OK\r\n");
assert_int_equal(process_prometheus_remote_write_response(buffer, NULL), 0);
@@ -1834,7 +1834,7 @@ static void test_format_batch_mongodb(void **state)
connector_specific_data->first_buffer->next = current_buffer;
connector_specific_data->last_buffer = current_buffer;
- BUFFER *buffer = buffer_create(0);
+ BUFFER *buffer = buffer_create(0, NULL);
buffer_sprintf(buffer, "{ \"metric\": \"test_metric\" }\n");
instance->buffer = buffer;
stats->buffered_metrics = 1;
diff --git a/exporting/tests/test_exporting_engine.h b/exporting/tests/test_exporting_engine.h
index a9180a51..24dac863 100644
--- a/exporting/tests/test_exporting_engine.h
+++ b/exporting/tests/test_exporting_engine.h
@@ -55,9 +55,6 @@ int __wrap_connect_to_one_of(
size_t *reconnects_counter,
char *connected_to,
size_t connected_to_size);
-void __rrdhost_check_rdlock(RRDHOST *host, const char *file, const char *function, const unsigned long line);
-void __rrdset_check_rdlock(RRDSET *st, const char *file, const char *function, const unsigned long line);
-void __rrd_check_rdlock(const char *file, const char *function, const unsigned long line);
time_t __mock_rrddim_query_oldest_time(STORAGE_METRIC_HANDLE *db_metric_handle);
time_t __mock_rrddim_query_latest_time(STORAGE_METRIC_HANDLE *db_metric_handle);
void __mock_rrddim_query_init(STORAGE_METRIC_HANDLE *db_metric_handle, struct rrddim_query_handle *handle, time_t start_time, time_t end_time);
diff --git a/health/Makefile.am b/health/Makefile.am
index 7c8d7f9d..f0cbb771 100644
--- a/health/Makefile.am
+++ b/health/Makefile.am
@@ -36,13 +36,14 @@ dist_healthconfig_DATA = \
health.d/cgroups.conf \
health.d/cpu.conf \
health.d/cockroachdb.conf \
+ health.d/consul.conf \
health.d/disks.conf \
health.d/dnsmasq_dhcp.conf \
health.d/dns_query.conf \
health.d/dockerd.conf \
+ health.d/elasticsearch.conf \
health.d/entropy.conf \
health.d/exporting.conf \
- health.d/fping.conf \
health.d/geth.conf \
health.d/ioping.conf \
health.d/gearman.conf \
diff --git a/health/QUICKSTART.md b/health/QUICKSTART.md
deleted file mode 100644
index bc2da2df..00000000
--- a/health/QUICKSTART.md
+++ /dev/null
@@ -1,143 +0,0 @@
-<!--
-title: "Health quickstart"
-custom_edit_url: https://github.com/netdata/netdata/edit/master/health/QUICKSTART.md
--->
-
-# Health quickstart
-
-In this quickstart guide, you'll learn the basics of editing health configuration files. With this knowledge, you
-will be able to customize how and when Netdata triggers alarms based on the health and performance of your system or
-infrastructure.
-
-To learn about more advanced health configurations, visit the [health reference guide](/health/REFERENCE.md).
-
-## Edit health configuration files
-
-You should [use `edit-config`](/docs/configure/nodes.md) to edit Netdata's health configuration files. `edit-config`
-will open your system's default terminal editor for you to make your changes. Once you've saved and closed the editor,
-`edit-config` will copy your edited file into `/etc/netdata/health.d/`, which will override the stock file in
-`/usr/lib/netdata/conf.d/health.d/` and ensure your customizations are persistent between updates.
-
-For example, to edit the `cpu.conf` health configuration file, you would run:
-
-```bash
-cd /etc/netdata/ # Replace with your Netdata configuration directory, if not /etc/netdata/
-./edit-config health.d/cpu.conf
-```
-
-Each health configuration file contains one or more health entities, which always begin with an `alarm:` or `template:`
-line. You can edit these entities based on your needs. To make any changes live, be sure to [reload your health
-configuration](#reload-health-configuration).
-
-## Reference Netdata's stock health configuration files
-
-While you should always [use `edit-config`](#edit-health-configuration-files), you might also want to view the stock
-health configuration files Netdata ships with. Stock files can be useful as reference material, or to determine which
-file you should edit with `edit-config`.
-
-By default, Netdata will put health configuration files in `/usr/lib/netdata/conf.d/health.d`. However, you can
-double-check the location of these files by navigating to `http://NODE:19999/netdata.conf`, replacing `NODE` with the IP
-address or hostname for your Agent dashboard, looking for the `stock health configuration directory` option. The value
-here will show the correct path for your installation.
-
-```conf
-[directories]
- ...
- # stock health config = /usr/lib/netdata/conf.d/health.d
-```
-
-Navigate to the health configuration directory to see all the available files and open them for reading.
-
-```bash
-cd /usr/lib/netdata/conf.d/health.d/
-ls
-adaptec_raid.conf entropy.conf memory.conf squid.conf
-am2320.conf fping.conf mongodb.conf
-apache.conf mysql.conf swap.conf
-...
-```
-
-> ⚠️ If you edit configuration files in your stock health configuration directory, Netdata will overwrite them during
-> any updates. Please use `edit-config` as described in the [section above](#edit-health-configuration-files).
-
-## Write a new health entity
-
-While tuning existing alarms may work in some cases, you may need to write entirely new health entities based on how
-your systems and applications work.
-
-To write a new health entity, let's create a new file inside of the `health.d/` directory. We'll name our file
-`example.conf` for now.
-
-```bash
-./edit-config health.d/example.conf
-```
-
-As an example, let's build a health entity that triggers an alarm your system's RAM usage goes above 80%. Copy and paste
-the following into the editor:
-
-```yaml
- alarm: ram_usage
- on: system.ram
-lookup: average -1m percentage of used
- units: %
- every: 1m
- warn: $this > 80
- crit: $this > 90
- info: The percentage of RAM used by the system.
-```
-
-Let's look into each of the lines to see how they create a working health entity.
-
-- `alarm`: The name for your new entity. The name needs to follow these requirements:
- - Any alphabet letter or number.
- - The symbols `.` and `_`.
- - Cannot be `chart name`, `dimension name`, `family name`, or `chart variable names`.
-- `on`: Which chart the entity listens to.
-- `lookup`: Which metrics the alarm monitors, the duration of time to monitor, and how to process the metrics into a
- usable format.
- - `average`: Calculate the average of all the metrics collected.
- - `-1m`: Use metrics from 1 minute ago until now to calculate that average.
- - `percentage`: Clarify that we're calculating a percentage of RAM usage.
- - `of used`: Specify which dimension (`used`) on the `system.ram` chart you want to monitor with this entity.
-- `units`: Use percentages rather than absolute units.
-- `every`: How often to perform the `lookup` calculation to decide whether or not to trigger this alarm.
-- `warn`/`crit`: The value at which Netdata should trigger a warning or critical alarm.
-- `info`: A description of the alarm, which will appear in the dashboard and notifications.
-
-Let's put all these lines into a human-readable format.
-
-This health entity, named **ram_usage**, watches at the **system.ram** chart. It looks up the last **1 minute** of
-metrics from the **used** dimension and calculates the **average** of all those metrics in a **percentage** format,
-using a **% unit**. The entity performs this lookup **every minute**. If the average RAM usage percentage over the last
-1 minute is **more than 80%**, the entity triggers a warning alarm. If the usage is **more than 90%**, the entity
-triggers a critical alarm.
-
-Now that you've written a new health entity, you need to reload it to see it live on the dashboard.
-
-## Reload health configuration
-
-To make any changes to your health configuration live, you must reload Netdata's health monitoring system. To do that
-without restarting all of Netdata, run the following:
-
-```bash
-netdatacli reload-health
-```
-
-If you receive an error like `command not found`, this means that `netdatacli` is not installed in your `$PATH`. In that
- case, you can reload only the health component by sending a `SIGUSR2` to Netdata:
-
-```bash
-killall -USR2 netdata
-```
-## What's next?
-
-To learn about all of Netdata's health configuration options, view the [reference guide](/health/REFERENCE.md) and
-[daemon configuration](/daemon/config/README.md#health-section-options) for additional options available in the
-`[health]` section of `netdata.conf`.
-
-Or, get guided insights into specific health configurations with our [health guides](/health/README.md#guides).
-
-Finally, move on to Netdata's [notification system](/health/notifications/README.md) to learn more about how Netdata can
-let you know when the health of your systems or apps goes awry.
-
-
diff --git a/health/README.md b/health/README.md
index 2b1caf54..460f6568 100644
--- a/health/README.md
+++ b/health/README.md
@@ -1,6 +1,10 @@
<!--
title: "Health monitoring"
custom_edit_url: https://github.com/netdata/netdata/edit/master/health/README.md
+sidebar_label: "Health monitoring"
+learn_status: "Published"
+learn_topic_type: "Concepts"
+learn_rel_path: "Concepts"
-->
# Health monitoring
@@ -10,15 +14,13 @@ worked closely with our community of DevOps engineers, SREs, and developers to d
alarms that work without any configuration.
The Agent's health monitoring system is also dynamic and fully customizable. You can write entirely new alarms, tune the
-community-configured alarms for every app/service [the Agent collects metrics from](/collectors/COLLECTORS.md), or
+community-configured alarms for every app/service [the Agent collects metrics from](https://github.com/netdata/netdata/blob/master/collectors/COLLECTORS.md), or
silence anything you're not interested in. You can even power complex lookups by running statistical algorithms against
your metrics.
Ready to take the next steps with health monitoring?
-[Quickstart](/health/QUICKSTART.md)
-
-[Configuration reference](/health/REFERENCE.md)
+[Configuration reference](https://github.com/netdata/netdata/blob/master/health/REFERENCE.md)
## Guides
@@ -26,13 +28,13 @@ Every infrastructure is different, so we're not interested in mandating how you
monitoring features. Instead, these guides should give you the details you need to tweak alarms to your heart's
content.
-[Stopping notifications for individual alarms](/docs/guides/monitor/stop-notifications-alarms.md)
+[Stopping notifications for individual alarms](https://github.com/netdata/netdata/blob/master/docs/guides/monitor/stop-notifications-alarms.md)
-[Use dimension templates to create dynamic alarms](/docs/guides/monitor/dimension-templates.md)
+[Use dimension templates to create dynamic alarms](https://github.com/netdata/netdata/blob/master/docs/guides/monitor/dimension-templates.md)
## Related features
-**[Notifications](/health/notifications/README.md)**: Get notified about ongoing alarms from your Agents via your
+**[Notifications](https://github.com/netdata/netdata/blob/master/health/notifications/README.md)**: Get notified about ongoing alarms from your Agents via your
favorite platform(s), such as Slack, Discord, PagerDuty, email, and much more.
diff --git a/health/REFERENCE.md b/health/REFERENCE.md
index 90da4102..27031cd1 100644
--- a/health/REFERENCE.md
+++ b/health/REFERENCE.md
@@ -1,6 +1,10 @@
<!--
title: "Health configuration reference"
-custom_edit_url: https://github.com/netdata/netdata/edit/master/health/REFERENCE.md
+sidebar_label: "Health"
+custom_edit_url: "https://github.com/netdata/netdata/edit/master/health/REFERENCE.md"
+learn_status: "Published"
+learn_topic_type: "Tasks"
+learn_rel_path: "Operations/Alerts"
-->
# Health configuration reference
@@ -11,7 +15,7 @@ This guide contains information about editing health configuration files to twea
entities that are customized to the needs of your infrastructure.
To learn the basics of locating and editing health configuration files, see the [health
-quickstart](/health/QUICKSTART.md).
+quickstart](https://github.com/netdata/netdata/blob/master/health/QUICKSTART.md).
## Health configuration files
@@ -19,7 +23,7 @@ You can configure the Agent's health watchdog service by editing files in two lo
- The `[health]` section in `netdata.conf`. By editing the daemon's behavior, you can disable health monitoring
altogether, run health checks more or less often, and more. See [daemon
- configuration](/daemon/config/README.md#health-section-options) for a table of all the available settings, their
+ configuration](https://github.com/netdata/netdata/blob/master/daemon/config/README.md#health-section-options) for a table of all the available settings, their
default values, and what they control.
- The individual `.conf` files in `health.d/`. These health entity files are organized by the type of metric they are
performing calculations on or their associated collector. You should edit these files using the `edit-config`
@@ -52,7 +56,7 @@ Netdata parses the following lines. Beneath the table is an in-depth explanation
- The `every` line is **required** if not using `lookup`.
- Each entity **must** have at least one of the following lines: `lookup`, `calc`, `warn`, or `crit`.
- A few lines use space-separated lists to define how the entity behaves. You can use `*` as a wildcard or prefix with
- `!` for a negative match. Order is important, too! See our [simple patterns docs](/libnetdata/simple_pattern/README.md) for
+ `!` for a negative match. Order is important, too! See our [simple patterns docs](https://github.com/netdata/netdata/blob/master/libnetdata/simple_pattern/README.md) for
more examples.
- Lines terminated by a `\` are spliced together with the next line. The backslash is removed and the following line is
joined with the current one. No space is inserted, so you may split a line anywhere, even in the middle of a word.
@@ -236,7 +240,7 @@ hosts: server1 server2 database* !redis3 redis*
#### Alarm line `plugin`
The `plugin` line filters which plugin within the context this alarm should apply to. The value is a space-separated
-list of [simple patterns](/libnetdata/simple_pattern/README.md). For example,
+list of [simple patterns](https://github.com/netdata/netdata/blob/master/libnetdata/simple_pattern/README.md). For example,
you can create a filter for an alarm that applies specifically to `python.d.plugin`:
```yaml
@@ -250,7 +254,7 @@ comprehensive example using both.
#### Alarm line `module`
The `module` line filters which module within the context this alarm should apply to. The value is a space-separated
-list of [simple patterns](/libnetdata/simple_pattern/README.md). For
+list of [simple patterns](https://github.com/netdata/netdata/blob/master/libnetdata/simple_pattern/README.md). For
example, you can create an alarm that applies only on the `isc_dhcpd` module started by `python.d.plugin`:
```yaml
@@ -262,7 +266,7 @@ module: isc_dhcpd
The `charts` line filters which chart this alarm should apply to. It is only available on entities using the
[`template`](#alarm-line-alarm-or-template) line.
-The value is a space-separated list of [simple patterns](/libnetdata/simple_pattern/README.md). For
+The value is a space-separated list of [simple patterns](https://github.com/netdata/netdata/blob/master/libnetdata/simple_pattern/README.md). For
example, a template that applies to `disk.svctm` (Average Service Time) context, but excludes the disk `sdb` from alarms:
```yaml
@@ -276,7 +280,7 @@ template: disk_svctm_alarm
The `families` line, used only alongside templates, filters which families within the context this alarm should apply
to. The value is a space-separated list.
-The value is a space-separate list of simple patterns. See our [simple patterns docs](/libnetdata/simple_pattern/README.md) for
+The value is a space-separate list of simple patterns. See our [simple patterns docs](https://github.com/netdata/netdata/blob/master/libnetdata/simple_pattern/README.md) for
some examples.
For example, you can create a template on the `disk.io` context, but filter it to only the `sda` and `sdb` families:
@@ -295,7 +299,7 @@ The format is:
lookup: METHOD AFTER [at BEFORE] [every DURATION] [OPTIONS] [of DIMENSIONS] [foreach DIMENSIONS]
```
-Everything is the same with [badges](/web/api/badges/README.md). In short:
+Everything is the same with [badges](https://github.com/netdata/netdata/blob/master/web/api/badges/README.md). In short:
- `METHOD` is one of `average`, `min`, `max`, `sum`, `incremental-sum`.
This is required.
@@ -312,7 +316,7 @@ Everything is the same with [badges](/web/api/badges/README.md). In short:
above too).
- `OPTIONS` is a space separated list of `percentage`, `absolute`, `min2max`, `unaligned`,
- `match-ids`, `match-names`. Check the [badges](/web/api/badges/README.md) documentation for more info.
+ `match-ids`, `match-names`. Check the [badges](https://github.com/netdata/netdata/blob/master/web/api/badges/README.md) documentation for more info.
- `of DIMENSIONS` is optional and has to be the last parameter. Dimensions have to be separated
by `,` or `|`. The space characters found in dimensions will be kept as-is (a few dimensions
@@ -499,7 +503,7 @@ good idea to tell Netdata to not clear the notification, by using the `no-clear-
#### Alarm line `host labels`
-Defines the list of labels present on a host. See our [host labels guide](/docs/guides/using-host-labels.md) for
+Defines the list of labels present on a host. See our [host labels guide](https://github.com/netdata/netdata/blob/master/docs/guides/using-host-labels.md) for
an explanation of host labels and how to implement them.
For example, let's suppose that `netdata.conf` is configured with the following labels:
@@ -532,7 +536,7 @@ that will be applied to all hosts installed in the last decade with the followin
host labels: installed = 201*
```
-See our [simple patterns docs](/libnetdata/simple_pattern/README.md) for more examples.
+See our [simple patterns docs](https://github.com/netdata/netdata/blob/master/libnetdata/simple_pattern/README.md) for more examples.
#### Alarm line `info`
@@ -548,13 +552,13 @@ alert information. Current variables supported are:
| variable | description |
| ---------| ----------- |
-| $family | Will be replaced by the family instance for the alert (e.g. eth0) |
-| $label: | Followed by a chart label name, this will replace the variable with the chart label's value |
+| ${family} | Will be replaced by the family instance for the alert (e.g. eth0) |
+| ${label:LABEL_NAME} | The variable will be replaced with the value of the label |
For example, an info field like the following:
```yaml
-info: average inbound utilization for the network interface $family over the last minute
+info: average inbound utilization for the network interface ${family} over the last minute
```
Will be rendered on the alert acting on interface `eth0` as:
@@ -567,7 +571,7 @@ An alert acting on a chart that has a chart label named e.g. `target`, with a va
can be enriched as follows:
```yaml
-info: average ratio of HTTP responses with unexpected status over the last 5 minutes for the site $label:target
+info: average ratio of HTTP responses with unexpected status over the last 5 minutes for the site ${label:target}
```
Will become:
@@ -647,15 +651,15 @@ You can find all the variables that can be used for a given chart, using
Agent dashboard. For example, [variables for the `system.cpu` chart of the
registry](https://registry.my-netdata.io/api/v1/alarm_variables?chart=system.cpu).
-> If you don't know how to find the CHART_NAME, you can read about it [here](/web/README.md#charts).
+> If you don't know how to find the CHART_NAME, you can read about it [here](https://github.com/netdata/netdata/blob/master/web/README.md#charts).
Netdata supports 3 internal indexes for variables that will be used in health monitoring.
<details markdown="1"><summary>The variables below can be used in both chart alarms and context templates.</summary>
Although the `alarm_variables` link shows you variables for a particular chart, the same variables can also be used in
-templates for charts belonging to a given [context](/web/README.md#contexts). The reason is that all charts of a given
-context are essentially identical, with the only difference being the [family](/web/README.md#families) that
+templates for charts belonging to a given [context](https://github.com/netdata/netdata/blob/master/web/README.md#contexts). The reason is that all charts of a given
+context are essentially identical, with the only difference being the [family](https://github.com/netdata/netdata/blob/master/web/README.md#families) that
identifies a particular hardware or software instance. Charts and templates do not apply to specific families anyway,
unless if you explicitly limit an alarm with the [alarm line `families`](#alarm-line-families).
@@ -995,7 +999,7 @@ The `lookup` line will use the `anomaly_rate` dimension of the `anomaly_detectio
## Troubleshooting
-You can compile Netdata with [debugging](/daemon/README.md#debugging) and then set in `netdata.conf`:
+You can compile Netdata with [debugging](https://github.com/netdata/netdata/blob/master/daemon/README.md#debugging) and then set in `netdata.conf`:
```yaml
[global]
@@ -1018,6 +1022,6 @@ expression.
It's currently not possible to schedule notifications from within the alarm template. For those scenarios where you need
to temporary disable notifications (for instance when running backups triggers a disk alert) you can disable or silence
notifications are runtime. The health checks can be controlled at runtime via the [health management
-api](/web/api/health/README.md).
+api](https://github.com/netdata/netdata/blob/master/web/api/health/README.md).
diff --git a/health/health.c b/health/health.c
index 3784e0f3..b34f54ab 100644
--- a/health/health.c
+++ b/health/health.c
@@ -159,9 +159,10 @@ static bool prepare_command(BUFFER *wb,
unsigned int default_health_enabled = 1;
char *silencers_filename;
+SIMPLE_PATTERN *conf_enabled_alarms = NULL;
// the queue of executed alarm notifications that haven't been waited for yet
-static __thread struct {
+static struct {
ALARM_ENTRY *head; // oldest
ALARM_ENTRY *tail; // latest
} alarm_notifications_in_progress = {NULL, NULL};
@@ -301,7 +302,7 @@ void health_init(void) {
* @param host the structure of the host that the function will reload the configuration.
*/
static void health_reload_host(RRDHOST *host) {
- if(unlikely(!host->health_enabled) && !rrdhost_flag_check(host, RRDHOST_FLAG_INITIALIZED_HEALTH))
+ if(unlikely(!host->health.health_enabled) && !rrdhost_flag_check(host, RRDHOST_FLAG_INITIALIZED_HEALTH))
return;
log_health("[%s]: Reloading health.", rrdhost_hostname(host));
@@ -345,7 +346,6 @@ static void health_reload_host(RRDHOST *host) {
rrdcalctemplate_link_matching_templates_to_rrdset(st);
}
rrdset_foreach_done(st);
- host->aclk_alert_reloaded = 1;
}
/**
@@ -363,6 +363,12 @@ void health_reload(void) {
health_reload_host(host);
rrd_unlock();
+
+#ifdef ENABLE_ACLK
+ if (netdata_cloud_setting) {
+ aclk_alert_reloaded = 1;
+ }
+#endif
}
// ----------------------------------------------------------------------------
@@ -444,8 +450,8 @@ static inline void health_alarm_execute(RRDHOST *host, ALARM_ENTRY *ae) {
log_health("[%s]: Sending notification for alarm '%s.%s' status %s.", rrdhost_hostname(host), ae_chart_name(ae), ae_name(ae), rrdcalc_status2string(ae->new_status));
- const char *exec = (ae->exec) ? ae_exec(ae) : string2str(host->health_default_exec);
- const char *recipient = (ae->recipient) ? ae_recipient(ae) : string2str(host->health_default_recipient);
+ const char *exec = (ae->exec) ? ae_exec(ae) : string2str(host->health.health_default_exec);
+ const char *recipient = (ae->recipient) ? ae_recipient(ae) : string2str(host->health.health_default_recipient);
int n_warn=0, n_crit=0;
RRDCALC *rc;
@@ -453,8 +459,8 @@ static inline void health_alarm_execute(RRDHOST *host, ALARM_ENTRY *ae) {
BUFFER *warn_alarms, *crit_alarms;
active_alerts_t *active_alerts = callocz(ACTIVE_ALARMS_LIST_EXAMINE, sizeof(active_alerts_t));
- warn_alarms = buffer_create(NETDATA_WEB_RESPONSE_INITIAL_SIZE);
- crit_alarms = buffer_create(NETDATA_WEB_RESPONSE_INITIAL_SIZE);
+ warn_alarms = buffer_create(NETDATA_WEB_RESPONSE_INITIAL_SIZE, &netdata_buffers_statistics.buffers_health);
+ crit_alarms = buffer_create(NETDATA_WEB_RESPONSE_INITIAL_SIZE, &netdata_buffers_statistics.buffers_health);
foreach_rrdcalc_in_rrdhost_read(host, rc) {
if(unlikely(!rc->rrdset || !rc->rrdset->last_collected_time.tv_sec))
@@ -511,7 +517,7 @@ static inline void health_alarm_execute(RRDHOST *host, ALARM_ENTRY *ae) {
char *edit_command = ae->source ? health_edit_command_from_source(ae_source(ae)) : strdupz("UNKNOWN=0=UNKNOWN");
- BUFFER *wb = buffer_create(8192);
+ BUFFER *wb = buffer_create(8192, &netdata_buffers_statistics.buffers_health);
bool ok = prepare_command(wb,
exec,
recipient,
@@ -692,8 +698,8 @@ static inline int rrdcalc_isrunnable(RRDCALC *rc, time_t now, time_t *next_run)
}
int update_every = rc->rrdset->update_every;
- time_t first = rrdset_first_entry_t(rc->rrdset);
- time_t last = rrdset_last_entry_t(rc->rrdset);
+ time_t first = rrdset_first_entry_s(rc->rrdset);
+ time_t last = rrdset_last_entry_s(rc->rrdset);
if(unlikely(now + update_every < first /* || now - update_every > last */)) {
debug(D_HEALTH
@@ -719,7 +725,7 @@ static inline int rrdcalc_isrunnable(RRDCALC *rc, time_t now, time_t *next_run)
}
static inline int check_if_resumed_from_suspension(void) {
- static __thread usec_t last_realtime = 0, last_monotonic = 0;
+ static usec_t last_realtime = 0, last_monotonic = 0;
usec_t realtime = now_realtime_usec(), monotonic = now_monotonic_usec();
int ret = 0;
@@ -735,25 +741,29 @@ static inline int check_if_resumed_from_suspension(void) {
return ret;
}
-static void health_thread_cleanup(void *ptr) {
+static void health_main_cleanup(void *ptr) {
worker_unregister();
- struct health_state *h = ptr;
- h->host->health_spawn = 0;
+ struct netdata_static_thread *static_thread = (struct netdata_static_thread *)ptr;
+ static_thread->enabled = NETDATA_MAIN_THREAD_EXITING;
+ info("cleaning up...");
+ static_thread->enabled = NETDATA_MAIN_THREAD_EXITED;
- netdata_thread_cancel(netdata_thread_self());
- log_health("[%s]: Health thread ended.", rrdhost_hostname(h->host));
- debug(D_HEALTH, "HEALTH %s: Health thread ended.", rrdhost_hostname(h->host));
+ log_health("Health thread ended.");
}
static void initialize_health(RRDHOST *host, int is_localhost) {
- if(!host->health_enabled || rrdhost_flag_check(host, RRDHOST_FLAG_INITIALIZED_HEALTH)) return;
+ if(!host->health.health_enabled ||
+ rrdhost_flag_check(host, RRDHOST_FLAG_INITIALIZED_HEALTH) ||
+ !service_running(SERVICE_HEALTH))
+ return;
+
rrdhost_flag_set(host, RRDHOST_FLAG_INITIALIZED_HEALTH);
log_health("[%s]: Initializing health.", rrdhost_hostname(host));
- host->health_default_warn_repeat_every = config_get_duration(CONFIG_SECTION_HEALTH, "default repeat warning", "never");
- host->health_default_crit_repeat_every = config_get_duration(CONFIG_SECTION_HEALTH, "default repeat critical", "never");
+ host->health.health_default_warn_repeat_every = config_get_duration(CONFIG_SECTION_HEALTH, "default repeat warning", "never");
+ host->health.health_default_crit_repeat_every = config_get_duration(CONFIG_SECTION_HEALTH, "default repeat critical", "never");
host->health_log.next_log_id = 1;
host->health_log.next_alarm_id = 1;
@@ -769,6 +779,8 @@ static void initialize_health(RRDHOST *host, int is_localhost) {
else
host->health_log.max = (unsigned int)n;
+ conf_enabled_alarms = simple_pattern_create(config_get(CONFIG_SECTION_HEALTH, "enabled alarms", "*"), NULL, SIMPLE_PATTERN_EXACT);
+
netdata_rwlock_init(&host->health_log.alarm_log_rwlock);
char filename[FILENAME_MAX + 1];
@@ -785,30 +797,15 @@ static void initialize_health(RRDHOST *host, int is_localhost) {
if(r != 0 && errno != EEXIST)
error("Host '%s': cannot create directory '%s'", rrdhost_hostname(host), filename);
}
- snprintfz(filename, FILENAME_MAX, "%s/health/health-log.db", host->varlib_dir);
- host->health_log_filename = strdupz(filename);
snprintfz(filename, FILENAME_MAX, "%s/alarm-notify.sh", netdata_configured_primary_plugins_dir);
- host->health_default_exec = string_strdupz(config_get(CONFIG_SECTION_HEALTH, "script to execute on alarm", filename));
- host->health_default_recipient = string_strdupz("root");
-
- if (!file_is_migrated(host->health_log_filename)) {
- int rc = sql_create_health_log_table(host);
- if (unlikely(rc)) {
- log_health("[%s]: Failed to create health log table in the database", rrdhost_hostname(host));
- health_alarm_log_load(host);
- health_alarm_log_open(host);
- }
- else {
- health_alarm_log_load(host);
- add_migrated_file(host->health_log_filename, 0);
- }
- } else {
- // TODO: This needs to go to the metadata thread
- // Health should wait before accessing the table (needs to be created by the metadata thread)
- sql_create_health_log_table(host);
- sql_health_alarm_log_load(host);
- }
+ host->health.health_default_exec = string_strdupz(config_get(CONFIG_SECTION_HEALTH, "script to execute on alarm", filename));
+ host->health.health_default_recipient = string_strdupz("root");
+
+ // TODO: This needs to go to the metadata thread
+ // Health should wait before accessing the table (needs to be created by the metadata thread)
+ sql_create_health_log_table(host);
+ sql_health_alarm_log_load(host);
// ------------------------------------------------------------------------
// load health configuration
@@ -828,16 +825,14 @@ static void initialize_health(RRDHOST *host, int is_localhost) {
//Discard alarms with labels that do not apply to host
rrdcalc_delete_alerts_not_matching_host_labels_from_this_host(host);
-
- health_silencers_init();
}
-static void health_sleep(time_t next_run, unsigned int loop __maybe_unused, RRDHOST *host) {
+static void health_sleep(time_t next_run, unsigned int loop __maybe_unused) {
time_t now = now_realtime_sec();
if(now < next_run) {
worker_is_idle();
debug(D_HEALTH, "Health monitoring iteration no %u done. Next iteration in %d secs", loop, (int) (next_run - now));
- while (now < next_run && host->health_enabled && !netdata_exit) {
+ while (now < next_run && service_running(SERVICE_HEALTH)) {
sleep_usec(USEC_PER_SEC);
now = now_realtime_sec();
}
@@ -995,555 +990,567 @@ void *health_main(void *ptr) {
worker_register_job_name(WORKER_HEALTH_JOB_DELAYED_INIT_RRDSET, "rrdset init");
worker_register_job_name(WORKER_HEALTH_JOB_DELAYED_INIT_RRDDIM, "rrddim init");
- struct health_state *h = ptr;
- netdata_thread_cleanup_push(health_thread_cleanup, ptr);
-
- RRDHOST *host = h->host;
- initialize_health(host, host == localhost);
+ netdata_thread_cleanup_push(health_main_cleanup, ptr);
int min_run_every = (int)config_get_number(CONFIG_SECTION_HEALTH, "run at least every seconds", 10);
if(min_run_every < 1) min_run_every = 1;
- int cleanup_sql_every_loop = 7200 / min_run_every;
-
- time_t now = now_realtime_sec();
time_t hibernation_delay = config_get_number(CONFIG_SECTION_HEALTH, "postpone alarms during hibernation for seconds", 60);
bool health_running_logged = false;
- rrdhost_rdlock(host); //CHECK
- rrdcalc_delete_alerts_not_matching_host_labels_from_this_host(host);
- rrdhost_unlock(host);
+ rrdcalc_delete_alerts_not_matching_host_labels_from_all_hosts();
unsigned int loop = 0;
#ifdef ENABLE_ACLK
unsigned int marked_aclk_reload_loop = 0;
#endif
- while(!netdata_exit && host->health_enabled) {
+ while(service_running(SERVICE_HEALTH)) {
loop++;
debug(D_HEALTH, "Health monitoring iteration no %u started", loop);
- now = now_realtime_sec();
+ time_t now = now_realtime_sec();
int runnable = 0, apply_hibernation_delay = 0;
time_t next_run = now + min_run_every;
RRDCALC *rc;
+ RRDHOST *host;
if (unlikely(check_if_resumed_from_suspension())) {
apply_hibernation_delay = 1;
log_health(
- "[%s]: Postponing alarm checks for %"PRId64" seconds, "
+ "Postponing alarm checks for %"PRId64" seconds, "
"because it seems that the system was just resumed from suspension.",
- rrdhost_hostname(host),
(int64_t)hibernation_delay);
}
if (unlikely(silencers->all_alarms && silencers->stype == STYPE_DISABLE_ALARMS)) {
- static __thread int logged=0;
+ static int logged=0;
if (!logged) {
- log_health("[%s]: Skipping health checks, because all alarms are disabled via a %s command.",
- rrdhost_hostname(host),
+ log_health("Skipping health checks, because all alarms are disabled via a %s command.",
HEALTH_CMDAPI_CMD_DISABLEALL);
logged = 1;
}
}
#ifdef ENABLE_ACLK
- if (host->aclk_alert_reloaded && !marked_aclk_reload_loop)
+ if (aclk_alert_reloaded && !marked_aclk_reload_loop)
marked_aclk_reload_loop = loop;
#endif
- if (unlikely(apply_hibernation_delay)) {
- log_health(
- "[%s]: Postponing health checks for %"PRId64" seconds.",
- rrdhost_hostname(host),
- (int64_t)hibernation_delay);
-
- host->health_delay_up_to = now + hibernation_delay;
- next_run = now + hibernation_delay;
- health_sleep(next_run, loop, host);
- }
+ worker_is_busy(WORKER_HEALTH_JOB_RRD_LOCK);
+ rrd_rdlock();
- if (unlikely(host->health_delay_up_to)) {
- if (unlikely(now < host->health_delay_up_to)) {
- next_run = host->health_delay_up_to;
- health_sleep(next_run, loop, host);
- continue;
- }
+ rrdhost_foreach_read(host) {
- log_health("[%s]: Resuming health checks after delay.", rrdhost_hostname(host));
- host->health_delay_up_to = 0;
- }
+ if(unlikely(!service_running(SERVICE_HEALTH)))
+ break;
- // wait until cleanup of obsolete charts on children is complete
- if (host != localhost) {
- if (unlikely(host->trigger_chart_obsoletion_check == 1)) {
- log_health("[%s]: Waiting for chart obsoletion check.", rrdhost_hostname(host));
- health_sleep(next_run, loop, host);
+ if (unlikely(!host->health.health_enabled))
continue;
- }
- }
- if (!health_running_logged) {
- log_health("[%s]: Health is running.", rrdhost_hostname(host));
- health_running_logged = true;
- }
-
- if(likely(!host->health_log_fp) && (loop == 1 || loop % cleanup_sql_every_loop == 0))
- sql_health_alarm_log_cleanup(host);
+ if (unlikely(!rrdhost_flag_check(host, RRDHOST_FLAG_INITIALIZED_HEALTH))) {
+ rrd_unlock();
+ initialize_health(host, host == localhost);
+ rrd_rdlock();
+ }
- health_execute_delayed_initializations(host);
+ health_execute_delayed_initializations(host);
- worker_is_busy(WORKER_HEALTH_JOB_HOST_LOCK);
+ rrdcalc_delete_alerts_not_matching_host_labels_from_this_host(host);
- // the first loop is to lookup values from the db
- foreach_rrdcalc_in_rrdhost_read(host, rc) {
+ if (unlikely(apply_hibernation_delay)) {
+ log_health(
+ "[%s]: Postponing health checks for %"PRId64" seconds.",
+ rrdhost_hostname(host),
+ (int64_t)hibernation_delay);
- rrdcalc_update_info_using_rrdset_labels(rc);
+ host->health.health_delay_up_to = now + hibernation_delay;
+ }
- if (update_disabled_silenced(host, rc))
- continue;
+ if (unlikely(host->health.health_delay_up_to)) {
+ if (unlikely(now < host->health.health_delay_up_to)) {
+ continue;
+ }
- // create an alert removed event if the chart is obsolete and
- // has stopped being collected for 60 seconds
- if (unlikely(rc->rrdset && rc->status != RRDCALC_STATUS_REMOVED &&
- rrdset_flag_check(rc->rrdset, RRDSET_FLAG_OBSOLETE) &&
- now > (rc->rrdset->last_collected_time.tv_sec + 60))) {
- if (!rrdcalc_isrepeating(rc)) {
- worker_is_busy(WORKER_HEALTH_JOB_ALARM_LOG_ENTRY);
- time_t now = now_realtime_sec();
-
- ALARM_ENTRY *ae = health_create_alarm_entry(
- host,
- rc->id,
- rc->next_event_id++,
- rc->config_hash_id,
- now,
- rc->name,
- rc->rrdset->id,
- rc->rrdset->context,
- rc->rrdset->family,
- rc->classification,
- rc->component,
- rc->type,
- rc->exec,
- rc->recipient,
- now - rc->last_status_change,
- rc->value,
- NAN,
- rc->status,
- RRDCALC_STATUS_REMOVED,
- rc->source,
- rc->units,
- rc->info,
- 0,
- rrdcalc_isrepeating(rc)?HEALTH_ENTRY_FLAG_IS_REPEATING:0);
-
- if (ae) {
- health_alarm_log_add_entry(host, ae);
- rc->old_status = rc->status;
- rc->status = RRDCALC_STATUS_REMOVED;
- rc->last_status_change = now;
- rc->last_updated = now;
- rc->value = NAN;
+ log_health("[%s]: Resuming health checks after delay.", rrdhost_hostname(host));
+ host->health.health_delay_up_to = 0;
+ }
-#ifdef ENABLE_ACLK
- if (netdata_cloud_setting && likely(!host->aclk_alert_reloaded))
- sql_queue_alarm_to_aclk(host, ae, 1);
-#endif
- }
+ // wait until cleanup of obsolete charts on children is complete
+ if (host != localhost) {
+ if (unlikely(host->trigger_chart_obsoletion_check == 1)) {
+ log_health("[%s]: Waiting for chart obsoletion check.", rrdhost_hostname(host));
+ continue;
}
}
- if (unlikely(!rrdcalc_isrunnable(rc, now, &next_run))) {
- if (unlikely(rc->run_flags & RRDCALC_FLAG_RUNNABLE))
- rc->run_flags &= ~RRDCALC_FLAG_RUNNABLE;
- continue;
+ if (!health_running_logged) {
+ log_health("[%s]: Health is running.", rrdhost_hostname(host));
+ health_running_logged = true;
}
- runnable++;
- rc->old_value = rc->value;
- rc->run_flags |= RRDCALC_FLAG_RUNNABLE;
+ worker_is_busy(WORKER_HEALTH_JOB_HOST_LOCK);
- // ------------------------------------------------------------
- // if there is database lookup, do it
+ // the first loop is to lookup values from the db
+ foreach_rrdcalc_in_rrdhost_read(host, rc) {
- if (unlikely(RRDCALC_HAS_DB_LOOKUP(rc))) {
- worker_is_busy(WORKER_HEALTH_JOB_DB_QUERY);
+ if(unlikely(!service_running(SERVICE_HEALTH)))
+ break;
- /* time_t old_db_timestamp = rc->db_before; */
- int value_is_null = 0;
+ rrdcalc_update_info_using_rrdset_labels(rc);
- int ret = rrdset2value_api_v1(rc->rrdset, NULL, &rc->value, rrdcalc_dimensions(rc), 1,
- rc->after, rc->before, rc->group, NULL,
- 0, rc->options,
- &rc->db_after,&rc->db_before,
- NULL, NULL, NULL,
- &value_is_null, NULL, 0, 0,
- QUERY_SOURCE_HEALTH);
+ if (update_disabled_silenced(host, rc))
+ continue;
- if (unlikely(ret != 200)) {
- // database lookup failed
- rc->value = NAN;
- rc->run_flags |= RRDCALC_FLAG_DB_ERROR;
+ // create an alert removed event if the chart is obsolete and
+ // has stopped being collected for 60 seconds
+ if (unlikely(rc->rrdset && rc->status != RRDCALC_STATUS_REMOVED &&
+ rrdset_flag_check(rc->rrdset, RRDSET_FLAG_OBSOLETE) &&
+ now > (rc->rrdset->last_collected_time.tv_sec + 60))) {
+ if (!rrdcalc_isrepeating(rc)) {
+ worker_is_busy(WORKER_HEALTH_JOB_ALARM_LOG_ENTRY);
+ time_t now = now_realtime_sec();
+
+ ALARM_ENTRY *ae = health_create_alarm_entry(
+ host,
+ rc->id,
+ rc->next_event_id++,
+ rc->config_hash_id,
+ now,
+ rc->name,
+ rc->rrdset->id,
+ rc->rrdset->context,
+ rc->rrdset->family,
+ rc->classification,
+ rc->component,
+ rc->type,
+ rc->exec,
+ rc->recipient,
+ now - rc->last_status_change,
+ rc->value,
+ NAN,
+ rc->status,
+ RRDCALC_STATUS_REMOVED,
+ rc->source,
+ rc->units,
+ rc->info,
+ 0,
+ rrdcalc_isrepeating(rc)?HEALTH_ENTRY_FLAG_IS_REPEATING:0);
+
+ if (ae) {
+ health_alarm_log_add_entry(host, ae);
+ rc->old_status = rc->status;
+ rc->status = RRDCALC_STATUS_REMOVED;
+ rc->last_status_change = now;
+ rc->last_updated = now;
+ rc->value = NAN;
- debug(D_HEALTH, "Health on host '%s', alarm '%s.%s': database lookup returned error %d",
- rrdhost_hostname(host), rrdcalc_chart_name(rc), rrdcalc_name(rc), ret
- );
- } else
- rc->run_flags &= ~RRDCALC_FLAG_DB_ERROR;
-
- /* - RRDCALC_FLAG_DB_STALE not currently used
- if (unlikely(old_db_timestamp == rc->db_before)) {
- // database is stale
-
- debug(D_HEALTH, "Health on host '%s', alarm '%s.%s': database is stale", host->hostname, rc->chart?rc->chart:"NOCHART", rc->name);
-
- if (unlikely(!(rc->rrdcalc_flags & RRDCALC_FLAG_DB_STALE))) {
- rc->rrdcalc_flags |= RRDCALC_FLAG_DB_STALE;
- error("Health on host '%s', alarm '%s.%s': database is stale", host->hostname, rc->chart?rc->chart:"NOCHART", rc->name);
- }
- }
- else if (unlikely(rc->rrdcalc_flags & RRDCALC_FLAG_DB_STALE))
- rc->rrdcalc_flags &= ~RRDCALC_FLAG_DB_STALE;
- */
-
- if (unlikely(value_is_null)) {
- // collected value is null
- rc->value = NAN;
- rc->run_flags |= RRDCALC_FLAG_DB_NAN;
-
- debug(D_HEALTH,
- "Health on host '%s', alarm '%s.%s': database lookup returned empty value (possibly value is not collected yet)",
- rrdhost_hostname(host), rrdcalc_chart_name(rc), rrdcalc_name(rc)
- );
- } else
- rc->run_flags &= ~RRDCALC_FLAG_DB_NAN;
+#ifdef ENABLE_ACLK
+ if (netdata_cloud_setting && likely(!aclk_alert_reloaded))
+ sql_queue_alarm_to_aclk(host, ae, 1);
+#endif
+ }
+ }
+ }
- debug(D_HEALTH, "Health on host '%s', alarm '%s.%s': database lookup gave value " NETDATA_DOUBLE_FORMAT,
- rrdhost_hostname(host), rrdcalc_chart_name(rc), rrdcalc_name(rc), rc->value
- );
- }
+ if (unlikely(!rrdcalc_isrunnable(rc, now, &next_run))) {
+ if (unlikely(rc->run_flags & RRDCALC_FLAG_RUNNABLE))
+ rc->run_flags &= ~RRDCALC_FLAG_RUNNABLE;
+ continue;
+ }
- // ------------------------------------------------------------
- // if there is calculation expression, run it
+ runnable++;
+ rc->old_value = rc->value;
+ rc->run_flags |= RRDCALC_FLAG_RUNNABLE;
- if (unlikely(rc->calculation)) {
- worker_is_busy(WORKER_HEALTH_JOB_CALC_EVAL);
+ // ------------------------------------------------------------
+ // if there is database lookup, do it
- if (unlikely(!expression_evaluate(rc->calculation))) {
- // calculation failed
- rc->value = NAN;
- rc->run_flags |= RRDCALC_FLAG_CALC_ERROR;
+ if (unlikely(RRDCALC_HAS_DB_LOOKUP(rc))) {
+ worker_is_busy(WORKER_HEALTH_JOB_DB_QUERY);
- debug(D_HEALTH, "Health on host '%s', alarm '%s.%s': expression '%s' failed: %s",
- rrdhost_hostname(host), rrdcalc_chart_name(rc), rrdcalc_name(rc),
- rc->calculation->parsed_as, buffer_tostring(rc->calculation->error_msg)
- );
- } else {
- rc->run_flags &= ~RRDCALC_FLAG_CALC_ERROR;
+ /* time_t old_db_timestamp = rc->db_before; */
+ int value_is_null = 0;
- debug(D_HEALTH, "Health on host '%s', alarm '%s.%s': expression '%s' gave value "
- NETDATA_DOUBLE_FORMAT
- ": %s (source: %s)", rrdhost_hostname(host), rrdcalc_chart_name(rc), rrdcalc_name(rc),
- rc->calculation->parsed_as, rc->calculation->result,
- buffer_tostring(rc->calculation->error_msg), rrdcalc_source(rc)
- );
+ int ret = rrdset2value_api_v1(rc->rrdset, NULL, &rc->value, rrdcalc_dimensions(rc), 1,
+ rc->after, rc->before, rc->group, NULL,
+ 0, rc->options,
+ &rc->db_after,&rc->db_before,
+ NULL, NULL, NULL,
+ &value_is_null, NULL, 0, 0,
+ QUERY_SOURCE_HEALTH, STORAGE_PRIORITY_LOW);
- rc->value = rc->calculation->result;
- }
- }
- }
- foreach_rrdcalc_in_rrdhost_done(rc);
+ if (unlikely(ret != 200)) {
+ // database lookup failed
+ rc->value = NAN;
+ rc->run_flags |= RRDCALC_FLAG_DB_ERROR;
- if (unlikely(runnable && !netdata_exit)) {
- foreach_rrdcalc_in_rrdhost_read(host, rc) {
- if (unlikely(!(rc->run_flags & RRDCALC_FLAG_RUNNABLE)))
- continue;
+ debug(D_HEALTH, "Health on host '%s', alarm '%s.%s': database lookup returned error %d",
+ rrdhost_hostname(host), rrdcalc_chart_name(rc), rrdcalc_name(rc), ret
+ );
+ } else
+ rc->run_flags &= ~RRDCALC_FLAG_DB_ERROR;
- if (rc->run_flags & RRDCALC_FLAG_DISABLED) {
- continue;
+ if (unlikely(value_is_null)) {
+ // collected value is null
+ rc->value = NAN;
+ rc->run_flags |= RRDCALC_FLAG_DB_NAN;
+
+ debug(D_HEALTH,
+ "Health on host '%s', alarm '%s.%s': database lookup returned empty value (possibly value is not collected yet)",
+ rrdhost_hostname(host), rrdcalc_chart_name(rc), rrdcalc_name(rc)
+ );
+ } else
+ rc->run_flags &= ~RRDCALC_FLAG_DB_NAN;
+
+ debug(D_HEALTH, "Health on host '%s', alarm '%s.%s': database lookup gave value " NETDATA_DOUBLE_FORMAT,
+ rrdhost_hostname(host), rrdcalc_chart_name(rc), rrdcalc_name(rc), rc->value
+ );
}
- RRDCALC_STATUS warning_status = RRDCALC_STATUS_UNDEFINED;
- RRDCALC_STATUS critical_status = RRDCALC_STATUS_UNDEFINED;
- // --------------------------------------------------------
- // check the warning expression
+ // ------------------------------------------------------------
+ // if there is calculation expression, run it
- if (likely(rc->warning)) {
- worker_is_busy(WORKER_HEALTH_JOB_WARNING_EVAL);
+ if (unlikely(rc->calculation)) {
+ worker_is_busy(WORKER_HEALTH_JOB_CALC_EVAL);
- if (unlikely(!expression_evaluate(rc->warning))) {
+ if (unlikely(!expression_evaluate(rc->calculation))) {
// calculation failed
- rc->run_flags |= RRDCALC_FLAG_WARN_ERROR;
+ rc->value = NAN;
+ rc->run_flags |= RRDCALC_FLAG_CALC_ERROR;
- debug(D_HEALTH,
- "Health on host '%s', alarm '%s.%s': warning expression failed with error: %s",
+ debug(D_HEALTH, "Health on host '%s', alarm '%s.%s': expression '%s' failed: %s",
rrdhost_hostname(host), rrdcalc_chart_name(rc), rrdcalc_name(rc),
- buffer_tostring(rc->warning->error_msg)
+ rc->calculation->parsed_as, buffer_tostring(rc->calculation->error_msg)
);
} else {
- rc->run_flags &= ~RRDCALC_FLAG_WARN_ERROR;
- debug(D_HEALTH, "Health on host '%s', alarm '%s.%s': warning expression gave value "
+ rc->run_flags &= ~RRDCALC_FLAG_CALC_ERROR;
+
+ debug(D_HEALTH, "Health on host '%s', alarm '%s.%s': expression '%s' gave value "
NETDATA_DOUBLE_FORMAT
- ": %s (source: %s)", rrdhost_hostname(host), rrdcalc_chart_name(rc),
- rrdcalc_name(rc), rc->warning->result, buffer_tostring(rc->warning->error_msg), rrdcalc_source(rc)
+ ": %s (source: %s)", rrdhost_hostname(host), rrdcalc_chart_name(rc), rrdcalc_name(rc),
+ rc->calculation->parsed_as, rc->calculation->result,
+ buffer_tostring(rc->calculation->error_msg), rrdcalc_source(rc)
);
- warning_status = rrdcalc_value2status(rc->warning->result);
+
+ rc->value = rc->calculation->result;
}
}
+ }
+ foreach_rrdcalc_in_rrdhost_done(rc);
- // --------------------------------------------------------
- // check the critical expression
+ if (unlikely(runnable && service_running(SERVICE_HEALTH))) {
+ foreach_rrdcalc_in_rrdhost_read(host, rc) {
+ if(unlikely(!service_running(SERVICE_HEALTH)))
+ break;
- if (likely(rc->critical)) {
- worker_is_busy(WORKER_HEALTH_JOB_CRITICAL_EVAL);
+ if (unlikely(!(rc->run_flags & RRDCALC_FLAG_RUNNABLE)))
+ continue;
- if (unlikely(!expression_evaluate(rc->critical))) {
- // calculation failed
- rc->run_flags |= RRDCALC_FLAG_CRIT_ERROR;
+ if (rc->run_flags & RRDCALC_FLAG_DISABLED) {
+ continue;
+ }
+ RRDCALC_STATUS warning_status = RRDCALC_STATUS_UNDEFINED;
+ RRDCALC_STATUS critical_status = RRDCALC_STATUS_UNDEFINED;
+
+ // --------------------------------------------------------
+ // check the warning expression
+
+ if (likely(rc->warning)) {
+ worker_is_busy(WORKER_HEALTH_JOB_WARNING_EVAL);
+
+ if (unlikely(!expression_evaluate(rc->warning))) {
+ // calculation failed
+ rc->run_flags |= RRDCALC_FLAG_WARN_ERROR;
+
+ debug(D_HEALTH,
+ "Health on host '%s', alarm '%s.%s': warning expression failed with error: %s",
+ rrdhost_hostname(host), rrdcalc_chart_name(rc), rrdcalc_name(rc),
+ buffer_tostring(rc->warning->error_msg)
+ );
+ } else {
+ rc->run_flags &= ~RRDCALC_FLAG_WARN_ERROR;
+ debug(D_HEALTH, "Health on host '%s', alarm '%s.%s': warning expression gave value "
+ NETDATA_DOUBLE_FORMAT
+ ": %s (source: %s)", rrdhost_hostname(host), rrdcalc_chart_name(rc),
+ rrdcalc_name(rc), rc->warning->result, buffer_tostring(rc->warning->error_msg), rrdcalc_source(rc)
+ );
+ warning_status = rrdcalc_value2status(rc->warning->result);
+ }
+ }
- debug(D_HEALTH,
- "Health on host '%s', alarm '%s.%s': critical expression failed with error: %s",
- rrdhost_hostname(host), rrdcalc_chart_name(rc), rrdcalc_name(rc),
- buffer_tostring(rc->critical->error_msg)
- );
- } else {
- rc->run_flags &= ~RRDCALC_FLAG_CRIT_ERROR;
- debug(D_HEALTH, "Health on host '%s', alarm '%s.%s': critical expression gave value "
- NETDATA_DOUBLE_FORMAT
- ": %s (source: %s)", rrdhost_hostname(host), rrdcalc_chart_name(rc),
- rrdcalc_name(rc), rc->critical->result, buffer_tostring(rc->critical->error_msg),
- rrdcalc_source(rc)
- );
- critical_status = rrdcalc_value2status(rc->critical->result);
+ // --------------------------------------------------------
+ // check the critical expression
+
+ if (likely(rc->critical)) {
+ worker_is_busy(WORKER_HEALTH_JOB_CRITICAL_EVAL);
+
+ if (unlikely(!expression_evaluate(rc->critical))) {
+ // calculation failed
+ rc->run_flags |= RRDCALC_FLAG_CRIT_ERROR;
+
+ debug(D_HEALTH,
+ "Health on host '%s', alarm '%s.%s': critical expression failed with error: %s",
+ rrdhost_hostname(host), rrdcalc_chart_name(rc), rrdcalc_name(rc),
+ buffer_tostring(rc->critical->error_msg)
+ );
+ } else {
+ rc->run_flags &= ~RRDCALC_FLAG_CRIT_ERROR;
+ debug(D_HEALTH, "Health on host '%s', alarm '%s.%s': critical expression gave value "
+ NETDATA_DOUBLE_FORMAT
+ ": %s (source: %s)", rrdhost_hostname(host), rrdcalc_chart_name(rc),
+ rrdcalc_name(rc), rc->critical->result, buffer_tostring(rc->critical->error_msg),
+ rrdcalc_source(rc)
+ );
+ critical_status = rrdcalc_value2status(rc->critical->result);
+ }
}
- }
- // --------------------------------------------------------
- // decide the final alarm status
+ // --------------------------------------------------------
+ // decide the final alarm status
- RRDCALC_STATUS status = RRDCALC_STATUS_UNDEFINED;
+ RRDCALC_STATUS status = RRDCALC_STATUS_UNDEFINED;
- switch (warning_status) {
- case RRDCALC_STATUS_CLEAR:
- status = RRDCALC_STATUS_CLEAR;
- break;
+ switch (warning_status) {
+ case RRDCALC_STATUS_CLEAR:
+ status = RRDCALC_STATUS_CLEAR;
+ break;
- case RRDCALC_STATUS_RAISED:
- status = RRDCALC_STATUS_WARNING;
- break;
+ case RRDCALC_STATUS_RAISED:
+ status = RRDCALC_STATUS_WARNING;
+ break;
- default:
- break;
- }
+ default:
+ break;
+ }
- switch (critical_status) {
- case RRDCALC_STATUS_CLEAR:
- if (status == RRDCALC_STATUS_UNDEFINED)
- status = RRDCALC_STATUS_CLEAR;
- break;
+ switch (critical_status) {
+ case RRDCALC_STATUS_CLEAR:
+ if (status == RRDCALC_STATUS_UNDEFINED)
+ status = RRDCALC_STATUS_CLEAR;
+ break;
- case RRDCALC_STATUS_RAISED:
- status = RRDCALC_STATUS_CRITICAL;
- break;
+ case RRDCALC_STATUS_RAISED:
+ status = RRDCALC_STATUS_CRITICAL;
+ break;
- default:
- break;
- }
+ default:
+ break;
+ }
- // --------------------------------------------------------
- // check if the new status and the old differ
+ // --------------------------------------------------------
+ // check if the new status and the old differ
- if (status != rc->status) {
- worker_is_busy(WORKER_HEALTH_JOB_ALARM_LOG_ENTRY);
- int delay = 0;
+ if (status != rc->status) {
+ worker_is_busy(WORKER_HEALTH_JOB_ALARM_LOG_ENTRY);
+ int delay = 0;
- // apply trigger hysteresis
+ // apply trigger hysteresis
- if (now > rc->delay_up_to_timestamp) {
- rc->delay_up_current = rc->delay_up_duration;
- rc->delay_down_current = rc->delay_down_duration;
- rc->delay_last = 0;
- rc->delay_up_to_timestamp = 0;
- } else {
- rc->delay_up_current = (int) (rc->delay_up_current * rc->delay_multiplier);
- if (rc->delay_up_current > rc->delay_max_duration)
- rc->delay_up_current = rc->delay_max_duration;
+ if (now > rc->delay_up_to_timestamp) {
+ rc->delay_up_current = rc->delay_up_duration;
+ rc->delay_down_current = rc->delay_down_duration;
+ rc->delay_last = 0;
+ rc->delay_up_to_timestamp = 0;
+ } else {
+ rc->delay_up_current = (int) (rc->delay_up_current * rc->delay_multiplier);
+ if (rc->delay_up_current > rc->delay_max_duration)
+ rc->delay_up_current = rc->delay_max_duration;
- rc->delay_down_current = (int) (rc->delay_down_current * rc->delay_multiplier);
- if (rc->delay_down_current > rc->delay_max_duration)
- rc->delay_down_current = rc->delay_max_duration;
- }
+ rc->delay_down_current = (int) (rc->delay_down_current * rc->delay_multiplier);
+ if (rc->delay_down_current > rc->delay_max_duration)
+ rc->delay_down_current = rc->delay_max_duration;
+ }
- if (status > rc->status)
- delay = rc->delay_up_current;
- else
- delay = rc->delay_down_current;
-
- // COMMENTED: because we do need to send raising alarms
- // if(now + delay < rc->delay_up_to_timestamp)
- // delay = (int)(rc->delay_up_to_timestamp - now);
-
- rc->delay_last = delay;
- rc->delay_up_to_timestamp = now + delay;
-
- ALARM_ENTRY *ae = health_create_alarm_entry(
- host,
- rc->id,
- rc->next_event_id++,
- rc->config_hash_id,
- now,
- rc->name,
- rc->rrdset->id,
- rc->rrdset->context,
- rc->rrdset->family,
- rc->classification,
- rc->component,
- rc->type,
- rc->exec,
- rc->recipient,
- now - rc->last_status_change,
- rc->old_value,
- rc->value,
- rc->status,
- status,
- rc->source,
- rc->units,
- rc->info,
- rc->delay_last,
- (
- ((rc->options & RRDCALC_OPTION_NO_CLEAR_NOTIFICATION)? HEALTH_ENTRY_FLAG_NO_CLEAR_NOTIFICATION : 0) |
- ((rc->run_flags & RRDCALC_FLAG_SILENCED)? HEALTH_ENTRY_FLAG_SILENCED : 0) |
- (rrdcalc_isrepeating(rc)?HEALTH_ENTRY_FLAG_IS_REPEATING:0)
- )
- );
-
- health_alarm_log_add_entry(host, ae);
-
- log_health("[%s]: Alert event for [%s.%s], value [%s], status [%s].", rrdhost_hostname(host), ae_chart_name(ae), ae_name(ae), ae_new_value_string(ae), rrdcalc_status2string(ae->new_status));
-
- rc->last_status_change = now;
- rc->old_status = rc->status;
- rc->status = status;
- }
+ if (status > rc->status)
+ delay = rc->delay_up_current;
+ else
+ delay = rc->delay_down_current;
+
+ // COMMENTED: because we do need to send raising alarms
+ // if(now + delay < rc->delay_up_to_timestamp)
+ // delay = (int)(rc->delay_up_to_timestamp - now);
+
+ rc->delay_last = delay;
+ rc->delay_up_to_timestamp = now + delay;
+
+ ALARM_ENTRY *ae = health_create_alarm_entry(
+ host,
+ rc->id,
+ rc->next_event_id++,
+ rc->config_hash_id,
+ now,
+ rc->name,
+ rc->rrdset->id,
+ rc->rrdset->context,
+ rc->rrdset->family,
+ rc->classification,
+ rc->component,
+ rc->type,
+ rc->exec,
+ rc->recipient,
+ now - rc->last_status_change,
+ rc->old_value,
+ rc->value,
+ rc->status,
+ status,
+ rc->source,
+ rc->units,
+ rc->info,
+ rc->delay_last,
+ (
+ ((rc->options & RRDCALC_OPTION_NO_CLEAR_NOTIFICATION)? HEALTH_ENTRY_FLAG_NO_CLEAR_NOTIFICATION : 0) |
+ ((rc->run_flags & RRDCALC_FLAG_SILENCED)? HEALTH_ENTRY_FLAG_SILENCED : 0) |
+ (rrdcalc_isrepeating(rc)?HEALTH_ENTRY_FLAG_IS_REPEATING:0)
+ )
+ );
- rc->last_updated = now;
- rc->next_update = now + rc->update_every;
+ health_alarm_log_add_entry(host, ae);
- if (next_run > rc->next_update)
- next_run = rc->next_update;
- }
- foreach_rrdcalc_in_rrdhost_done(rc);
+ log_health("[%s]: Alert event for [%s.%s], value [%s], status [%s].", rrdhost_hostname(host), ae_chart_name(ae), ae_name(ae), ae_new_value_string(ae), rrdcalc_status2string(ae->new_status));
- // process repeating alarms
- foreach_rrdcalc_in_rrdhost_read(host, rc) {
- int repeat_every = 0;
- if(unlikely(rrdcalc_isrepeating(rc) && rc->delay_up_to_timestamp <= now)) {
- if(unlikely(rc->status == RRDCALC_STATUS_WARNING)) {
- rc->run_flags &= ~RRDCALC_FLAG_RUN_ONCE;
- repeat_every = rc->warn_repeat_every;
- } else if(unlikely(rc->status == RRDCALC_STATUS_CRITICAL)) {
- rc->run_flags &= ~RRDCALC_FLAG_RUN_ONCE;
- repeat_every = rc->crit_repeat_every;
- } else if(unlikely(rc->status == RRDCALC_STATUS_CLEAR)) {
- if(!(rc->run_flags & RRDCALC_FLAG_RUN_ONCE)) {
- if(rc->old_status == RRDCALC_STATUS_CRITICAL) {
- repeat_every = 1;
- } else if (rc->old_status == RRDCALC_STATUS_WARNING) {
- repeat_every = 1;
+ rc->last_status_change = now;
+ rc->old_status = rc->status;
+ rc->status = status;
+ }
+
+ rc->last_updated = now;
+ rc->next_update = now + rc->update_every;
+
+ if (next_run > rc->next_update)
+ next_run = rc->next_update;
+ }
+ foreach_rrdcalc_in_rrdhost_done(rc);
+
+ // process repeating alarms
+ foreach_rrdcalc_in_rrdhost_read(host, rc) {
+ if(unlikely(!service_running(SERVICE_HEALTH)))
+ break;
+
+ int repeat_every = 0;
+ if(unlikely(rrdcalc_isrepeating(rc) && rc->delay_up_to_timestamp <= now)) {
+ if(unlikely(rc->status == RRDCALC_STATUS_WARNING)) {
+ rc->run_flags &= ~RRDCALC_FLAG_RUN_ONCE;
+ repeat_every = rc->warn_repeat_every;
+ } else if(unlikely(rc->status == RRDCALC_STATUS_CRITICAL)) {
+ rc->run_flags &= ~RRDCALC_FLAG_RUN_ONCE;
+ repeat_every = rc->crit_repeat_every;
+ } else if(unlikely(rc->status == RRDCALC_STATUS_CLEAR)) {
+ if(!(rc->run_flags & RRDCALC_FLAG_RUN_ONCE)) {
+ if(rc->old_status == RRDCALC_STATUS_CRITICAL) {
+ repeat_every = 1;
+ } else if (rc->old_status == RRDCALC_STATUS_WARNING) {
+ repeat_every = 1;
+ }
}
}
+ } else {
+ continue;
}
- } else {
- continue;
- }
- if(unlikely(repeat_every > 0 && (rc->last_repeat + repeat_every) <= now)) {
- worker_is_busy(WORKER_HEALTH_JOB_ALARM_LOG_ENTRY);
- rc->last_repeat = now;
- if (likely(rc->times_repeat < UINT32_MAX)) rc->times_repeat++;
-
- ALARM_ENTRY *ae = health_create_alarm_entry(
- host,
- rc->id,
- rc->next_event_id++,
- rc->config_hash_id,
- now,
- rc->name,
- rc->rrdset->id,
- rc->rrdset->context,
- rc->rrdset->family,
- rc->classification,
- rc->component,
- rc->type,
- rc->exec,
- rc->recipient,
- now - rc->last_status_change,
- rc->old_value,
- rc->value,
- rc->old_status,
- rc->status,
- rc->source,
- rc->units,
- rc->info,
- rc->delay_last,
- (
- ((rc->options & RRDCALC_OPTION_NO_CLEAR_NOTIFICATION)? HEALTH_ENTRY_FLAG_NO_CLEAR_NOTIFICATION : 0) |
- ((rc->run_flags & RRDCALC_FLAG_SILENCED)? HEALTH_ENTRY_FLAG_SILENCED : 0) |
- (rrdcalc_isrepeating(rc)?HEALTH_ENTRY_FLAG_IS_REPEATING:0)
- )
- );
-
- ae->last_repeat = rc->last_repeat;
- if (!(rc->run_flags & RRDCALC_FLAG_RUN_ONCE) && rc->status == RRDCALC_STATUS_CLEAR) {
- ae->flags |= HEALTH_ENTRY_RUN_ONCE;
+ if(unlikely(repeat_every > 0 && (rc->last_repeat + repeat_every) <= now)) {
+ worker_is_busy(WORKER_HEALTH_JOB_ALARM_LOG_ENTRY);
+ rc->last_repeat = now;
+ if (likely(rc->times_repeat < UINT32_MAX)) rc->times_repeat++;
+
+ ALARM_ENTRY *ae = health_create_alarm_entry(
+ host,
+ rc->id,
+ rc->next_event_id++,
+ rc->config_hash_id,
+ now,
+ rc->name,
+ rc->rrdset->id,
+ rc->rrdset->context,
+ rc->rrdset->family,
+ rc->classification,
+ rc->component,
+ rc->type,
+ rc->exec,
+ rc->recipient,
+ now - rc->last_status_change,
+ rc->old_value,
+ rc->value,
+ rc->old_status,
+ rc->status,
+ rc->source,
+ rc->units,
+ rc->info,
+ rc->delay_last,
+ (
+ ((rc->options & RRDCALC_OPTION_NO_CLEAR_NOTIFICATION)? HEALTH_ENTRY_FLAG_NO_CLEAR_NOTIFICATION : 0) |
+ ((rc->run_flags & RRDCALC_FLAG_SILENCED)? HEALTH_ENTRY_FLAG_SILENCED : 0) |
+ (rrdcalc_isrepeating(rc)?HEALTH_ENTRY_FLAG_IS_REPEATING:0)
+ )
+ );
+
+ ae->last_repeat = rc->last_repeat;
+ if (!(rc->run_flags & RRDCALC_FLAG_RUN_ONCE) && rc->status == RRDCALC_STATUS_CLEAR) {
+ ae->flags |= HEALTH_ENTRY_RUN_ONCE;
+ }
+ rc->run_flags |= RRDCALC_FLAG_RUN_ONCE;
+ health_process_notifications(host, ae);
+ debug(D_HEALTH, "Notification sent for the repeating alarm %u.", ae->alarm_id);
+ health_alarm_wait_for_execution(ae);
+ health_alarm_log_free_one_nochecks_nounlink(ae);
}
- rc->run_flags |= RRDCALC_FLAG_RUN_ONCE;
- health_process_notifications(host, ae);
- debug(D_HEALTH, "Notification sent for the repeating alarm %u.", ae->alarm_id);
- health_alarm_wait_for_execution(ae);
- health_alarm_log_free_one_nochecks_nounlink(ae);
}
+ foreach_rrdcalc_in_rrdhost_done(rc);
}
- foreach_rrdcalc_in_rrdhost_done(rc);
- }
- if (unlikely(netdata_exit))
- break;
+ if (unlikely(!service_running(SERVICE_HEALTH)))
+ break;
- // execute notifications
- // and cleanup
- worker_is_busy(WORKER_HEALTH_JOB_ALARM_LOG_PROCESS);
- health_alarm_log_process(host);
+ // execute notifications
+ // and cleanup
+ worker_is_busy(WORKER_HEALTH_JOB_ALARM_LOG_PROCESS);
+ health_alarm_log_process(host);
- if (unlikely(netdata_exit)) {
- // wait for all notifications to finish before allowing health to be cleaned up
- ALARM_ENTRY *ae;
- while (NULL != (ae = alarm_notifications_in_progress.head)) {
- health_alarm_wait_for_execution(ae);
+ if (unlikely(!service_running(SERVICE_HEALTH))) {
+ // wait for all notifications to finish before allowing health to be cleaned up
+ ALARM_ENTRY *ae;
+ while (NULL != (ae = alarm_notifications_in_progress.head)) {
+ if(unlikely(!service_running(SERVICE_HEALTH)))
+ break;
+
+ health_alarm_wait_for_execution(ae);
+ }
+ break;
}
- break;
- }
+ } //for each host
+
+ rrd_unlock();
// wait for all notifications to finish before allowing health to be cleaned up
ALARM_ENTRY *ae;
while (NULL != (ae = alarm_notifications_in_progress.head)) {
+ if(unlikely(!service_running(SERVICE_HEALTH)))
+ break;
+
health_alarm_wait_for_execution(ae);
}
#ifdef ENABLE_ACLK
- if (netdata_cloud_setting && unlikely(host->aclk_alert_reloaded) && loop > (marked_aclk_reload_loop + 2)) {
- sql_queue_removed_alerts_to_aclk(host);
- host->aclk_alert_reloaded = 0;
+ if (netdata_cloud_setting && unlikely(aclk_alert_reloaded) && loop > (marked_aclk_reload_loop + 2)) {
+ rrdhost_foreach_read(host) {
+ if(unlikely(!service_running(SERVICE_HEALTH)))
+ break;
+
+ if (unlikely(!host->health.health_enabled))
+ continue;
+
+ sql_queue_removed_alerts_to_aclk(host);
+ }
+ aclk_alert_reloaded = 0;
marked_aclk_reload_loop = 0;
}
#endif
- if(unlikely(netdata_exit))
+ if(unlikely(!service_running(SERVICE_HEALTH)))
break;
- health_sleep(next_run, loop, host);
+ health_sleep(next_run, loop);
} // forever
@@ -1554,28 +1561,13 @@ void *health_main(void *ptr) {
void health_add_host_labels(void) {
DICTIONARY *labels = localhost->rrdlabels;
+ // The source should be CONF, but when it is set, these labels are exported by default ('send configured labels' in exporting.conf).
+ // Their export seems to break exporting to Graphite, see https://github.com/netdata/netdata/issues/14084.
+
int is_ephemeral = appconfig_get_boolean(&netdata_config, CONFIG_SECTION_HEALTH, "is ephemeral", CONFIG_BOOLEAN_NO);
- rrdlabels_add(labels, "_is_ephemeral", is_ephemeral ? "true" : "false", RRDLABEL_SRC_CONFIG);
+ rrdlabels_add(labels, "_is_ephemeral", is_ephemeral ? "true" : "false", RRDLABEL_SRC_AUTO);
int has_unstable_connection = appconfig_get_boolean(&netdata_config, CONFIG_SECTION_HEALTH, "has unstable connection", CONFIG_BOOLEAN_NO);
- rrdlabels_add(labels, "_has_unstable_connection", has_unstable_connection ? "true" : "false", RRDLABEL_SRC_CONFIG);
+ rrdlabels_add(labels, "_has_unstable_connection", has_unstable_connection ? "true" : "false", RRDLABEL_SRC_AUTO);
}
-void health_thread_spawn(RRDHOST * host) {
- if(!host->health_spawn) {
- char tag[NETDATA_THREAD_TAG_MAX + 1];
- snprintfz(tag, NETDATA_THREAD_TAG_MAX, "HEALTH[%s]", rrdhost_hostname(host));
- struct health_state *health = callocz(1, sizeof(*health));
- health->host = host;
-
- if(netdata_thread_create(&host->health_thread, tag, NETDATA_THREAD_OPTION_JOINABLE, health_main, (void *) health)) {
- log_health("[%s]: Failed to create new thread for client.", rrdhost_hostname(host));
- error("HEALTH [%s]: Failed to create new thread for client.", rrdhost_hostname(host));
- }
- else {
- log_health("[%s]: Created new thread for client.", rrdhost_hostname(host));
- host->health_spawn = 1;
- host->aclk_alert_reloaded = 1;
- }
- }
-}
diff --git a/health/health.d/cgroups.conf b/health/health.d/cgroups.conf
index 4bfe38b6..08260ff6 100644
--- a/health/health.d/cgroups.conf
+++ b/health/health.d/cgroups.conf
@@ -51,7 +51,7 @@ component: Network
lookup: average -1m unaligned of received
units: packets
every: 10s
- info: average number of packets received by the network interface $family over the last minute
+ info: average number of packets received by the network interface ${label:device} over the last minute
template: cgroup_10s_received_packets_storm
on: cgroup.net_packets
@@ -66,7 +66,7 @@ component: Network
warn: $this > (($status >= $WARNING)?(200):(5000))
crit: $this > (($status == $CRITICAL)?(5000):(6000))
options: no-clear-notification
- info: ratio of average number of received packets for the network interface $family over the last 10 seconds, \
+ info: ratio of average number of received packets for the network interface ${label:device} over the last 10 seconds, \
compared to the rate over the last minute
to: sysadmin
@@ -121,7 +121,7 @@ component: Network
lookup: average -1m unaligned of received
units: packets
every: 10s
- info: average number of packets received by the network interface $family over the last minute
+ info: average number of packets received by the network interface ${label:device} over the last minute
template: k8s_cgroup_10s_received_packets_storm
on: k8s.cgroup.net_packets
@@ -136,6 +136,6 @@ component: Network
warn: $this > (($status >= $WARNING)?(200):(5000))
crit: $this > (($status == $CRITICAL)?(5000):(6000))
options: no-clear-notification
- info: ratio of average number of received packets for the network interface $family over the last 10 seconds, \
+ info: ratio of average number of received packets for the network interface ${label:device} over the last 10 seconds, \
compared to the rate over the last minute
to: sysadmin
diff --git a/health/health.d/consul.conf b/health/health.d/consul.conf
new file mode 100644
index 00000000..dff6d2df
--- /dev/null
+++ b/health/health.d/consul.conf
@@ -0,0 +1,159 @@
+# you can disable an alarm notification by setting the 'to' line to: silent
+
+ template: consul_license_expiration_time
+ on: consul.license_expiration_time
+ class: Errors
+ type: ServiceMesh
+component: Consul
+ calc: $license_expiration
+ every: 60m
+ units: seconds
+ warn: $this < 14*24*60*60
+ crit: $this < 7*24*60*60
+ info: Consul Enterprise licence expiration time on node ${label:node_name} datacenter ${label:datacenter}
+ to: sysadmin
+
+ template: consul_autopilot_health_status
+ on: consul.autopilot_health_status
+ class: Errors
+ type: ServiceMesh
+component: Consul
+ calc: $unhealthy
+ every: 10s
+ units: status
+ warn: $this == 1
+ delay: down 5m multiplier 1.5 max 1h
+ info: datacenter ${label:datacenter} cluster is unhealthy as reported by server ${label:node_name}
+ to: sysadmin
+
+ template: consul_autopilot_server_health_status
+ on: consul.autopilot_server_health_status
+ class: Errors
+ type: ServiceMesh
+component: Consul
+ calc: $unhealthy
+ every: 10s
+ units: status
+ warn: $this == 1
+ delay: down 5m multiplier 1.5 max 1h
+ info: server ${label:node_name} from datacenter ${label:datacenter} is unhealthy
+ to: sysadmin
+
+ template: consul_raft_leader_last_contact_time
+ on: consul.raft_leader_last_contact_time
+ class: Errors
+ type: ServiceMesh
+component: Consul
+ lookup: average -1m unaligned of quantile_0.5
+ every: 10s
+ units: milliseconds
+ warn: $this > (($status >= $WARNING) ? (150) : (200))
+ crit: $this > (($status == $CRITICAL) ? (200) : (500))
+ delay: down 5m multiplier 1.5 max 1h
+ info: median time elapsed since leader server ${label:node_name} datacenter ${label:datacenter} was last able to contact the follower nodes
+ to: sysadmin
+
+ template: consul_raft_leadership_transitions
+ on: consul.raft_leadership_transitions_rate
+ class: Errors
+ type: ServiceMesh
+component: Consul
+ lookup: sum -1m unaligned
+ every: 10s
+ units: transitions
+ warn: $this > 0
+ delay: down 5m multiplier 1.5 max 1h
+ info: there has been a leadership change and server ${label:node_name} datacenter ${label:datacenter} has become the leader
+ to: sysadmin
+
+ template: consul_raft_thread_main_saturation
+ on: consul.raft_thread_main_saturation_perc
+ class: Utilization
+ type: ServiceMesh
+component: Consul
+ lookup: average -1m unaligned of quantile_0.9
+ every: 10s
+ units: percentage
+ warn: $this > (($status >= $WARNING) ? (40) : (50))
+ delay: down 5m multiplier 1.5 max 1h
+ info: average saturation of the main Raft goroutine on server ${label:node_name} datacenter ${label:datacenter}
+ to: sysadmin
+
+ template: consul_raft_thread_fsm_saturation
+ on: consul.raft_thread_fsm_saturation_perc
+ class: Utilization
+ type: ServiceMesh
+component: Consul
+ lookup: average -1m unaligned of quantile_0.9
+ every: 10s
+ units: milliseconds
+ warn: $this > (($status >= $WARNING) ? (40) : (50))
+ delay: down 5m multiplier 1.5 max 1h
+ info: average saturation of the FSM Raft goroutine on server ${label:node_name} datacenter ${label:datacenter}
+ to: sysadmin
+
+ template: consul_client_rpc_requests_exceeded
+ on: consul.client_rpc_requests_exceeded_rate
+ class: Errors
+ type: ServiceMesh
+component: Consul
+ lookup: sum -1m unaligned
+ every: 10s
+ units: requests
+ warn: $this > (($status >= $WARNING) ? (0) : (5))
+ delay: down 5m multiplier 1.5 max 1h
+ info: number of rate-limited RPC requests made by server ${label:node_name} datacenter ${label:datacenter}
+ to: sysadmin
+
+ template: consul_client_rpc_requests_failed
+ on: consul.client_rpc_requests_failed_rate
+ class: Errors
+ type: ServiceMesh
+component: Consul
+ lookup: sum -1m unaligned
+ every: 10s
+ units: requests
+ warn: $this > (($status >= $WARNING) ? (0) : (5))
+ delay: down 5m multiplier 1.5 max 1h
+ info: number of failed RPC requests made by server ${label:node_name} datacenter ${label:datacenter}
+ to: sysadmin
+
+ template: consul_node_health_check_status
+ on: consul.node_health_check_status
+ class: Errors
+ type: ServiceMesh
+component: Consul
+ calc: $warning + $critical
+ every: 10s
+ units: status
+ warn: $this != nan AND $this != 0
+ delay: down 5m multiplier 1.5 max 1h
+ info: node health check ${label:check_name} has failed on server ${label:node_name} datacenter ${label:datacenter}
+ to: sysadmin
+
+ template: consul_service_health_check_status
+ on: consul.service_health_check_status
+ class: Errors
+ type: ServiceMesh
+component: Consul
+ calc: $warning + $critical
+ every: 10s
+ units: status
+ warn: $this == 1
+ delay: down 5m multiplier 1.5 max 1h
+ info: service health check ${label:check_name} for service ${label:service_name} has failed on server ${label:node_name} datacenter ${label:datacenter}
+ to: sysadmin
+
+ template: consul_gc_pause_time
+ on: consul.gc_pause_time
+ class: Errors
+ type: ServiceMesh
+component: Consul
+ lookup: sum -1m unaligned
+ every: 10s
+ units: seconds
+ warn: $this > (($status >= $WARNING) ? (1) : (2))
+ crit: $this > (($status >= $WARNING) ? (2) : (5))
+ delay: down 5m multiplier 1.5 max 1h
+ info: time spent in stop-the-world garbage collection pauses on server ${label:node_name} datacenter ${label:datacenter}
+ to: sysadmin
diff --git a/health/health.d/disks.conf b/health/health.d/disks.conf
index 5daff61a..fd207fbc 100644
--- a/health/health.d/disks.conf
+++ b/health/health.d/disks.conf
@@ -23,7 +23,7 @@ component: Disk
warn: $this > (($status >= $WARNING ) ? (80) : (90))
crit: $this > (($status == $CRITICAL) ? (90) : (98))
delay: up 1m down 15m multiplier 1.5 max 1h
- info: disk $family space utilization
+ info: disk ${label:mount_point} space utilization
to: sysadmin
template: disk_inode_usage
@@ -40,7 +40,7 @@ component: Disk
warn: $this > (($status >= $WARNING) ? (80) : (90))
crit: $this > (($status == $CRITICAL) ? (90) : (98))
delay: up 1m down 15m multiplier 1.5 max 1h
- info: disk $family inode utilization
+ info: disk ${label:mount_point} inode utilization
to: sysadmin
@@ -147,7 +147,7 @@ component: Disk
every: 1m
warn: $this > 98 * (($status >= $WARNING) ? (0.7) : (1))
delay: down 15m multiplier 1.2 max 1h
- info: average percentage of time $family disk was busy over the last 10 minutes
+ info: average percentage of time ${label:device} disk was busy over the last 10 minutes
to: silent
@@ -169,5 +169,5 @@ component: Disk
every: 1m
warn: $this > 5000 * (($status >= $WARNING) ? (0.7) : (1))
delay: down 15m multiplier 1.2 max 1h
- info: average backlog size of the $family disk over the last 10 minutes
+ info: average backlog size of the ${label:device} disk over the last 10 minutes
to: silent
diff --git a/health/health.d/dns_query.conf b/health/health.d/dns_query.conf
index b9d6c237..bf9397d8 100644
--- a/health/health.d/dns_query.conf
+++ b/health/health.d/dns_query.conf
@@ -10,5 +10,5 @@ component: DNS
every: 10s
warn: $this != nan && $this != 1
delay: up 30s down 5m multiplier 1.5 max 1h
- info: DNS request type $label:record_type to server $label:server is unsuccessful
+ info: DNS request type ${label:record_type} to server ${label:server} is unsuccessful
to: sysadmin
diff --git a/health/health.d/elasticsearch.conf b/health/health.d/elasticsearch.conf
new file mode 100644
index 00000000..47f8e1eb
--- /dev/null
+++ b/health/health.d/elasticsearch.conf
@@ -0,0 +1,73 @@
+# you can disable an alarm notification by setting the 'to' line to: silent
+
+# 'red' is a threshold, can't lookup the 'red' dimension - using simple pattern is a workaround.
+
+ template: elasticsearch_cluster_health_status_red
+ on: elasticsearch.cluster_health_status
+ class: Errors
+ type: SearchEngine
+component: Elasticsearch
+ lookup: average -5s unaligned of *ed
+ every: 10s
+ units: status
+ warn: $this == 1
+ delay: down 5m multiplier 1.5 max 1h
+ info: cluster health status is red.
+ to: sysadmin
+
+# the idea of '-10m' is to handle yellow status after node restart,
+# (usually) no action is required because Elasticsearch will automatically restore the green status.
+ template: elasticsearch_cluster_health_status_yellow
+ on: elasticsearch.cluster_health_status
+ class: Errors
+ type: SearchEngine
+component: Elasticsearch
+ lookup: average -10m unaligned of yellow
+ every: 1m
+ units: status
+ warn: $this == 1
+ delay: down 5m multiplier 1.5 max 1h
+ info: cluster health status is yellow.
+ to: sysadmin
+
+ template: elasticsearch_node_index_health_red
+ on: elasticsearch.node_index_health
+ class: Errors
+ type: SearchEngine
+component: Elasticsearch
+ lookup: average -5s unaligned of *ed
+ every: 10s
+ units: status
+ warn: $this == 1
+ delay: down 5m multiplier 1.5 max 1h
+ info: node index $label:index health status is red.
+ to: sysadmin
+
+# don't convert 'lookup' value to seconds in 'calc' due to UI showing seconds as hh:mm:ss (0 as now).
+
+ template: elasticsearch_node_indices_search_time_query
+ on: elasticsearch.node_indices_search_time
+ class: Workload
+ type: SearchEngine
+component: Elasticsearch
+ lookup: average -10m unaligned of query
+ every: 10s
+ units: milliseconds
+ warn: $this > (($status >= $WARNING) ? (20 * 1000) : (30 * 1000))
+ delay: down 5m multiplier 1.5 max 1h
+ info: search performance is degraded, queries run slowly.
+ to: sysadmin
+
+ template: elasticsearch_node_indices_search_time_fetch
+ on: elasticsearch.node_indices_search_time
+ class: Workload
+ type: SearchEngine
+component: Elasticsearch
+ lookup: average -10m unaligned of fetch
+ every: 10s
+ units: milliseconds
+ warn: $this > (($status >= $WARNING) ? (3 * 1000) : (5 * 1000))
+ crit: $this > (($status == $CRITICAL) ? (5 * 1000) : (30 * 1000))
+ delay: down 5m multiplier 1.5 max 1h
+ info: search performance is degraded, fetches run slowly.
+ to: sysadmin
diff --git a/health/health.d/fping.conf b/health/health.d/fping.conf
deleted file mode 100644
index bb22419f..00000000
--- a/health/health.d/fping.conf
+++ /dev/null
@@ -1,64 +0,0 @@
-
- template: fping_last_collected_secs
- families: *
- on: fping.latency
- class: Latency
- type: Other
-component: Network
- calc: $now - $last_collected_t
- units: seconds ago
- every: 10s
- warn: $this > (($status >= $WARNING) ? ($update_every) : ( 5 * $update_every))
- crit: $this > (($status == $CRITICAL) ? ($update_every) : (60 * $update_every))
- delay: down 5m multiplier 1.5 max 1h
- info: number of seconds since the last successful data collection
- to: sysadmin
-
- template: fping_host_reachable
- families: *
- on: fping.latency
- class: Errors
- type: Other
-component: Network
- calc: $average != nan
- units: up/down
- every: 10s
- crit: $this == 0
- delay: down 30m multiplier 1.5 max 2h
- info: reachability status of the network host (0: unreachable, 1: reachable)
- to: sysadmin
-
- template: fping_host_latency
- families: *
- on: fping.latency
- class: Latency
- type: Other
-component: Network
- lookup: average -10s unaligned of average
- units: ms
- every: 10s
- green: 500
- red: 1000
- warn: $this > $green OR $max > $red
- crit: $this > $red
- delay: down 30m multiplier 1.5 max 2h
- info: average latency to the network host over the last 10 seconds
- to: sysadmin
-
- template: fping_packet_loss
- families: *
- on: fping.quality
- class: Errors
- type: System
-component: Network
- lookup: average -10m unaligned of returned
- calc: 100 - $this
- green: 1
- red: 10
- units: %
- every: 10s
- warn: $this > $green
- crit: $this > $red
- delay: down 30m multiplier 1.5 max 2h
- info: packet loss ratio to the network host over the last 10 minutes
- to: sysadmin
diff --git a/health/health.d/httpcheck.conf b/health/health.d/httpcheck.conf
index 599c47ac..2008b000 100644
--- a/health/health.d/httpcheck.conf
+++ b/health/health.d/httpcheck.conf
@@ -10,7 +10,7 @@ component: HTTP endpoint
calc: ($this < 75) ? (0) : ($this)
every: 5s
units: up/down
- info: average ratio of successful HTTP requests over the last minute (at least 75%)
+ info: HTTP endpoint ${label:url} liveness status
to: silent
template: httpcheck_web_service_bad_content
@@ -25,8 +25,7 @@ component: HTTP endpoint
warn: $this >= 10 AND $this < 40
crit: $this >= 40
delay: down 5m multiplier 1.5 max 1h
- info: average ratio of HTTP responses with unexpected content over the last 5 minutes
- options: no-clear-notification
+ info: percentage of HTTP responses from ${label:url} with unexpected content in the last 5 minutes
to: webmaster
template: httpcheck_web_service_bad_status
@@ -41,8 +40,7 @@ component: HTTP endpoint
warn: $this >= 10 AND $this < 40
crit: $this >= 40
delay: down 5m multiplier 1.5 max 1h
- info: average ratio of HTTP responses with unexpected status over the last 5 minutes
- options: no-clear-notification
+ info: percentage of HTTP responses from ${label:url} with unexpected status in the last 5 minutes
to: webmaster
template: httpcheck_web_service_timeouts
@@ -54,9 +52,13 @@ component: HTTP endpoint
lookup: average -5m unaligned percentage of timeout
every: 10s
units: %
- info: average ratio of HTTP request timeouts over the last 5 minutes
+ warn: $this >= 10 AND $this < 40
+ crit: $this >= 40
+ delay: down 5m multiplier 1.5 max 1h
+ info: percentage of timed-out HTTP requests to ${label:url} in the last 5 minutes
+ to: webmaster
- template: httpcheck_no_web_service_connections
+ template: httpcheck_web_service_no_connection
families: *
on: httpcheck.status
class: Errors
@@ -65,48 +67,8 @@ component: HTTP endpoint
lookup: average -5m unaligned percentage of no_connection
every: 10s
units: %
- info: average ratio of failed requests during the last 5 minutes
-
-# combined timeout & no connection alarm
- template: httpcheck_web_service_unreachable
- families: *
- on: httpcheck.status
- class: Errors
- type: Web Server
-component: HTTP endpoint
- calc: ($httpcheck_no_web_service_connections >= $httpcheck_web_service_timeouts) ? ($httpcheck_no_web_service_connections) : ($httpcheck_web_service_timeouts)
- units: %
- every: 10s
- warn: ($httpcheck_no_web_service_connections >= 10 OR $httpcheck_web_service_timeouts >= 10) AND ($httpcheck_no_web_service_connections < 40 OR $httpcheck_web_service_timeouts < 40)
- crit: $httpcheck_no_web_service_connections >= 40 OR $httpcheck_web_service_timeouts >= 40
- delay: down 5m multiplier 1.5 max 1h
- info: ratio of failed requests either due to timeouts or no connection over the last 5 minutes
- options: no-clear-notification
- to: webmaster
-
- template: httpcheck_1h_web_service_response_time
- families: *
- on: httpcheck.responsetime
- class: Latency
- type: Other
-component: HTTP endpoint
- lookup: average -1h unaligned of time
- every: 30s
- units: ms
- info: average HTTP response time over the last hour
-
- template: httpcheck_web_service_slow
- families: *
- on: httpcheck.responsetime
- class: Latency
- type: Web Server
-component: HTTP endpoint
- lookup: average -3m unaligned of time
- units: ms
- every: 10s
- warn: ($this > ($httpcheck_1h_web_service_response_time * 2) )
- crit: ($this > ($httpcheck_1h_web_service_response_time * 3) )
+ warn: $this >= 10 AND $this < 40
+ crit: $this >= 40
delay: down 5m multiplier 1.5 max 1h
- info: average HTTP response time over the last 3 minutes, compared to the average over the last hour
- options: no-clear-notification
+ info: percentage of failed HTTP requests to ${label:url} in the last 5 minutes
to: webmaster
diff --git a/health/health.d/kubelet.conf b/health/health.d/kubelet.conf
index c2778cc5..428b6ee9 100644
--- a/health/health.d/kubelet.conf
+++ b/health/health.d/kubelet.conf
@@ -9,7 +9,7 @@
class: Errors
type: Kubernetes
component: Kubelet
- calc: $kubelet_node_config_error
+ calc: $experiencing_error
units: bool
every: 10s
warn: $this == 1
@@ -20,12 +20,12 @@ component: Kubelet
# Failed Token() requests to the alternate token source
template: kubelet_token_requests
- lookup: sum -10s of token_fail_count
on: k8s_kubelet.kubelet_token_requests
class: Errors
type: Kubernetes
component: Kubelet
- units: failed requests
+ lookup: sum -10s of failed
+ units: requests
every: 10s
warn: $this > 0
delay: down 1m multiplier 1.5 max 2h
@@ -35,11 +35,11 @@ component: Kubelet
# Docker and runtime operation errors
template: kubelet_operations_error
- lookup: sum -1m
on: k8s_kubelet.kubelet_operations_errors
class: Errors
type: Kubernetes
component: Kubelet
+ lookup: sum -1m
units: errors
every: 10s
warn: $this > (($status >= $WARNING) ? (0) : (20))
@@ -67,7 +67,7 @@ component: Kubelet
class: Latency
type: Kubernetes
component: Kubelet
- lookup: average -1m unaligned of kubelet_pleg_relist_latency_05
+ lookup: average -1m unaligned of 0.5
units: microseconds
every: 10s
info: average Pod Lifecycle Event Generator relisting latency over the last minute (quantile 0.5)
@@ -77,7 +77,7 @@ component: Kubelet
class: Latency
type: Kubernetes
component: Kubelet
- lookup: average -10s unaligned of kubelet_pleg_relist_latency_05
+ lookup: average -10s unaligned of 0.5
calc: $this * 100 / (($kubelet_1m_pleg_relist_latency_quantile_05 < 1000)?(1000):($kubelet_1m_pleg_relist_latency_quantile_05))
every: 10s
units: %
@@ -95,7 +95,7 @@ component: Kubelet
class: Latency
type: Kubernetes
component: Kubelet
- lookup: average -1m unaligned of kubelet_pleg_relist_latency_09
+ lookup: average -1m unaligned of 0.9
units: microseconds
every: 10s
info: average Pod Lifecycle Event Generator relisting latency over the last minute (quantile 0.9)
@@ -105,7 +105,7 @@ component: Kubelet
class: Latency
type: Kubernetes
component: Kubelet
- lookup: average -10s unaligned of kubelet_pleg_relist_latency_09
+ lookup: average -10s unaligned of 0.9
calc: $this * 100 / (($kubelet_1m_pleg_relist_latency_quantile_09 < 1000)?(1000):($kubelet_1m_pleg_relist_latency_quantile_09))
every: 10s
units: %
@@ -123,7 +123,7 @@ component: Kubelet
class: Latency
type: Kubernetes
component: Kubelet
- lookup: average -1m unaligned of kubelet_pleg_relist_latency_099
+ lookup: average -1m unaligned of 0.99
units: microseconds
every: 10s
info: average Pod Lifecycle Event Generator relisting latency over the last minute (quantile 0.99)
@@ -133,7 +133,7 @@ component: Kubelet
class: Latency
type: Kubernetes
component: Kubelet
- lookup: average -10s unaligned of kubelet_pleg_relist_latency_099
+ lookup: average -10s unaligned of 0.99
calc: $this * 100 / (($kubelet_1m_pleg_relist_latency_quantile_099 < 1000)?(1000):($kubelet_1m_pleg_relist_latency_quantile_099))
every: 10s
units: %
diff --git a/health/health.d/load.conf b/health/health.d/load.conf
index 0bd872f8..75989c57 100644
--- a/health/health.d/load.conf
+++ b/health/health.d/load.conf
@@ -11,7 +11,7 @@
component: Load
os: linux
hosts: *
- calc: ($active_processors == nan or $active_processors == inf or $active_processors < 2) ? ( 2 ) : ( $active_processors )
+ calc: ($active_processors == nan or $active_processors == 0) ? (nan) : ( ($active_processors < 2) ? ( 2 ) : ( $active_processors ) )
units: cpus
every: 1m
info: number of active CPU cores in the system
@@ -28,6 +28,7 @@ component: Load
os: linux
hosts: *
lookup: max -1m unaligned of load15
+ calc: ($load_cpu_number == nan) ? (nan) : ($this)
units: load
every: 1m
warn: ($this * 100 / $load_cpu_number) > (($status >= $WARNING) ? 175 : 200)
@@ -43,6 +44,7 @@ component: Load
os: linux
hosts: *
lookup: max -1m unaligned of load5
+ calc: ($load_cpu_number == nan) ? (nan) : ($this)
units: load
every: 1m
warn: ($this * 100 / $load_cpu_number) > (($status >= $WARNING) ? 350 : 400)
@@ -58,6 +60,7 @@ component: Load
os: linux
hosts: *
lookup: max -1m unaligned of load1
+ calc: ($load_cpu_number == nan) ? (nan) : ($this)
units: load
every: 1m
warn: ($this * 100 / $load_cpu_number) > (($status >= $WARNING) ? 700 : 800)
diff --git a/health/health.d/mdstat.conf b/health/health.d/mdstat.conf
index cedaa000..ed980a26 100644
--- a/health/health.d/mdstat.conf
+++ b/health/health.d/mdstat.conf
@@ -20,7 +20,7 @@ component: RAID
every: 10s
calc: $down
crit: $this > 0
- info: number of devices in the down state for the $family array. \
+ info: number of devices in the down state for the ${label:device} ${label:raid_level} array. \
Any number > 0 indicates that the array is degraded.
to: sysadmin
@@ -35,7 +35,7 @@ component: RAID
every: 60s
warn: $this > 1024
delay: up 30m
- info: number of unsynchronized blocks for the $family array
+ info: number of unsynchronized blocks for the ${label:device} ${label:raid_level} array
to: sysadmin
template: mdstat_nonredundant_last_collected
diff --git a/health/health.d/net.conf b/health/health.d/net.conf
index 9d5b3b8d..a0723f30 100644
--- a/health/health.d/net.conf
+++ b/health/health.d/net.conf
@@ -15,7 +15,7 @@ component: Network
calc: ( $nic_speed_max > 0 ) ? ( $nic_speed_max) : ( nan )
units: Mbit
every: 10s
- info: network interface $family current speed
+ info: network interface ${label:device} current speed
template: 1m_received_traffic_overflow
on: net.net
@@ -31,7 +31,7 @@ component: Network
every: 10s
warn: $this > (($status >= $WARNING) ? (85) : (90))
delay: up 1m down 1m multiplier 1.5 max 1h
- info: average inbound utilization for the network interface $family over the last minute
+ info: average inbound utilization for the network interface ${label:device} over the last minute
to: sysadmin
template: 1m_sent_traffic_overflow
@@ -48,7 +48,7 @@ component: Network
every: 10s
warn: $this > (($status >= $WARNING) ? (85) : (90))
delay: up 1m down 1m multiplier 1.5 max 1h
- info: average outbound utilization for the network interface $family over the last minute
+ info: average outbound utilization for the network interface ${label:device} over the last minute
to: sysadmin
# -----------------------------------------------------------------------------
@@ -72,7 +72,7 @@ component: Network
lookup: sum -10m unaligned absolute of inbound
units: packets
every: 1m
- info: number of inbound dropped packets for the network interface $family in the last 10 minutes
+ info: number of inbound dropped packets for the network interface ${label:device} in the last 10 minutes
template: outbound_packets_dropped
on: net.drops
@@ -85,7 +85,7 @@ component: Network
lookup: sum -10m unaligned absolute of outbound
units: packets
every: 1m
- info: number of outbound dropped packets for the network interface $family in the last 10 minutes
+ info: number of outbound dropped packets for the network interface ${label:device} in the last 10 minutes
template: inbound_packets_dropped_ratio
on: net.packets
@@ -101,7 +101,7 @@ component: Network
every: 1m
warn: $this >= 2
delay: up 1m down 1h multiplier 1.5 max 2h
- info: ratio of inbound dropped packets for the network interface $family over the last 10 minutes
+ info: ratio of inbound dropped packets for the network interface ${label:device} over the last 10 minutes
to: sysadmin
template: outbound_packets_dropped_ratio
@@ -118,7 +118,7 @@ component: Network
every: 1m
warn: $this >= 2
delay: up 1m down 1h multiplier 1.5 max 2h
- info: ratio of outbound dropped packets for the network interface $family over the last 10 minutes
+ info: ratio of outbound dropped packets for the network interface ${label:device} over the last 10 minutes
to: sysadmin
template: wifi_inbound_packets_dropped_ratio
@@ -135,7 +135,7 @@ component: Network
every: 1m
warn: $this >= 10
delay: up 1m down 1h multiplier 1.5 max 2h
- info: ratio of inbound dropped packets for the network interface $family over the last 10 minutes
+ info: ratio of inbound dropped packets for the network interface ${label:device} over the last 10 minutes
to: sysadmin
template: wifi_outbound_packets_dropped_ratio
@@ -152,7 +152,7 @@ component: Network
every: 1m
warn: $this >= 10
delay: up 1m down 1h multiplier 1.5 max 2h
- info: ratio of outbound dropped packets for the network interface $family over the last 10 minutes
+ info: ratio of outbound dropped packets for the network interface ${label:device} over the last 10 minutes
to: sysadmin
# -----------------------------------------------------------------------------
@@ -171,7 +171,7 @@ component: Network
every: 1m
warn: $this >= 5
delay: down 1h multiplier 1.5 max 2h
- info: number of inbound errors for the network interface $family in the last 10 minutes
+ info: number of inbound errors for the network interface ${label:device} in the last 10 minutes
to: sysadmin
template: interface_outbound_errors
@@ -187,7 +187,7 @@ component: Network
every: 1m
warn: $this >= 5
delay: down 1h multiplier 1.5 max 2h
- info: number of outbound errors for the network interface $family in the last 10 minutes
+ info: number of outbound errors for the network interface ${label:device} in the last 10 minutes
to: sysadmin
# -----------------------------------------------------------------------------
@@ -211,7 +211,7 @@ component: Network
every: 1m
warn: $this > 0
delay: down 1h multiplier 1.5 max 2h
- info: number of FIFO errors for the network interface $family in the last 10 minutes
+ info: number of FIFO errors for the network interface ${label:device} in the last 10 minutes
to: sysadmin
# -----------------------------------------------------------------------------
@@ -234,7 +234,7 @@ component: Network
lookup: average -1m unaligned of received
units: packets
every: 10s
- info: average number of packets received by the network interface $family over the last minute
+ info: average number of packets received by the network interface ${label:device} over the last minute
template: 10s_received_packets_storm
on: net.packets
@@ -251,6 +251,6 @@ component: Network
warn: $this > (($status >= $WARNING)?(200):(5000))
crit: $this > (($status == $CRITICAL)?(5000):(6000))
options: no-clear-notification
- info: ratio of average number of received packets for the network interface $family over the last 10 seconds, \
+ info: ratio of average number of received packets for the network interface ${label:device} over the last 10 seconds, \
compared to the rate over the last minute
to: sysadmin
diff --git a/health/health.d/nvme.conf b/health/health.d/nvme.conf
index 5f729d52..b7c0e6fd 100644
--- a/health/health.d/nvme.conf
+++ b/health/health.d/nvme.conf
@@ -11,5 +11,5 @@ component: Disk
every: 10s
crit: $this != nan AND $this != 0
delay: down 5m multiplier 1.5 max 2h
- info: NVMe device $label:device has critical warnings
+ info: NVMe device ${label:device} has critical warnings
to: sysadmin
diff --git a/health/health.d/ping.conf b/health/health.d/ping.conf
index cbe7c30c..fa8213ad 100644
--- a/health/health.d/ping.conf
+++ b/health/health.d/ping.conf
@@ -12,7 +12,7 @@ component: Network
every: 10s
crit: $this == 0
delay: down 30m multiplier 1.5 max 2h
- info: network host $label:host reachability status
+ info: network host ${label:host} reachability status
to: sysadmin
template: ping_packet_loss
@@ -29,7 +29,7 @@ component: Network
warn: $this > $green
crit: $this > $red
delay: down 30m multiplier 1.5 max 2h
- info: packet loss percentage to the network host $label:host over the last 10 minutes
+ info: packet loss percentage to the network host ${label:host} over the last 10 minutes
to: sysadmin
template: ping_host_latency
@@ -46,5 +46,5 @@ component: Network
warn: $this > $green OR $max > $red
crit: $this > $red
delay: down 30m multiplier 1.5 max 2h
- info: average latency to the network host $label:host over the last 10 seconds
+ info: average latency to the network host ${label:host} over the last 10 seconds
to: sysadmin
diff --git a/health/health.d/portcheck.conf b/health/health.d/portcheck.conf
index 8cbd7729..e8908404 100644
--- a/health/health.d/portcheck.conf
+++ b/health/health.d/portcheck.conf
@@ -10,7 +10,7 @@ component: TCP endpoint
calc: ($this < 75) ? (0) : ($this)
every: 5s
units: up/down
- info: average ratio of successful connections over the last minute (at least 75%)
+ info: TCP host ${label:host} port ${label:port} liveness status
to: silent
template: portcheck_connection_timeouts
@@ -25,7 +25,7 @@ component: TCP endpoint
warn: $this >= 10 AND $this < 40
crit: $this >= 40
delay: down 5m multiplier 1.5 max 1h
- info: average ratio of timeouts over the last 5 minutes
+ info: percentage of timed-out TCP connections to host ${label:host} port ${label:port} in the last 5 minutes
to: sysadmin
template: portcheck_connection_fails
@@ -40,5 +40,5 @@ component: TCP endpoint
warn: $this >= 10 AND $this < 40
crit: $this >= 40
delay: down 5m multiplier 1.5 max 1h
- info: average ratio of failed connections over the last 5 minutes
+ info: percentage of failed TCP connections to host ${label:host} port ${label:port} in the last 5 minutes
to: sysadmin
diff --git a/health/health.d/postgres.conf b/health/health.d/postgres.conf
index 66d034cf..67b25673 100644
--- a/health/health.d/postgres.conf
+++ b/health/health.d/postgres.conf
@@ -58,7 +58,7 @@ component: PostgreSQL
warn: $this < (($status >= $WARNING) ? (70) : (60))
crit: $this < (($status == $CRITICAL) ? (60) : (50))
delay: down 15m multiplier 1.5 max 1h
- info: average cache hit ratio in db $label:database over the last minute
+ info: average cache hit ratio in db ${label:database} over the last minute
to: dba
template: postgres_db_transactions_rollback_ratio
@@ -72,7 +72,7 @@ component: PostgreSQL
every: 1m
warn: $this > (($status >= $WARNING) ? (0) : (2))
delay: down 15m multiplier 1.5 max 1h
- info: average aborted transactions percentage in db $label:database over the last five minutes
+ info: average aborted transactions percentage in db ${label:database} over the last five minutes
to: dba
template: postgres_db_deadlocks_rate
@@ -86,7 +86,7 @@ component: PostgreSQL
every: 1m
warn: $this > (($status >= $WARNING) ? (0) : (10))
delay: down 15m multiplier 1.5 max 1h
- info: number of deadlocks detected in db $label:database in the last minute
+ info: number of deadlocks detected in db ${label:database} in the last minute
to: dba
# Table alarms
@@ -104,7 +104,7 @@ component: PostgreSQL
warn: $this < (($status >= $WARNING) ? (70) : (60))
crit: $this < (($status == $CRITICAL) ? (60) : (50))
delay: down 15m multiplier 1.5 max 1h
- info: average cache hit ratio in db $label:database table $label:table over the last minute
+ info: average cache hit ratio in db ${label:database} table ${label:table} over the last minute
to: dba
template: postgres_table_index_cache_io_ratio
@@ -120,7 +120,7 @@ component: PostgreSQL
warn: $this < (($status >= $WARNING) ? (70) : (60))
crit: $this < (($status == $CRITICAL) ? (60) : (50))
delay: down 15m multiplier 1.5 max 1h
- info: average index cache hit ratio in db $label:database table $label:table over the last minute
+ info: average index cache hit ratio in db ${label:database} table ${label:table} over the last minute
to: dba
template: postgres_table_toast_cache_io_ratio
@@ -136,7 +136,7 @@ component: PostgreSQL
warn: $this < (($status >= $WARNING) ? (70) : (60))
crit: $this < (($status == $CRITICAL) ? (60) : (50))
delay: down 15m multiplier 1.5 max 1h
- info: average TOAST hit ratio in db $label:database table $label:table over the last minute
+ info: average TOAST hit ratio in db ${label:database} table ${label:table} over the last minute
to: dba
template: postgres_table_toast_index_cache_io_ratio
@@ -152,7 +152,7 @@ component: PostgreSQL
warn: $this < (($status >= $WARNING) ? (70) : (60))
crit: $this < (($status == $CRITICAL) ? (60) : (50))
delay: down 15m multiplier 1.5 max 1h
- info: average index TOAST hit ratio in db $label:database table $label:table over the last minute
+ info: average index TOAST hit ratio in db ${label:database} table ${label:table} over the last minute
to: dba
template: postgres_table_bloat_size_perc
@@ -161,13 +161,13 @@ component: PostgreSQL
type: Database
component: PostgreSQL
hosts: *
- calc: $bloat
+ calc: ($table_size > (1024 * 1024 * 100)) ? ($bloat) : (0)
units: %
every: 1m
warn: $this > (($status >= $WARNING) ? (60) : (70))
crit: $this > (($status == $CRITICAL) ? (70) : (80))
delay: down 15m multiplier 1.5 max 1h
- info: bloat size percentage in db $label:database table $label:table
+ info: bloat size percentage in db ${label:database} table ${label:table}
to: dba
template: postgres_table_last_autovacuum_time
@@ -180,7 +180,7 @@ component: PostgreSQL
units: seconds
every: 1m
warn: $this != nan AND $this > (60 * 60 * 24 * 7)
- info: time elapsed since db $label:database table $label:table was vacuumed by the autovacuum daemon
+ info: time elapsed since db ${label:database} table ${label:table} was vacuumed by the autovacuum daemon
to: dba
template: postgres_table_last_autoanalyze_time
@@ -193,7 +193,7 @@ component: PostgreSQL
units: seconds
every: 1m
warn: $this != nan AND $this > (60 * 60 * 24 * 7)
- info: time elapsed since db $label:database table $label:table was analyzed by the autovacuum daemon
+ info: time elapsed since db ${label:database} table ${label:table} was analyzed by the autovacuum daemon
to: dba
# Index alarms
@@ -204,11 +204,11 @@ component: PostgreSQL
type: Database
component: PostgreSQL
hosts: *
- calc: $bloat
+ calc: ($index_size > (1024 * 1024 * 10)) ? ($bloat) : (0)
units: %
every: 1m
warn: $this > (($status >= $WARNING) ? (60) : (70))
crit: $this > (($status == $CRITICAL) ? (70) : (80))
delay: down 15m multiplier 1.5 max 1h
- info: bloat size percentage in db $label:database table $label:table index $label:index
+ info: bloat size percentage in db ${label:database} table ${label:table} index ${label:index}
to: dba
diff --git a/health/health.d/zfs.conf b/health/health.d/zfs.conf
index 785838d4..7f8ea279 100644
--- a/health/health.d/zfs.conf
+++ b/health/health.d/zfs.conf
@@ -24,7 +24,7 @@ component: File system
every: 10s
warn: $this > 0
delay: down 1m multiplier 1.5 max 1h
- info: ZFS pool $family state is degraded
+ info: ZFS pool ${label:pool} state is degraded
to: sysadmin
template: zfs_pool_state_crit
@@ -37,5 +37,5 @@ component: File system
every: 10s
crit: $this > 0
delay: down 1m multiplier 1.5 max 1h
- info: ZFS pool $family state is faulted or unavail
+ info: ZFS pool ${label:pool} state is faulted or unavail
to: sysadmin
diff --git a/health/health.h b/health/health.h
index 15d8326e..50c3e345 100644
--- a/health/health.h
+++ b/health/health.h
@@ -31,6 +31,7 @@ extern unsigned int default_health_enabled;
#define HEALTH_SILENCERS_MAX_FILE_LEN 10000
extern char *silencers_filename;
+extern SIMPLE_PATTERN *conf_enabled_alarms;
void health_init(void);
@@ -48,9 +49,6 @@ int health_alarm_log_open(RRDHOST *host);
void health_alarm_log_save(RRDHOST *host, ALARM_ENTRY *ae);
void health_alarm_log_load(RRDHOST *host);
-void health_thread_spawn(RRDHOST *host);
-void health_thread_stop(RRDHOST *host);
-
ALARM_ENTRY* health_create_alarm_entry(
RRDHOST *host,
uint32_t alarm_id,
@@ -79,11 +77,6 @@ ALARM_ENTRY* health_create_alarm_entry(
void health_alarm_log_add_entry(RRDHOST *host, ALARM_ENTRY *ae);
-struct health_state {
- RRDHOST *host;
- netdata_thread_t thread;
-};
-
void health_readdir(RRDHOST *host, const char *user_path, const char *stock_path, const char *subpath);
char *health_user_config_dir(void);
char *health_stock_config_dir(void);
diff --git a/health/health_config.c b/health/health_config.c
index f9decfad..55d5e10e 100644
--- a/health/health_config.c
+++ b/health/health_config.c
@@ -553,33 +553,37 @@ static int health_readfile(const char *filename, void *data) {
rt = NULL;
}
- rc = callocz(1, sizeof(RRDCALC));
- rc->next_event_id = 1;
-
- {
- char *tmp = strdupz(value);
- if(rrdvar_fix_name(tmp))
- error("Health configuration renamed alarm '%s' to '%s'", value, tmp);
-
- rc->name = string_strdupz(tmp);
- freez(tmp);
- }
-
- rc->source = health_source_file(line, filename);
- rc->green = NAN;
- rc->red = NAN;
- rc->value = NAN;
- rc->old_value = NAN;
- rc->delay_multiplier = 1.0;
- rc->old_status = RRDCALC_STATUS_UNINITIALIZED;
- rc->warn_repeat_every = host->health_default_warn_repeat_every;
- rc->crit_repeat_every = host->health_default_crit_repeat_every;
- if (alert_cfg)
- alert_config_free(alert_cfg);
- alert_cfg = callocz(1, sizeof(struct alert_config));
-
- alert_cfg->alarm = string_dup(rc->name);
- ignore_this = 0;
+ if (simple_pattern_matches(conf_enabled_alarms, value)) {
+ rc = callocz(1, sizeof(RRDCALC));
+ rc->next_event_id = 1;
+
+ {
+ char *tmp = strdupz(value);
+ if(rrdvar_fix_name(tmp))
+ error("Health configuration renamed alarm '%s' to '%s'", value, tmp);
+
+ rc->name = string_strdupz(tmp);
+ freez(tmp);
+ }
+
+ rc->source = health_source_file(line, filename);
+ rc->green = NAN;
+ rc->red = NAN;
+ rc->value = NAN;
+ rc->old_value = NAN;
+ rc->delay_multiplier = 1.0;
+ rc->old_status = RRDCALC_STATUS_UNINITIALIZED;
+ rc->warn_repeat_every = host->health.health_default_warn_repeat_every;
+ rc->crit_repeat_every = host->health.health_default_crit_repeat_every;
+ if (alert_cfg)
+ alert_config_free(alert_cfg);
+ alert_cfg = callocz(1, sizeof(struct alert_config));
+
+ alert_cfg->alarm = string_dup(rc->name);
+ ignore_this = 0;
+ } else {
+ rc = NULL;
+ }
}
else if(hash == hash_template && !strcasecmp(key, HEALTH_TEMPLATE_KEY)) {
if(rc) {
@@ -599,29 +603,33 @@ static int health_readfile(const char *filename, void *data) {
rrdcalctemplate_add_from_config(host, rt);
}
- rt = callocz(1, sizeof(RRDCALCTEMPLATE));
+ if (simple_pattern_matches(conf_enabled_alarms, value)) {
+ rt = callocz(1, sizeof(RRDCALCTEMPLATE));
- {
- char *tmp = strdupz(value);
- if(rrdvar_fix_name(tmp))
- error("Health configuration renamed template '%s' to '%s'", value, tmp);
-
- rt->name = string_strdupz(tmp);
- freez(tmp);
- }
+ {
+ char *tmp = strdupz(value);
+ if(rrdvar_fix_name(tmp))
+ error("Health configuration renamed template '%s' to '%s'", value, tmp);
- rt->source = health_source_file(line, filename);
- rt->green = NAN;
- rt->red = NAN;
- rt->delay_multiplier = (float)1.0;
- rt->warn_repeat_every = host->health_default_warn_repeat_every;
- rt->crit_repeat_every = host->health_default_crit_repeat_every;
- if (alert_cfg)
- alert_config_free(alert_cfg);
- alert_cfg = callocz(1, sizeof(struct alert_config));
+ rt->name = string_strdupz(tmp);
+ freez(tmp);
+ }
- alert_cfg->template_key = string_dup(rt->name);
- ignore_this = 0;
+ rt->source = health_source_file(line, filename);
+ rt->green = NAN;
+ rt->red = NAN;
+ rt->delay_multiplier = (float)1.0;
+ rt->warn_repeat_every = host->health.health_default_warn_repeat_every;
+ rt->crit_repeat_every = host->health.health_default_crit_repeat_every;
+ if (alert_cfg)
+ alert_config_free(alert_cfg);
+ alert_cfg = callocz(1, sizeof(struct alert_config));
+
+ alert_cfg->template_key = string_dup(rt->name);
+ ignore_this = 0;
+ } else {
+ rt = NULL;
+ }
}
else if(hash == hash_os && !strcasecmp(key, HEALTH_OS_KEY)) {
char *os_match = value;
@@ -1163,7 +1171,8 @@ void sql_refresh_hashes(void)
}
void health_readdir(RRDHOST *host, const char *user_path, const char *stock_path, const char *subpath) {
- if(unlikely(!host->health_enabled) && !rrdhost_flag_check(host, RRDHOST_FLAG_INITIALIZED_HEALTH)) {
+ if(unlikely((!host->health.health_enabled) && !rrdhost_flag_check(host, RRDHOST_FLAG_INITIALIZED_HEALTH)) ||
+ !service_running(SERVICE_HEALTH)) {
debug(D_HEALTH, "CONFIG health is not enabled for host '%s'", rrdhost_hostname(host));
return;
}
diff --git a/health/health_json.c b/health/health_json.c
index 2dd59fd4..8cabaa0b 100644
--- a/health/health_json.c
+++ b/health/health_json.c
@@ -75,8 +75,8 @@ void health_alarm_entry2json_nolock(BUFFER *wb, ALARM_ENTRY *ae, RRDHOST *host)
, (ae->flags & HEALTH_ENTRY_FLAG_UPDATED)?"true":"false"
, (unsigned long)ae->exec_run_timestamp
, (ae->flags & HEALTH_ENTRY_FLAG_EXEC_FAILED)?"true":"false"
- , ae->exec?ae_exec(ae):string2str(host->health_default_exec)
- , ae->recipient?ae_recipient(ae):string2str(host->health_default_recipient)
+ , ae->exec?ae_exec(ae):string2str(host->health.health_default_exec)
+ , ae->recipient?ae_recipient(ae):string2str(host->health.health_default_recipient)
, ae->exec_code
, ae_source(ae)
, edit_command
@@ -219,8 +219,8 @@ static inline void health_rrdcalc2json_nolock(RRDHOST *host, BUFFER *wb, RRDCALC
, (rc->rrdset)?"true":"false"
, (rc->run_flags & RRDCALC_FLAG_DISABLED)?"true":"false"
, (rc->run_flags & RRDCALC_FLAG_SILENCED)?"true":"false"
- , rc->exec?rrdcalc_exec(rc):string2str(host->health_default_exec)
- , rc->recipient?rrdcalc_recipient(rc):string2str(host->health_default_recipient)
+ , rc->exec?rrdcalc_exec(rc):string2str(host->health.health_default_exec)
+ , rc->recipient?rrdcalc_recipient(rc):string2str(host->health.health_default_recipient)
, rrdcalc_source(rc)
, rrdcalc_units(rc)
, rrdcalc_info(rc)
@@ -372,7 +372,7 @@ void health_alarms2json(RRDHOST *host, BUFFER *wb, int all) {
"\n\t\"alarms\": {\n",
rrdhost_hostname(host),
(host->health_log.next_log_id > 0)?(host->health_log.next_log_id - 1):0,
- host->health_enabled?"true":"false",
+ host->health.health_enabled?"true":"false",
(unsigned long)now_realtime_sec());
health_alarms2json_fill_alarms(host, wb, all, health_rrdcalc2json_nolock);
diff --git a/health/health_log.c b/health/health_log.c
index 8105e01a..d3417493 100644
--- a/health/health_log.c
+++ b/health/health_log.c
@@ -3,149 +3,10 @@
#include "health.h"
// ----------------------------------------------------------------------------
-// health alarm log load/save
-// no need for locking - only one thread is reading / writing the alarms log
-
-inline int health_alarm_log_open(RRDHOST *host) {
- if(host->health_log_fp)
- fclose(host->health_log_fp);
-
- host->health_log_fp = fopen(host->health_log_filename, "a");
-
- if(host->health_log_fp) {
- if (setvbuf(host->health_log_fp, NULL, _IOLBF, 0) != 0)
- error("HEALTH [%s]: cannot set line buffering on health log file '%s'.", rrdhost_hostname(host), host->health_log_filename);
- return 0;
- }
-
- error("HEALTH [%s]: cannot open health log file '%s'. Health data will be lost in case of netdata or server crash.", rrdhost_hostname(host), host->health_log_filename);
- return -1;
-}
-
-static inline void health_alarm_log_close(RRDHOST *host) {
- if(host->health_log_fp) {
- fclose(host->health_log_fp);
- host->health_log_fp = NULL;
- }
-}
-
-static inline void health_log_rotate(RRDHOST *host) {
- static size_t rotate_every = 0;
-
- if(unlikely(rotate_every == 0)) {
- rotate_every = (size_t)config_get_number(CONFIG_SECTION_HEALTH, "rotate log every lines", 2000);
- if(rotate_every < 100) rotate_every = 100;
- }
-
- if(unlikely(host->health_log_entries_written > rotate_every)) {
- if(unlikely(host->health_log_fp)) {
- health_alarm_log_close(host);
-
- char old_filename[FILENAME_MAX + 1];
- snprintfz(old_filename, FILENAME_MAX, "%s.old", host->health_log_filename);
-
- if(unlink(old_filename) == -1 && errno != ENOENT)
- error("HEALTH [%s]: cannot remove old alarms log file '%s'", rrdhost_hostname(host), old_filename);
-
- if(link(host->health_log_filename, old_filename) == -1 && errno != ENOENT)
- error("HEALTH [%s]: cannot move file '%s' to '%s'.", rrdhost_hostname(host), host->health_log_filename, old_filename);
-
- if(unlink(host->health_log_filename) == -1 && errno != ENOENT)
- error("HEALTH [%s]: cannot remove old alarms log file '%s'", rrdhost_hostname(host), host->health_log_filename);
-
- // open it with truncate
- host->health_log_fp = fopen(host->health_log_filename, "w");
-
- if(host->health_log_fp)
- fclose(host->health_log_fp);
- else
- error("HEALTH [%s]: cannot truncate health log '%s'", rrdhost_hostname(host), host->health_log_filename);
-
- host->health_log_fp = NULL;
-
- host->health_log_entries_written = 0;
- health_alarm_log_open(host);
- }
- }
-}
-
-inline void health_label_log_save(RRDHOST *host) {
- health_log_rotate(host);
-
- if(unlikely(host->health_log_fp)) {
- BUFFER *wb = buffer_create(1024);
-
- rrdlabels_to_buffer(localhost->rrdlabels, wb, "", "=", "", "\t ", NULL, NULL, NULL, NULL);
- char *write = (char *) buffer_tostring(wb);
-
- if (unlikely(fprintf(host->health_log_fp, "L\t%s", write) < 0))
- error("HEALTH [%s]: failed to save alarm log entry to '%s'. Health data may be lost in case of abnormal restart.",
- rrdhost_hostname(host), host->health_log_filename);
- else
- host->health_log_entries_written++;
-
- buffer_free(wb);
- }
-}
inline void health_alarm_log_save(RRDHOST *host, ALARM_ENTRY *ae) {
- health_log_rotate(host);
- if(unlikely(host->health_log_fp)) {
- if(unlikely(fprintf(host->health_log_fp
- , "%c\t%s"
- "\t%08x\t%08x\t%08x\t%08x\t%08x"
- "\t%08x\t%08x\t%08x"
- "\t%08x\t%08x\t%08x"
- "\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s"
- "\t%d\t%d\t%d\t%d"
- "\t" NETDATA_DOUBLE_FORMAT_AUTO "\t" NETDATA_DOUBLE_FORMAT_AUTO
- "\t%016"PRIx64""
- "\t%s\t%s\t%s"
- "\n"
- , (ae->flags & HEALTH_ENTRY_FLAG_SAVED)?'U':'A'
- , rrdhost_hostname(host)
-
- , ae->unique_id
- , ae->alarm_id
- , ae->alarm_event_id
- , ae->updated_by_id
- , ae->updates_id
-
- , (uint32_t)ae->when
- , (uint32_t)ae->duration
- , (uint32_t)ae->non_clear_duration
- , (uint32_t)ae->flags
- , (uint32_t)ae->exec_run_timestamp
- , (uint32_t)ae->delay_up_to_timestamp
-
- , ae_name(ae)
- , ae_chart_name(ae)
- , ae_family(ae)
- , ae_exec(ae)
- , ae_recipient(ae)
- , ae_source(ae)
- , ae_units(ae)
- , ae_info(ae)
- , ae->exec_code
- , ae->new_status
- , ae->old_status
- , ae->delay
-
- , ae->new_value
- , ae->old_value
- , (uint64_t)ae->last_repeat
- , (ae->classification)?ae_classification(ae):"Unknown"
- , (ae->component)?ae_component(ae):"Unknown"
- , (ae->type)?ae_type(ae):"Unknown"
- ) < 0))
- error("HEALTH [%s]: failed to save alarm log entry to '%s'. Health data may be lost in case of abnormal restart.", rrdhost_hostname(host), host->health_log_filename);
- else {
- ae->flags |= HEALTH_ENTRY_FLAG_SAVED;
- host->health_log_entries_written++;
- }
- }else
- sql_health_alarm_log_save(host, ae);
+ sql_health_alarm_log_save(host, ae);
#ifdef ENABLE_ACLK
if (netdata_cloud_setting) {
@@ -154,291 +15,6 @@ inline void health_alarm_log_save(RRDHOST *host, ALARM_ENTRY *ae) {
#endif
}
-static uint32_t is_valid_alarm_id(RRDHOST *host, const char *chart, const char *name, uint32_t alarm_id)
-{
- STRING *chart_string = string_strdupz(chart);
- STRING *name_string = string_strdupz(name);
-
- uint32_t ret = 1;
-
- ALARM_ENTRY *ae;
- for(ae = host->health_log.alarms; ae ;ae = ae->next) {
- if (unlikely(ae->alarm_id == alarm_id && (!(chart_string == ae->chart && name_string == ae->name)))) {
- ret = 0;
- break;
- }
- }
-
- string_freez(chart_string);
- string_freez(name_string);
-
- return ret;
-}
-
-static inline ssize_t health_alarm_log_read(RRDHOST *host, FILE *fp, const char *filename) {
- errno = 0;
-
- char *s, *buf = mallocz(65536 + 1);
- size_t line = 0, len = 0;
- ssize_t loaded = 0, updated = 0, errored = 0, duplicate = 0;
-
- DICTIONARY *all_rrdcalcs = dictionary_create(
- DICT_OPTION_NAME_LINK_DONT_CLONE | DICT_OPTION_VALUE_LINK_DONT_CLONE | DICT_OPTION_DONT_OVERWRITE_VALUE);
- RRDCALC *rc;
- foreach_rrdcalc_in_rrdhost_read(host, rc) {
- dictionary_set(all_rrdcalcs, rrdcalc_name(rc), rc, sizeof(*rc));
- }
- foreach_rrdcalc_in_rrdhost_done(rc);
-
- netdata_rwlock_rdlock(&host->health_log.alarm_log_rwlock);
-
- while((s = fgets_trim_len(buf, 65536, fp, &len))) {
- host->health_log_entries_written++;
- line++;
-
- int max_entries = 33, entries = 0;
- char *pointers[max_entries];
-
- pointers[entries++] = s++;
- while(*s) {
- if(unlikely(*s == '\t')) {
- *s = '\0';
- pointers[entries++] = ++s;
- if(entries >= max_entries) {
- error("HEALTH [%s]: line %zu of file '%s' has more than %d entries. Ignoring excessive entries.", rrdhost_hostname(host), line, filename, max_entries);
- break;
- }
- }
- else s++;
- }
-
- if(likely(*pointers[0] == 'L'))
- continue;
-
- if(likely(*pointers[0] == 'U' || *pointers[0] == 'A')) {
- ALARM_ENTRY *ae = NULL;
-
- if(entries < 27) {
- error("HEALTH [%s]: line %zu of file '%s' should have at least 27 entries, but it has %d. Ignoring it.", rrdhost_hostname(host), line, filename, entries);
- errored++;
- continue;
- }
-
- // check that we have valid ids
- uint32_t unique_id = (uint32_t)strtoul(pointers[2], NULL, 16);
- if(!unique_id) {
- error("HEALTH [%s]: line %zu of file '%s' states alarm entry with invalid unique id %u (%s). Ignoring it.", rrdhost_hostname(host), line, filename, unique_id, pointers[2]);
- errored++;
- continue;
- }
-
- uint32_t alarm_id = (uint32_t)strtoul(pointers[3], NULL, 16);
- if(!alarm_id) {
- error("HEALTH [%s]: line %zu of file '%s' states alarm entry for invalid alarm id %u (%s). Ignoring it.", rrdhost_hostname(host), line, filename, alarm_id, pointers[3]);
- errored++;
- continue;
- }
-
- // Check if we got last_repeat field
- time_t last_repeat = 0;
- if(entries > 27) {
- char* alarm_name = pointers[13];
- last_repeat = (time_t)strtoul(pointers[27], NULL, 16);
-
- rc = dictionary_get(all_rrdcalcs, alarm_name);
- if(unlikely(rc)) {
- if (rrdcalc_isrepeating(rc)) {
- rc->last_repeat = last_repeat;
- // We iterate through repeating alarm entries only to
- // find the latest last_repeat timestamp. Otherwise,
- // there is no need to keep them in memory.
- continue;
- }
- }
- }
-
- if(unlikely(*pointers[0] == 'A')) {
- // make sure it is properly numbered
- if(unlikely(host->health_log.alarms && unique_id < host->health_log.alarms->unique_id)) {
- error( "HEALTH [%s]: line %zu of file '%s' has alarm log entry %u in wrong order. Ignoring it."
- , rrdhost_hostname(host), line, filename, unique_id);
- errored++;
- continue;
- }
-
- ae = callocz(1, sizeof(ALARM_ENTRY));
- }
- else if(unlikely(*pointers[0] == 'U')) {
- // find the original
- for(ae = host->health_log.alarms; ae ; ae = ae->next) {
- if(unlikely(unique_id == ae->unique_id)) {
- if(unlikely(*pointers[0] == 'A')) {
- error("HEALTH [%s]: line %zu of file '%s' adds duplicate alarm log entry %u. Using the later."
- , rrdhost_hostname(host), line, filename, unique_id);
- *pointers[0] = 'U';
- duplicate++;
- }
- break;
- }
- else if(unlikely(unique_id > ae->unique_id)) {
- // no need to continue
- // the linked list is sorted
- ae = NULL;
- break;
- }
- }
- }
-
- // if not found, skip this line
- if(unlikely(!ae)) {
- // error("HEALTH [%s]: line %zu of file '%s' updates alarm log entry with unique id %u, but it is not found.", host->hostname, line, filename, unique_id);
- continue;
- }
-
- // check for a possible host mismatch
- //if(strcmp(pointers[1], host->hostname))
- // error("HEALTH [%s]: line %zu of file '%s' provides an alarm for host '%s' but this is named '%s'.", host->hostname, line, filename, pointers[1], host->hostname);
-
- ae->unique_id = unique_id;
- if (!is_valid_alarm_id(host, pointers[14], pointers[13], alarm_id)) {
- STRING *chart = string_strdupz(pointers[14]);
- STRING *name = string_strdupz(pointers[13]);
- alarm_id = rrdcalc_get_unique_id(host, chart, name, NULL);
- string_freez(chart);
- string_freez(name);
- }
- ae->alarm_id = alarm_id;
- ae->alarm_event_id = (uint32_t)strtoul(pointers[4], NULL, 16);
- ae->updated_by_id = (uint32_t)strtoul(pointers[5], NULL, 16);
- ae->updates_id = (uint32_t)strtoul(pointers[6], NULL, 16);
-
- ae->when = (uint32_t)strtoul(pointers[7], NULL, 16);
- ae->duration = (uint32_t)strtoul(pointers[8], NULL, 16);
- ae->non_clear_duration = (uint32_t)strtoul(pointers[9], NULL, 16);
-
- ae->flags = (uint32_t)strtoul(pointers[10], NULL, 16);
- ae->flags |= HEALTH_ENTRY_FLAG_SAVED;
-
- ae->exec_run_timestamp = (uint32_t)strtoul(pointers[11], NULL, 16);
- ae->delay_up_to_timestamp = (uint32_t)strtoul(pointers[12], NULL, 16);
-
- string_freez(ae->name);
- ae->name = string_strdupz(pointers[13]);
-
- string_freez(ae->chart);
- ae->chart = string_strdupz(pointers[14]);
-
- string_freez(ae->family);
- ae->family = string_strdupz(pointers[15]);
-
- string_freez(ae->exec);
- ae->exec = string_strdupz(pointers[16]);
-
- string_freez(ae->recipient);
- ae->recipient = string_strdupz(pointers[17]);
-
- string_freez(ae->source);
- ae->source = string_strdupz(pointers[18]);
-
- string_freez(ae->units);
- ae->units = string_strdupz(pointers[19]);
-
- string_freez(ae->info);
- ae->info = string_strdupz(pointers[20]);
-
- ae->exec_code = str2i(pointers[21]);
- ae->new_status = str2i(pointers[22]);
- ae->old_status = str2i(pointers[23]);
- ae->delay = str2i(pointers[24]);
-
- ae->new_value = str2l(pointers[25]);
- ae->old_value = str2l(pointers[26]);
-
- ae->last_repeat = last_repeat;
-
- if (likely(entries > 30)) {
- string_freez(ae->classification);
- ae->classification = string_strdupz(pointers[28]);
-
- string_freez(ae->component);
- ae->component = string_strdupz(pointers[29]);
-
- string_freez(ae->type);
- ae->type = string_strdupz(pointers[30]);
- }
-
- char value_string[100 + 1];
- string_freez(ae->old_value_string);
- string_freez(ae->new_value_string);
- ae->old_value_string = string_strdupz(format_value_and_unit(value_string, 100, ae->old_value, ae_units(ae), -1));
- ae->new_value_string = string_strdupz(format_value_and_unit(value_string, 100, ae->new_value, ae_units(ae), -1));
-
- // add it to host if not already there
- if(unlikely(*pointers[0] == 'A')) {
- ae->next = host->health_log.alarms;
- host->health_log.alarms = ae;
- sql_health_alarm_log_insert(host, ae);
- loaded++;
- }
- else {
- sql_health_alarm_log_update(host, ae);
- updated++;
- }
-
- if(unlikely(ae->unique_id > host->health_max_unique_id))
- host->health_max_unique_id = ae->unique_id;
-
- if(unlikely(ae->alarm_id >= host->health_max_alarm_id))
- host->health_max_alarm_id = ae->alarm_id;
- }
- else {
- error("HEALTH [%s]: line %zu of file '%s' is invalid (unrecognized entry type '%s').", rrdhost_hostname(host), line, filename, pointers[0]);
- errored++;
- }
- }
-
- netdata_rwlock_unlock(&host->health_log.alarm_log_rwlock);
-
- dictionary_destroy(all_rrdcalcs);
- all_rrdcalcs = NULL;
-
- freez(buf);
-
- if(!host->health_max_unique_id) host->health_max_unique_id = (uint32_t)now_realtime_sec();
- if(!host->health_max_alarm_id) host->health_max_alarm_id = (uint32_t)now_realtime_sec();
-
- host->health_log.next_log_id = host->health_max_unique_id + 1;
- if (unlikely(!host->health_log.next_alarm_id || host->health_log.next_alarm_id <= host->health_max_alarm_id))
- host->health_log.next_alarm_id = host->health_max_alarm_id + 1;
-
- debug(D_HEALTH, "HEALTH [%s]: loaded file '%s' with %zd new alarm entries, updated %zd alarms, errors %zd entries, duplicate %zd", rrdhost_hostname(host), filename, loaded, updated, errored, duplicate);
- return loaded;
-}
-
-inline void health_alarm_log_load(RRDHOST *host) {
- health_alarm_log_close(host);
-
- char filename[FILENAME_MAX + 1];
- snprintfz(filename, FILENAME_MAX, "%s.old", host->health_log_filename);
- FILE *fp = fopen(filename, "r");
- if(!fp)
- error("HEALTH [%s]: cannot open health file: %s", rrdhost_hostname(host), filename);
- else {
- health_alarm_log_read(host, fp, filename);
- fclose(fp);
- }
-
- host->health_log_entries_written = 0;
- fp = fopen(host->health_log_filename, "r");
- if(!fp)
- error("HEALTH [%s]: cannot open health file: %s", rrdhost_hostname(host), host->health_log_filename);
- else {
- health_alarm_log_read(host, fp, host->health_log_filename);
- fclose(fp);
- }
-}
-
-
// ----------------------------------------------------------------------------
// health alarm log management
diff --git a/health/notifications/README.md b/health/notifications/README.md
index 0bd6c764..c59fecce 100644
--- a/health/notifications/README.md
+++ b/health/notifications/README.md
@@ -1,7 +1,11 @@
<!--
title: "Alarm notifications"
description: "Reference documentation for Netdata's alarm notification feature, which supports dozens of endpoints, user roles, and more."
-custom_edit_url: https://github.com/netdata/netdata/edit/master/health/notifications/README.md
+custom_edit_url: "https://github.com/netdata/netdata/edit/master/health/notifications/README.md"
+sidebar_label: "Notifications Reference"
+learn_status: "Published"
+learn_topic_type: "Tasks"
+learn_rel_path: "Operations/Alerts"
-->
# Alarm notifications
diff --git a/health/notifications/alarm-notify.sh.in b/health/notifications/alarm-notify.sh.in
index 3edf3d08..0090427a 100755
--- a/health/notifications/alarm-notify.sh.in
+++ b/health/notifications/alarm-notify.sh.in
@@ -18,7 +18,7 @@
# - emails by @ktsaou
# - slack.com notifications by @ktsaou
# - alerta.io notifications by @kattunga
-# - discordapp.com notifications by @lowfive
+# - discord.com notifications by @lowfive
# - pushover.net notifications by @ktsaou
# - pushbullet.com push notifications by Tiago Peralta @tperalta82 #1070
# - telegram.org notifications by @hashworks #1002
@@ -484,53 +484,105 @@ msteams_migration
# filter a recipient based on alarm event severity
filter_recipient_by_criticality() {
- local method="${1}" x="${2}" r s
- shift
-
- r="${x/|*/}" # the recipient
- s="${x/*|/}" # the severity required for notifying this recipient
+ local method="${1}" recipient_arg="${2}"
+ local tracking_dir tracking_file modifier modifiers recipient="${recipient_arg/|*/}"
+ local mod_critical=0 mod_noclear=0 mod_nowarn=0
# no severity filtering for this person
- [ "${r}" = "${s}" ] && return 0
+ [ "${recipient}" = "${recipient_arg}" ] && return 0
+
+ # find out which modifiers are set
+ modifiers="${recipient_arg#*|}"
+ modifiers="${modifiers//|/ }" # replace pipes with spaces
+ modifiers="${modifiers,,}" # lowercase
+ for modifier in ${modifiers}; do
+ case "${modifier}" in
+ critical) mod_critical=1 ;;
+ noclear) mod_noclear=1 ;;
+ nowarn) mod_nowarn=1 ;;
+
+ *)
+ error "SEVERITY FILTERING for ${recipient_arg} VIA ${method}: invalid modifier '${modifier}'."
+ # invalid modifier, always send notification
+ return 0
+ ;;
+ esac
+ done
- # the severity is invalid
- s="${s^^}"
- if [ "${s}" != "CRITICAL" ]; then
- error "SEVERITY FILTERING for ${x} VIA ${method}: invalid severity '${s,,}', only 'critical' is supported."
- return 0
- fi
+ # set status tracking directory/file var
+ tracking_dir="${NETDATA_CACHE_DIR}/alarm-notify/${method}/${recipient}"
+ tracking_file="${tracking_dir}/${alarm_id}"
- # create the status tracking directory for this user
- [ ! -d "${NETDATA_CACHE_DIR}/alarm-notify/${method}/${r}" ] &&
- mkdir -p "${NETDATA_CACHE_DIR}/alarm-notify/${method}/${r}"
+ # create the status tracking directory for this user if "critical" modifier is set
+ [ "${mod_critical}" == "1" ] && [ ! -d "${tracking_dir}" ] && mkdir -p "${tracking_dir}"
case "${status}" in
- CRITICAL)
- # make sure he will get future notifications for this alarm too
- touch "${NETDATA_CACHE_DIR}/alarm-notify/${method}/${r}/${alarm_id}"
- debug "SEVERITY FILTERING for ${x} VIA ${method}: ALLOW: the alarm is CRITICAL (will now receive next status change)"
- return 0
- ;;
-
- WARNING)
- if [ -f "${NETDATA_CACHE_DIR}/alarm-notify/${method}/${r}/${alarm_id}" ]; then
- # we do not remove the file, so that he will get future notifications of this alarm
- debug "SEVERITY FILTERING for ${x} VIA ${method}: ALLOW: recipient has been notified for this alarm in the past (will still receive next status change)"
- return 0
- fi
- ;;
+ CRITICAL)
+ # "critical" modifier set, create tracking file for future status changes
+ if [ "${mod_critical}" == "1" ]; then
+ touch "${tracking_file}"
+ debug "SEVERITY FILTERING for ${recipient_arg} VIA ${method}: ALLOW: the alarm is CRITICAL (will now receive next status change)"
+ return 0
+ fi
- *)
- if [ -f "${NETDATA_CACHE_DIR}/alarm-notify/${method}/${r}/${alarm_id}" ]; then
- # remove the file, so that he will only receive notifications for CRITICAL states for this alarm
- rm "${NETDATA_CACHE_DIR}/alarm-notify/${method}/${r}/${alarm_id}"
- debug "SEVERITY FILTERING for ${x} VIA ${method}: ALLOW: recipient has been notified for this alarm (will only receive CRITICAL notifications from now on)"
+ # always send CRITICAL notification
+ debug "SEVERITY FILTERING for ${recipient_arg} VIA ${method}: ALLOW: the alarm is CRITICAL"
return 0
- fi
- ;;
+ ;;
+
+ WARNING)
+ # "nowarn" modifier set, block notification
+ if [ "${mod_nowarn}" == "1" ]; then
+ debug "SEVERITY FILTERING for ${recipient_arg} VIA ${method}: BLOCK: recipient should not receive this notification (nowarn modifier set)"
+ return 1
+ fi
+
+ # "critical" modifier not set, send notification
+ if [ "${mod_critical}" == "0" ]; then
+ debug "SEVERITY FILTERING for ${recipient_arg} VIA ${method}: ALLOW: the alarm is WARNING"
+ return 0
+ fi
+
+ # "critical" modifier set, send notification if tracking file exists
+ if [ "${mod_critical}" == "1" ] && [ -f "${tracking_file}" ]; then
+ debug "SEVERITY FILTERING for ${recipient_arg} VIA ${method}: ALLOW: recipient has been notified for this alarm in the past (will still receive next status change)"
+ return 0
+ fi
+ ;;
+
+ CLEAR)
+ # remove tracking file
+ [ -f "${tracking_file}" ] && rm "${tracking_file}"
+
+ # "noclear" modifier set, block notification
+ if [ "${mod_noclear}" == "1" ]; then
+ debug "SEVERITY FILTERING for ${recipient_arg} VIA ${method}: BLOCK: recipient should not receive this notification (noclear modifier set)"
+ return 1
+ fi
+
+ # "critical" modifier not set, send notification
+ if [ "${mod_critical}" == "0" ]; then
+ debug "SEVERITY FILTERING for ${recipient_arg} VIA ${method}: ALLOW: the alarm is CLEAR"
+ return 0
+ fi
+
+ # "critical" modifier set, send notification if tracking file exists
+ if [ "${mod_critical}" == "1" ] && [ -f "${tracking_file}" ]; then
+ debug "SEVERITY FILTERING for ${recipient_arg} VIA ${method}: ALLOW: recipient has been notified for this alarm in the past (no status change will be sent from now)"
+ return 0
+ fi
+ ;;
+
+ *)
+ # "critical" modifier set, send notification if tracking file exists
+ if [ "${mod_critical}" == "1" ] && [ -f "${tracking_file}" ]; then
+ debug "SEVERITY FILTERING for ${recipient_arg} VIA ${method}: ALLOW: recipient has been notified for this alarm in the past (will still receive next status change)"
+ return 0
+ fi
+ ;;
esac
- debug "SEVERITY FILTERING for ${x} VIA ${method}: BLOCK: recipient should not receive this notification"
+ debug "SEVERITY FILTERING for ${recipient_arg} VIA ${method}: BLOCK: recipient should not receive this notification"
return 1
}
@@ -1480,10 +1532,12 @@ send_slack() {
"fields": [
{
"title": "${chart}",
+ "value": "chart",
"short": true
},
{
"title": "${family}",
+ "value": "family",
"short": true
}
],
diff --git a/health/notifications/alerta/README.md b/health/notifications/alerta/README.md
index 9603aae0..5ecf55ee 100644
--- a/health/notifications/alerta/README.md
+++ b/health/notifications/alerta/README.md
@@ -1,7 +1,12 @@
<!--
title: "alerta.io"
+sidebar_label: "Alerta"
description: "Send alarm notifications to Alerta to see the latest health status updates from multiple nodes in a single interface."
-custom_edit_url: https://github.com/netdata/netdata/edit/master/health/notifications/alerta/README.md
+custom_edit_url: "https://github.com/netdata/netdata/edit/master/health/notifications/alerta/README.md"
+learn_status: "Published"
+learn_topic_type: "Tasks"
+learn_rel_path: "Setup/Notification/Agent"
+learn_autogeneration_metadata: "{'part_of_cloud': False, 'part_of_agent': True}"
-->
# alerta.io
diff --git a/health/notifications/awssns/README.md b/health/notifications/awssns/README.md
index fc4a665e..97768399 100644
--- a/health/notifications/awssns/README.md
+++ b/health/notifications/awssns/README.md
@@ -1,7 +1,12 @@
<!--
title: "Amazon SNS"
+sidebar_label: "Amazon SNS"
description: "hello"
-custom_edit_url: https://github.com/netdata/netdata/edit/master/health/notifications/awssns/README.md
+custom_edit_url: "https://github.com/netdata/netdata/edit/master/health/notifications/awssns/README.md"
+learn_status: "Published"
+learn_topic_type: "Tasks"
+learn_rel_path: "Setup/Notification/Agent"
+learn_autogeneration_metadata: "{'part_of_cloud': False, 'part_of_agent': True}"
-->
# Amazon SNS
diff --git a/health/notifications/custom/README.md b/health/notifications/custom/README.md
index edc42623..df8f88e4 100644
--- a/health/notifications/custom/README.md
+++ b/health/notifications/custom/README.md
@@ -1,6 +1,11 @@
<!--
title: "Custom"
-custom_edit_url: https://github.com/netdata/netdata/edit/master/health/notifications/custom/README.md
+sidebar_label: "Custom endpoint"
+custom_edit_url: "https://github.com/netdata/netdata/edit/master/health/notifications/custom/README.md"
+learn_status: "Published"
+learn_topic_type: "Tasks"
+learn_rel_path: "Setup/Notification/Agent"
+learn_autogeneration_metadata: "{'part_of_cloud': False, 'part_of_agent': True}"
-->
# Custom
@@ -8,8 +13,8 @@ custom_edit_url: https://github.com/netdata/netdata/edit/master/health/notificat
Netdata allows you to send custom notifications to any endpoint you choose.
To configure custom notifications, you will need to customize `health_alarm_notify.conf`. Open the file for editing
-using [`edit-config`](/docs/configure/nodes.md#use-edit-config-to-edit-configuration-files) from the [Netdata config
-directory](/docs/configure/nodes.md#the-netdata-config-directory), which is typically at `/etc/netdata`.
+using [`edit-config`](https://github.com/netdata/netdata/blob/master/docs/configure/nodes.md#use-edit-config-to-edit-configuration-files) from the [Netdata config
+directory](https://github.com/netdata/netdata/blob/master/docs/configure/nodes.md#the-netdata-config-directory), which is typically at `/etc/netdata`.
You can look at the other senders in `/usr/libexec/netdata/plugins.d/alarm-notify.sh` for examples of how to modify the `custom_sender()` function in `health_alarm_notify.conf`.
diff --git a/health/notifications/discord/README.md b/health/notifications/discord/README.md
index 568d03bc..b4cbce53 100644
--- a/health/notifications/discord/README.md
+++ b/health/notifications/discord/README.md
@@ -1,9 +1,14 @@
<!--
-title: "Discordapp.com"
-custom_edit_url: https://github.com/netdata/netdata/edit/master/health/notifications/discord/README.md
+title: "Discord.com"
+sidebar_label: "Discord"
+custom_edit_url: "https://github.com/netdata/netdata/edit/master/health/notifications/discord/README.md"
+learn_status: "Published"
+learn_topic_type: "Tasks"
+learn_rel_path: "Setup/Notification/Agent"
+learn_autogeneration_metadata: "{'part_of_cloud': False, 'part_of_agent': True}"
-->
-# Discordapp.com
+# Discord.com
This is what you will get:
@@ -11,7 +16,7 @@ This is what you will get:
You need:
-1. The **incoming webhook URL** as given by Discord. Create a webhook by following the official [Discord documentation](https://support.discordapp.com/hc/en-us/articles/228383668-Intro-to-Webhooks). You can use the same on all your Netdata servers (or you can have multiple if you like - your decision).
+1. The **incoming webhook URL** as given by Discord. Create a webhook by following the official [Discord documentation](https://support.discord.com/hc/en-us/articles/228383668-Intro-to-Webhooks). You can use the same on all your Netdata servers (or you can have multiple if you like - your decision).
2. One or more Discord channels to post the messages to.
Set them in `/etc/netdata/health_alarm_notify.conf` (to edit it on your system run `/etc/netdata/edit-config health_alarm_notify.conf`), like this:
@@ -27,8 +32,8 @@ Set them in `/etc/netdata/health_alarm_notify.conf` (to edit it on your system r
SEND_DISCORD="YES"
# Create a webhook by following the official documentation -
-# https://support.discordapp.com/hc/en-us/articles/228383668-Intro-to-Webhooks
-DISCORD_WEBHOOK_URL="https://discordapp.com/api/webhooks/XXXXXXXXXXXXX/XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
+# https://support.discord.com/hc/en-us/articles/228383668-Intro-to-Webhooks
+DISCORD_WEBHOOK_URL="https://discord.com/api/webhooks/XXXXXXXXXXXXX/XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
# if a role's recipients are not configured, a notification will be send to
# this discord channel (empty = do not send a notification for unconfigured
@@ -45,6 +50,4 @@ role_recipients_discord[dba]="databases systems"
role_recipients_discord[webmaster]="marketing development"
```
-The keywords `systems`, `databases`, `marketing`, `development` are discordapp.com channels (they should already exist within your discord server).
-
-
+The keywords `systems`, `databases`, `marketing`, `development` are discord.com channels (they should already exist within your discord server).
diff --git a/health/notifications/dynatrace/README.md b/health/notifications/dynatrace/README.md
index 3f8ad85b..a3668393 100644
--- a/health/notifications/dynatrace/README.md
+++ b/health/notifications/dynatrace/README.md
@@ -1,6 +1,11 @@
<!--
title: "Dynatrace"
-custom_edit_url: https://github.com/netdata/netdata/edit/master/health/notifications/dynatrace/README.md
+sidebar_label: "Dynatrace Events"
+custom_edit_url: "https://github.com/netdata/netdata/edit/master/health/notifications/dynatrace/README.md"
+learn_status: "Published"
+learn_topic_type: "Tasks"
+learn_rel_path: "Setup/Notification/Agent"
+learn_autogeneration_metadata: "{'part_of_cloud': False, 'part_of_agent': True}"
-->
# Dynatrace
diff --git a/health/notifications/email/README.md b/health/notifications/email/README.md
index 3dc84dd4..01dfd0e6 100644
--- a/health/notifications/email/README.md
+++ b/health/notifications/email/README.md
@@ -1,6 +1,11 @@
<!--
title: "Email"
-custom_edit_url: https://github.com/netdata/netdata/edit/master/health/notifications/email/README.md
+sidebar_label: "Email"
+custom_edit_url: "https://github.com/netdata/netdata/edit/master/health/notifications/email/README.md"
+learn_status: "Published"
+learn_topic_type: "Tasks"
+learn_rel_path: "Setup/Notification/Agent"
+learn_autogeneration_metadata: "{'part_of_cloud': True, 'part_of_agent': True}"
-->
# Email
diff --git a/health/notifications/flock/README.md b/health/notifications/flock/README.md
index b9e0025b..175f8a46 100644
--- a/health/notifications/flock/README.md
+++ b/health/notifications/flock/README.md
@@ -1,6 +1,11 @@
<!--
title: "Flock"
-custom_edit_url: https://github.com/netdata/netdata/edit/master/health/notifications/flock/README.md
+sidebar_label: "Flock"
+custom_edit_url: "https://github.com/netdata/netdata/edit/master/health/notifications/flock/README.md"
+learn_status: "Published"
+learn_topic_type: "Tasks"
+learn_rel_path: "Setup/Notification/Agent"
+learn_autogeneration_metadata: "{'part_of_cloud': False, 'part_of_agent': True}"
-->
# Flock
diff --git a/health/notifications/gotify/README.md b/health/notifications/gotify/README.md
index c253c845..d01502b6 100644
--- a/health/notifications/gotify/README.md
+++ b/health/notifications/gotify/README.md
@@ -3,6 +3,10 @@ title: "Send notifications to Gotify"
description: "Send alerts to your Gotify instance when an alert gets triggered in Netdata."
sidebar_label: "Gotify"
custom_edit_url: https://github.com/netdata/netdata/edit/master/health/notifications/gotify/README.md
+learn_status: "Published"
+learn_topic_type: "Tasks"
+learn_rel_path: "Setup/Notification/Agent"
+learn_autogeneration_metadata: "{'part_of_cloud': False, 'part_of_agent': True}"
-->
# Send notifications to Gotify
@@ -21,7 +25,7 @@ You can generate a new token in the Gotify Web UI.
To set up Gotify in Netdata:
1. Switch to your [config
-directory](/docs/configure/nodes.md) and edit the file `health_alarm_notify.conf` using the edit config script.
+directory](https://github.com/netdata/netdata/blob/master/docs/configure/nodes.md) and edit the file `health_alarm_notify.conf` using the edit config script.
```bash
./edit-config health_alarm_notify.conf
diff --git a/health/notifications/hangouts/README.md b/health/notifications/hangouts/README.md
index 7554b39c..45da1bfa 100644
--- a/health/notifications/hangouts/README.md
+++ b/health/notifications/hangouts/README.md
@@ -2,7 +2,11 @@
title: "Send notifications to Google Hangouts"
description: "Send alerts to Send notifications to Google Hangouts any time an anomaly or performance issue strikes a node in your infrastructure."
sidebar_label: "Google Hangouts"
-custom_edit_url: https://github.com/netdata/netdata/edit/master/health/notifications/hangouts/README.md
+custom_edit_url: "https://github.com/netdata/netdata/edit/master/health/notifications/hangouts/README.md"
+learn_status: "Published"
+learn_topic_type: "Tasks"
+learn_rel_path: "Setup/Notification/Agent"
+learn_autogeneration_metadata: "{'part_of_cloud': False, 'part_of_agent': True}"
-->
# Send notifications to Google Hangouts
diff --git a/health/notifications/health_alarm_notify.conf b/health/notifications/health_alarm_notify.conf
index 52de8664..4878661a 100755
--- a/health/notifications/health_alarm_notify.conf
+++ b/health/notifications/health_alarm_notify.conf
@@ -9,7 +9,7 @@
# - messages to your slack team (slack.com),
# - messages to your alerta server (alerta.io),
# - messages to your flock team (flock.com),
-# - messages to your discord guild (discordapp.com),
+# - messages to your discord guild (discord.com),
# - messages to your telegram chat / group chat (telegram.org)
# - sms messages to your cell phone or any sms enabled device (twilio.com)
# - sms messages to your cell phone or any sms enabled device (messagebird.com)
@@ -160,7 +160,11 @@ sendsms=""
# - pagerduty.com (pd) services
# - irc channels
#
-# You can append |critical to limit the notifications to be sent.
+# You can append modifiers to limit the notifications to be sent:
+# |critical - Send critical notifications and following status changes until
+# the alarm is cleared.
+# |nowarn - Do not send warning notifications.
+# |noclear - Do not send clear notifications.
#
# In these examples, the first recipient receives all the alarms
# while the second one receives only notifications for alarms that
@@ -182,6 +186,11 @@ sendsms=""
# irc : "<irc_channel_1> <irc_channel_2>|critical"
# hangouts : "alarms disasters|critical"
#
+# You can append multiple modifiers. In this example, recipient receives
+# notifications for critical alarms and following status changes except clear
+# notifications.
+# email : "user1@example.com|critical|noclear"
+#
# If a recipient is set to empty string, the default recipient of the given
# notification method (email, pushover, telegram, slack, alerta, etc) will be used.
# To disable a notification, use the recipient called: disabled
@@ -579,7 +588,7 @@ DEFAULT_RECIPIENT_FLOCK=""
#------------------------------------------------------------------------------
-# discord (discordapp.com) global notification options
+# discord (discord.com) global notification options
# multiple recipients can be given like this:
# "CHANNEL1 CHANNEL2 ..."
@@ -588,7 +597,7 @@ DEFAULT_RECIPIENT_FLOCK=""
SEND_DISCORD="YES"
# Create a webhook by following the official documentation -
-# https://support.discordapp.com/hc/en-us/articles/228383668-Intro-to-Webhooks
+# https://support.discord.com/hc/en-us/articles/228383668-Intro-to-Webhooks
DISCORD_WEBHOOK_URL=""
# if a role's recipients are not configured, a notification will be send to
diff --git a/health/notifications/irc/README.md b/health/notifications/irc/README.md
index 21c998d1..a4877f48 100644
--- a/health/notifications/irc/README.md
+++ b/health/notifications/irc/README.md
@@ -1,6 +1,11 @@
<!--
title: "IRC"
-custom_edit_url: https://github.com/netdata/netdata/edit/master/health/notifications/irc/README.md
+sidebar_label: "IRC"
+custom_edit_url: "https://github.com/netdata/netdata/edit/master/health/notifications/irc/README.md"
+learn_status: "Published"
+learn_topic_type: "Tasks"
+learn_rel_path: "Setup/Notification/Agent"
+learn_autogeneration_metadata: "{'part_of_cloud': False, 'part_of_agent': True}"
-->
# IRC
diff --git a/health/notifications/kavenegar/README.md b/health/notifications/kavenegar/README.md
index 6123eb90..443fcdba 100644
--- a/health/notifications/kavenegar/README.md
+++ b/health/notifications/kavenegar/README.md
@@ -1,6 +1,11 @@
<!--
title: "Kavenegar"
-custom_edit_url: https://github.com/netdata/netdata/edit/master/health/notifications/kavenegar/README.md
+sidebar_label: "Kavenegar"
+custom_edit_url: "https://github.com/netdata/netdata/edit/master/health/notifications/kavenegar/README.md"
+learn_status: "Published"
+learn_topic_type: "Tasks"
+learn_rel_path: "Setup/Notification/Agent"
+learn_autogeneration_metadata: "{'part_of_cloud': False, 'part_of_agent': True}"
-->
# Kavenegar
diff --git a/health/notifications/matrix/README.md b/health/notifications/matrix/README.md
index 8eeecf55..80e22da3 100644
--- a/health/notifications/matrix/README.md
+++ b/health/notifications/matrix/README.md
@@ -2,7 +2,11 @@
title: "Send Netdata notifications to Matrix network rooms"
description: "Stay aware of warning or critical anomalies by sending health alarms to Matrix network rooms with Netdata's health monitoring watchdog."
sidebar_label: "Matrix"
-custom_edit_url: https://github.com/netdata/netdata/edit/master/health/notifications/matrix/README.md
+custom_edit_url: "https://github.com/netdata/netdata/edit/master/health/notifications/matrix/README.md"
+learn_status: "Published"
+learn_topic_type: "Tasks"
+learn_rel_path: "Setup/Notification/Agent"
+learn_autogeneration_metadata: "{'part_of_cloud': False, 'part_of_agent': True}"
-->
# Matrix
diff --git a/health/notifications/messagebird/README.md b/health/notifications/messagebird/README.md
index f70e86c6..01430198 100644
--- a/health/notifications/messagebird/README.md
+++ b/health/notifications/messagebird/README.md
@@ -1,6 +1,11 @@
<!--
title: "Messagebird"
-custom_edit_url: https://github.com/netdata/netdata/edit/master/health/notifications/messagebird/README.md
+sidebar_label: "Messagebird"
+custom_edit_url: "https://github.com/netdata/netdata/edit/master/health/notifications/messagebird/README.md"
+learn_status: "Published"
+learn_topic_type: "Tasks"
+learn_rel_path: "Setup/Notification/Agent"
+learn_autogeneration_metadata: "{'part_of_cloud': False, 'part_of_agent': True}"
-->
# Messagebird
diff --git a/health/notifications/msteams/README.md b/health/notifications/msteams/README.md
index c9a13bac..75e652a7 100644
--- a/health/notifications/msteams/README.md
+++ b/health/notifications/msteams/README.md
@@ -1,6 +1,11 @@
<!--
title: "Microsoft Teams"
-custom_edit_url: https://github.com/netdata/netdata/edit/master/health/notifications/msteams/README.md
+sidebar_label: "Microsoft Teams"
+custom_edit_url: "https://github.com/netdata/netdata/edit/master/health/notifications/msteams/README.md"
+learn_status: "Published"
+learn_topic_type: "Tasks"
+learn_rel_path: "Setup/Notification/Agent"
+learn_autogeneration_metadata: "{'part_of_cloud': False, 'part_of_agent': True}"
-->
# Microsoft Teams
diff --git a/health/notifications/opsgenie/README.md b/health/notifications/opsgenie/README.md
index 640fcd42..20f14b39 100644
--- a/health/notifications/opsgenie/README.md
+++ b/health/notifications/opsgenie/README.md
@@ -2,7 +2,11 @@
title: "Send notifications to Opsgenie"
description: "Send alerts to your Opsgenie incident response account any time an anomaly or performance issue strikes a node in your infrastructure."
sidebar_label: "Opsgenie"
-custom_edit_url: https://github.com/netdata/netdata/edit/master/health/notifications/opsgenie/README.md
+custom_edit_url: "https://github.com/netdata/netdata/edit/master/health/notifications/opsgenie/README.md"
+learn_status: "Published"
+learn_topic_type: "Tasks"
+learn_rel_path: "Setup/Notification/Agent"
+learn_autogeneration_metadata: "{'part_of_cloud': False, 'part_of_agent': True}"
-->
# Send notifications to Opsgenie
@@ -13,9 +17,9 @@ incidents.
The first step is to create a [Netdata integration](https://docs.opsgenie.com/docs/api-integration) in the
[Opsgenie](https://www.atlassian.com/software/opsgenie) dashboard. After this, you need to edit
-`health_alarm_notify.conf` on your system, by running the following from your [config
-directory](/docs/configure/nodes.md):
-
+`health_alarm_notify.conf` on your system, by running the following from
+your [config directory](https://github.com/netdata/netdata/blob/master/docs/configure/nodes.md):
+
```bash
./edit-config health_alarm_notify.conf
```
@@ -56,7 +60,7 @@ message:
2020-09-03 23:07:00: alarm-notify.sh: ERROR: failed to send opsgenie notification for: hades test.chart.test_alarm is CRITICAL, with HTTP error code 401.
```
-You can find more details about the Opsgenie error codes in their [response
-docs](https://docs.opsgenie.com/docs/response).
+You can find more details about the Opsgenie error codes in
+their [response docs](https://docs.opsgenie.com/docs/response).
diff --git a/health/notifications/pagerduty/README.md b/health/notifications/pagerduty/README.md
index 30db6379..c6190e83 100644
--- a/health/notifications/pagerduty/README.md
+++ b/health/notifications/pagerduty/README.md
@@ -2,7 +2,11 @@
title: "Send alert notifications to PagerDuty"
description: "Send alerts to your PagerDuty dashboard any time an anomaly or performance issue strikes a node in your infrastructure."
sidebar_label: "PagerDuty"
-custom_edit_url: https://github.com/netdata/netdata/edit/master/health/notifications/pagerduty/README.md
+custom_edit_url: "https://github.com/netdata/netdata/edit/master/health/notifications/pagerduty/README.md"
+learn_status: "Published"
+learn_topic_type: "Tasks"
+learn_rel_path: "Setup/Notification/Agent"
+learn_autogeneration_metadata: "{'part_of_cloud': False, 'part_of_agent': True}"
-->
# Send alert notifications to PagerDuty
@@ -14,7 +18,7 @@ resolution times.
## What you need to get started
-- An installation of the open-source [Netdata](/docs/get-started.mdx) monitoring agent.
+- An installation of the open-source [Netdata](https://github.com/netdata/netdata/blob/master/docs/get-started.mdx) monitoring agent.
- An installation of the [PagerDuty agent](https://www.pagerduty.com/docs/guides/agent-install-guide/) on the node
running Netdata.
- A PagerDuty `Generic API` service using either the `Events API v2` or `Events API v1`.
@@ -25,8 +29,8 @@ resolution times.
to PagerDuty. Click **Use our API directly** and select either `Events API v2` or `Events API v1`. Once you finish
creating the service, click on the **Integrations** tab to find your **Integration Key**.
-Navigate to the [Netdata config directory](/docs/configure/nodes.md#the-netdata-config-directory) and use
-[`edit-config`](/docs/configure/nodes.md#use-edit-config-to-edit-configuration-files) to open
+Navigate to the [Netdata config directory](https://github.com/netdata/netdata/blob/master/docs/configure/nodes.md#the-netdata-config-directory) and use
+[`edit-config`](https://github.com/netdata/netdata/blob/master/docs/configure/nodes.md#use-edit-config-to-edit-configuration-files) to open
`health_alarm_notify.conf`.
```bash
@@ -59,5 +63,5 @@ sudo su -s /bin/bash netdata
Aside from the three values set in `health_alarm_notify.conf`, there is no further configuration required to send alert
notifications to PagerDuty.
-To configure individual alarms, read our [alert configuration](/docs/monitor/configure-alarms.md) doc or
-the [health entity reference](/health/REFERENCE.md) doc.
+To configure individual alarms, read our [alert configuration](https://github.com/netdata/netdata/blob/master/docs/monitor/configure-alarms.md) doc or
+the [health entity reference](https://github.com/netdata/netdata/blob/master/health/REFERENCE.md) doc.
diff --git a/health/notifications/prowl/README.md b/health/notifications/prowl/README.md
index dc136820..8656c131 100644
--- a/health/notifications/prowl/README.md
+++ b/health/notifications/prowl/README.md
@@ -1,6 +1,11 @@
<!--
title: "Prowl"
-custom_edit_url: https://github.com/netdata/netdata/edit/master/health/notifications/prowl/README.md
+sidebar_label: "Prowl"
+custom_edit_url: "https://github.com/netdata/netdata/edit/master/health/notifications/prowl/README.md"
+learn_status: "Published"
+learn_topic_type: "Tasks"
+learn_rel_path: "Setup/Notification/Agent"
+learn_autogeneration_metadata: "{'part_of_cloud': False, 'part_of_agent': True}"
-->
# Prowl
diff --git a/health/notifications/pushbullet/README.md b/health/notifications/pushbullet/README.md
index 194050bc..17ed9364 100644
--- a/health/notifications/pushbullet/README.md
+++ b/health/notifications/pushbullet/README.md
@@ -1,6 +1,11 @@
<!--
title: "PushBullet"
-custom_edit_url: https://github.com/netdata/netdata/edit/master/health/notifications/pushbullet/README.md
+sidebar_label: "PushBullet"
+custom_edit_url: "https://github.com/netdata/netdata/edit/master/health/notifications/pushbullet/README.md"
+learn_status: "Published"
+learn_topic_type: "Tasks"
+learn_rel_path: "Setup/Notification/Agent"
+learn_autogeneration_metadata: "{'part_of_cloud': False, 'part_of_agent': True}"
-->
# PushBullet
diff --git a/health/notifications/pushover/README.md b/health/notifications/pushover/README.md
index 1e50f714..4d5ea5a9 100644
--- a/health/notifications/pushover/README.md
+++ b/health/notifications/pushover/README.md
@@ -1,6 +1,11 @@
<!--
title: "PushOver"
-custom_edit_url: https://github.com/netdata/netdata/edit/master/health/notifications/pushover/README.md
+sidebar_label: "PushOver"
+custom_edit_url: "https://github.com/netdata/netdata/edit/master/health/notifications/pushover/README.md"
+learn_status: "Published"
+learn_topic_type: "Tasks"
+learn_rel_path: "Setup/Notification/Agent"
+learn_autogeneration_metadata: "{'part_of_cloud': False, 'part_of_agent': True}"
-->
# PushOver
diff --git a/health/notifications/rocketchat/README.md b/health/notifications/rocketchat/README.md
index 96d6160b..0f7867d0 100644
--- a/health/notifications/rocketchat/README.md
+++ b/health/notifications/rocketchat/README.md
@@ -1,6 +1,11 @@
<!--
title: "Rocket.Chat"
-custom_edit_url: https://github.com/netdata/netdata/edit/master/health/notifications/rocketchat/README.md
+sidebar_label: "Rocket Chat"
+custom_edit_url: "https://github.com/netdata/netdata/edit/master/health/notifications/rocketchat/README.md"
+learn_status: "Published"
+learn_topic_type: "Tasks"
+learn_rel_path: "Setup/Notification/Agent"
+learn_autogeneration_metadata: "{'part_of_cloud': False, 'part_of_agent': True}"
-->
# Rocket.Chat
diff --git a/health/notifications/slack/README.md b/health/notifications/slack/README.md
index ad36ce34..ad9a2134 100644
--- a/health/notifications/slack/README.md
+++ b/health/notifications/slack/README.md
@@ -1,6 +1,11 @@
<!--
title: "Slack"
-custom_edit_url: https://github.com/netdata/netdata/edit/master/health/notifications/slack/README.md
+sidebar_label: "Slack"
+custom_edit_url: "https://github.com/netdata/netdata/edit/master/health/notifications/slack/README.md"
+learn_status: "Published"
+learn_topic_type: "Tasks"
+learn_rel_path: "Setup/Notification/Agent"
+learn_autogeneration_metadata: "{'part_of_cloud': False, 'part_of_agent': True}"
-->
# Slack
diff --git a/health/notifications/smstools3/README.md b/health/notifications/smstools3/README.md
index 6618dfa1..9535c954 100644
--- a/health/notifications/smstools3/README.md
+++ b/health/notifications/smstools3/README.md
@@ -1,6 +1,11 @@
<!--
title: "SMS Server Tools 3"
-custom_edit_url: https://github.com/netdata/netdata/edit/master/health/notifications/smstools3/README.md
+sidebar_label: "SMS server"
+custom_edit_url: "https://github.com/netdata/netdata/edit/master/health/notifications/smstools3/README.md"
+learn_status: "Published"
+learn_topic_type: "Tasks"
+learn_rel_path: "Setup/Notification/Agent"
+learn_autogeneration_metadata: "{'part_of_cloud': False, 'part_of_agent': True}"
-->
# SMS Server Tools 3
diff --git a/health/notifications/stackpulse/README.md b/health/notifications/stackpulse/README.md
index c478fd58..25266e82 100644
--- a/health/notifications/stackpulse/README.md
+++ b/health/notifications/stackpulse/README.md
@@ -2,7 +2,11 @@
title: "Send notifications to StackPulse"
description: "Send alerts to your StackPulse Netdata integration any time an anomaly or performance issue strikes a node in your infrastructure."
sidebar_label: "StackPulse"
-custom_edit_url: https://github.com/netdata/netdata/edit/master/health/notifications/stackpulse/README.md
+custom_edit_url: "https://github.com/netdata/netdata/edit/master/health/notifications/stackpulse/README.md"
+learn_status: "Published"
+learn_topic_type: "Tasks"
+learn_rel_path: "Setup/Notification/Agent"
+learn_autogeneration_metadata: "{'part_of_cloud': False, 'part_of_agent': True}"
-->
# Send notifications to StackPulse
@@ -40,7 +44,7 @@ STACKPULSE_WEBHOOK="https://hooks.stackpulse.io/v1/webhooks/YOUR_UNIQUE_ID"
```
4. Now restart Netdata using `sudo systemctl restart netdata`, or the [appropriate
- method](/docs/configure/start-stop-restart.md) for your system. When your node creates an alarm, you can see the
+ method](https://github.com/netdata/netdata/blob/master/docs/configure/start-stop-restart.md) for your system. When your node creates an alarm, you can see the
associated notification on your StackPulse Administration Portal
## React to alarms with playbooks
diff --git a/health/notifications/syslog/README.md b/health/notifications/syslog/README.md
index 8b7863a1..3527decc 100644
--- a/health/notifications/syslog/README.md
+++ b/health/notifications/syslog/README.md
@@ -1,6 +1,11 @@
<!--
title: "Syslog"
-custom_edit_url: https://github.com/netdata/netdata/edit/master/health/notifications/syslog/README.md
+sidebar_label: "Syslog"
+custom_edit_url: "https://github.com/netdata/netdata/edit/master/health/notifications/syslog/README.md"
+learn_status: "Published"
+learn_topic_type: "Tasks"
+learn_rel_path: "Setup/Notification/Agent"
+learn_autogeneration_metadata: "{'part_of_cloud': False, 'part_of_agent': True}"
-->
# Syslog
diff --git a/health/notifications/telegram/README.md b/health/notifications/telegram/README.md
index 2a2ed562..f80a2838 100644
--- a/health/notifications/telegram/README.md
+++ b/health/notifications/telegram/README.md
@@ -1,6 +1,11 @@
<!--
title: "Telegram"
-custom_edit_url: https://github.com/netdata/netdata/edit/master/health/notifications/telegram/README.md
+sidebar_label: "Telegram"
+custom_edit_url: "https://github.com/netdata/netdata/edit/master/health/notifications/telegram/README.md"
+learn_status: "Published"
+learn_topic_type: "Tasks"
+learn_rel_path: "Setup/Notification/Agent"
+learn_autogeneration_metadata: "{'part_of_cloud': False, 'part_of_agent': True}"
-->
# Telegram
diff --git a/health/notifications/twilio/README.md b/health/notifications/twilio/README.md
index b563c66c..470b2413 100644
--- a/health/notifications/twilio/README.md
+++ b/health/notifications/twilio/README.md
@@ -1,6 +1,11 @@
<!--
title: "Twilio"
-custom_edit_url: https://github.com/netdata/netdata/edit/master/health/notifications/twilio/README.md
+sidebar_label: "Twilio"
+custom_edit_url: "https://github.com/netdata/netdata/edit/master/health/notifications/twilio/README.md"
+learn_status: "Published"
+learn_topic_type: "Tasks"
+learn_rel_path: "Setup/Notification/Agent"
+learn_autogeneration_metadata: "{'part_of_cloud': False, 'part_of_agent': True}"
-->
# Twilio
diff --git a/health/notifications/web/README.md b/health/notifications/web/README.md
index 185843af..b4afd9ea 100644
--- a/health/notifications/web/README.md
+++ b/health/notifications/web/README.md
@@ -1,9 +1,14 @@
<!--
-title: "Dashboard"
-custom_edit_url: https://github.com/netdata/netdata/edit/master/health/notifications/web/README.md
+title: "Pop up"
+sidebar_label: "Pop up notifications"
+custom_edit_url: "https://github.com/netdata/netdata/edit/master/health/notifications/web/README.md"
+learn_status: "Published"
+learn_topic_type: "Tasks"
+learn_rel_path: "Setup/Notification/Agent"
+learn_autogeneration_metadata: "{'part_of_cloud': False, 'part_of_agent': True}"
-->
-# Dashboard
+# Pop up notifications
The Netdata dashboard shows HTML notifications, when it is open.
diff --git a/libnetdata/Makefile.am b/libnetdata/Makefile.am
index 1208d16c..b81d620b 100644
--- a/libnetdata/Makefile.am
+++ b/libnetdata/Makefile.am
@@ -5,7 +5,7 @@ MAINTAINERCLEANFILES = $(srcdir)/Makefile.in
SUBDIRS = \
adaptive_resortable_list \
- arrayalloc \
+ aral \
avl \
buffer \
clocks \
@@ -15,6 +15,7 @@ SUBDIRS = \
ebpf \
eval \
json \
+ july \
health \
locks \
log \
diff --git a/libnetdata/README.md b/libnetdata/README.md
index fe0690d6..1424faf6 100644
--- a/libnetdata/README.md
+++ b/libnetdata/README.md
@@ -1,6 +1,10 @@
<!--
title: "libnetdata"
custom_edit_url: https://github.com/netdata/netdata/edit/master/libnetdata/README.md
+sidebar_label: "libnetdata"
+learn_status: "Published"
+learn_topic_type: "Tasks"
+learn_rel_path: "Developers/libnetdata"
-->
# libnetdata
diff --git a/libnetdata/adaptive_resortable_list/README.md b/libnetdata/adaptive_resortable_list/README.md
index 9eb942bc..95757848 100644
--- a/libnetdata/adaptive_resortable_list/README.md
+++ b/libnetdata/adaptive_resortable_list/README.md
@@ -1,6 +1,10 @@
<!--
title: "Adaptive Re-sortable List (ARL)"
custom_edit_url: https://github.com/netdata/netdata/edit/master/libnetdata/adaptive_resortable_list/README.md
+sidebar_label: "Adaptive Re-sortable List (ARL)"
+learn_status: "Published"
+learn_topic_type: "Tasks"
+learn_rel_path: "Developers/libnetdata libraries"
-->
# Adaptive Re-sortable List (ARL)
diff --git a/libnetdata/arrayalloc/Makefile.am b/libnetdata/aral/Makefile.am
index 161784b8..161784b8 100644
--- a/libnetdata/arrayalloc/Makefile.am
+++ b/libnetdata/aral/Makefile.am
diff --git a/libnetdata/aral/README.md b/libnetdata/aral/README.md
new file mode 100644
index 00000000..3b0f5bbd
--- /dev/null
+++ b/libnetdata/aral/README.md
@@ -0,0 +1,173 @@
+<!--
+title: "Array Allocator"
+custom_edit_url: https://github.com/netdata/netdata/edit/master/libnetdata/aral/README.md
+sidebar_label: "Array allocator"
+learn_status: "Published"
+learn_topic_type: "Tasks"
+learn_rel_path: "Developers/libnetdata libraries"
+-->
+
+# Array Allocator
+
+Come on! Array allocators are embedded in libc! Why do we need such a thing in Netdata?
+
+Well, we have a couple of problems to solve:
+
+1. **Fragmentation** - It is important for Netdata to keeps its overall memory footprint as low as possible. libc does an amazing job when the same thread allocates and frees some memory. But it simply cannot do better without knowing the specifics of the application when memory is allocated and freed randomly between threads.
+2. **Speed** - Especially when allocations and de-allocations happen across threads, the speed penalty is tremendous.
+
+In Netdata we have a few moments that are very tough. Imagine collecting 1 million metrics per second. You have a buffer for each metric and put append new points there. This works beautifully, of course! But then, when the buffers get full, imagine the situation. You suddenly need 1 million buffers, at once!
+
+To solve this problem we first spread out the buffers. So, the first time each metric asks for a buffer, it gets a smaller one. We added logic there to spread them as evenly as possible across time. Solved? Not exactly!
+
+We have 3 tiers for each metric. For the metrics of tier 0 (per second resolution) we have a max buffer for 1024 points and every new metrics gets a random size between 3 points and 1024. So they are distributed across time. For 1 million metrics, we have about 1000 buffers beings created every second.
+
+But at some point, the end of the minute will come, and suddenly all the metrics will need a new buffer for tier 1 (per minute). Oops! We will spread tier 1 buffers across time too, but the first minute is a tough one. We really need 1 million buffers instantly.
+
+And if that minute happens to also be the beginning of an hour... tier 2 (per hour) kicks in. For that instant we are going to need 2 million buffers instantly.
+
+The problem becomes even bigger when we collect 2, or even 10 million metrics...
+
+So solve it, Netdata uses a special implementation of an array allocator that is tightly integrated with the structures we need.
+
+## Features
+
+1. Malloc, or MMAP modes. File based MMAP is also supported to put the data in file backed up shared memory.
+2. Fully asynchronous operations. There are just a couple of points where spin-locks protect a few counters and pointers.
+3. Optional defragmenter, that once enabled it will make free operation slower while trying to maintain a sorted list of fragments to offer first during allocations. The defragmenter can be enabled / disabled at run time. The defragmenter can hurt performance on application with intense turn-around of allocation, like Netdata dbengine caches. So, it is disabled by default.
+4. Without the defragmenter enabled, ARAL still tries to keep pages full, but the depth of the search is limited to 3 pages (so, a page with a free slot will either become 1st, 2nd, or 3rd). At the same time, during allocations, ARAL will evaluate the first 2 pages to find the one that is more full than the other, to use it for the new allocation.
+
+## How it works
+
+Allocations are organized in pages. Pages have a minimum size (a system page, usually 4KB) and a maximum defined by for each different kind of object.
+
+Initially every page is free. When an allocation request is made, the free space is split, and the first element is reserved. Free space is now considered there rest.
+
+This continuous until the page gets full, where a new page is allocated and the process is repeated.
+
+Each allocation returned has a pointer appended to it. The pointer points to the page the allocation belongs to.
+
+When a pointer is freed, the page it belongs is identified, its space is marked free, and it is prepended in a single linked list that resides in the page itself. So, each page has its own list of free slots to use.
+
+Pages are then on another linked list. This is a double linked list and at its beginning has the pages with free space and at the end the pages that are full.
+
+When the defragmenter is enabled the pages double linked list is also sorted, like this: the fewer the free slots on a page, the earlier in the linked list the page will be, except if it does not have any free slot, in which case it will be at the end. So, the defragmenter tries to have pages full.
+
+When a page is entirerly free, it is given back to the system immediately. There is no caching of free pages.
+
+
+Parallelism is achieved like this:
+
+When some threads are waiting for a page to be allocated, free operations are allowed. If a free operation happens before a new page is allocated, any waiting thread will get the slot that is freed on another page.
+
+Free operations happen in parallel, even for the same page. There is a spin-lock on each page to protect the base pointer of the page's free slots single linked list. But, this is instant. All preparative work happens lockless, then to add the free slot to the page, the page spinlock is acquired, the free slot is prepended to the linked list on the page, the spinlock is released. Such free operations on different pages are totally parallel.
+
+Once the free operation on a page has finished, the pages double linked list spinlock is acquired to put the page first on that linked list. If the defragmenter is enabled, the spinlock is retained for a little longer, to find the exact position of the page in the linked list.
+
+During allocations, the reverse order is used. First get the pages double linked list spinlock, get the first page and decrement its free slots counter, then release the spinlock. If the first page does not have any free slots, a page allocation is spawn, without any locks acquired. All threads are spinning waiting for a page with free slots, either from the newly allocated one or from a free operation that may happen in parallel.
+
+Once a page is acquired, each thread locks its own page to get the first free slot and releases the lock immediately. This is guaranteed to succeed, because when the page was given to that thread its free slots counter was decremented. So, there is a free slot for every thread that got that page. All preparative work to return a pointer to the caller is done lock free. Allocations on different pages are done in parallel, without any intervention between them.
+
+
+## What to expect
+
+Systems not designed for parallelism achieve their top performance single threaded. The single threaded speed is the baseline. Adding more threads makes them slower.
+
+The baseline for ARAL is the following, the included stress test when running single threaded:
+
+```
+Running stress test of 1 threads, with 10000 elements each, for 5 seconds...
+2023-01-29 17:04:50: netdata INFO : TH[0] : set name of thread 1314983 to TH[0]
+ARAL executes 12.27 M malloc and 12.26 M free operations/s
+ARAL executes 12.29 M malloc and 12.29 M free operations/s
+ARAL executes 12.30 M malloc and 12.30 M free operations/s
+ARAL executes 12.30 M malloc and 12.29 M free operations/s
+ARAL executes 12.29 M malloc and 12.29 M free operations/s
+Waiting the threads to finish...
+2023-01-29 17:04:55: netdata INFO : MAIN : ARAL: did 61487356 malloc, 61487356 free, using 1 threads, in 5003808 usecs
+```
+
+The same test with 2 threads, both threads on the same ARAL of course. As you see performance improved:
+
+```
+Running stress test of 2 threads, with 10000 elements each, for 5 seconds...
+2023-01-29 17:05:25: netdata INFO : TH[0] : set name of thread 1315537 to TH[0]
+2023-01-29 17:05:25: netdata INFO : TH[1] : set name of thread 1315538 to TH[1]
+ARAL executes 17.75 M malloc and 17.73 M free operations/s
+ARAL executes 17.93 M malloc and 17.93 M free operations/s
+ARAL executes 18.17 M malloc and 18.18 M free operations/s
+ARAL executes 18.33 M malloc and 18.32 M free operations/s
+ARAL executes 18.36 M malloc and 18.36 M free operations/s
+Waiting the threads to finish...
+2023-01-29 17:05:30: netdata INFO : MAIN : ARAL: did 90976190 malloc, 90976190 free, using 2 threads, in 5029462 usecs
+```
+
+The same test with 4 threads:
+
+```
+Running stress test of 4 threads, with 10000 elements each, for 5 seconds...
+2023-01-29 17:10:12: netdata INFO : TH[0] : set name of thread 1319552 to TH[0]
+2023-01-29 17:10:12: netdata INFO : TH[1] : set name of thread 1319553 to TH[1]
+2023-01-29 17:10:12: netdata INFO : TH[2] : set name of thread 1319554 to TH[2]
+2023-01-29 17:10:12: netdata INFO : TH[3] : set name of thread 1319555 to TH[3]
+ARAL executes 19.95 M malloc and 19.91 M free operations/s
+ARAL executes 20.08 M malloc and 20.08 M free operations/s
+ARAL executes 20.85 M malloc and 20.85 M free operations/s
+ARAL executes 20.84 M malloc and 20.84 M free operations/s
+ARAL executes 21.37 M malloc and 21.37 M free operations/s
+Waiting the threads to finish...
+2023-01-29 17:10:17: netdata INFO : MAIN : ARAL: did 103549747 malloc, 103549747 free, using 4 threads, in 5023325 usecs
+```
+
+The same with 8 threads:
+
+```
+Running stress test of 8 threads, with 10000 elements each, for 5 seconds...
+2023-01-29 17:07:06: netdata INFO : TH[0] : set name of thread 1317608 to TH[0]
+2023-01-29 17:07:06: netdata INFO : TH[1] : set name of thread 1317609 to TH[1]
+2023-01-29 17:07:06: netdata INFO : TH[2] : set name of thread 1317610 to TH[2]
+2023-01-29 17:07:06: netdata INFO : TH[3] : set name of thread 1317611 to TH[3]
+2023-01-29 17:07:06: netdata INFO : TH[4] : set name of thread 1317612 to TH[4]
+2023-01-29 17:07:06: netdata INFO : TH[5] : set name of thread 1317613 to TH[5]
+2023-01-29 17:07:06: netdata INFO : TH[6] : set name of thread 1317614 to TH[6]
+2023-01-29 17:07:06: netdata INFO : TH[7] : set name of thread 1317615 to TH[7]
+ARAL executes 15.73 M malloc and 15.66 M free operations/s
+ARAL executes 13.95 M malloc and 13.94 M free operations/s
+ARAL executes 15.59 M malloc and 15.58 M free operations/s
+ARAL executes 15.49 M malloc and 15.49 M free operations/s
+ARAL executes 16.16 M malloc and 16.16 M free operations/s
+Waiting the threads to finish...
+2023-01-29 17:07:11: netdata INFO : MAIN : ARAL: did 78427750 malloc, 78427750 free, using 8 threads, in 5088591 usecs
+```
+
+The same with 16 threads:
+
+```
+Running stress test of 16 threads, with 10000 elements each, for 5 seconds...
+2023-01-29 17:08:04: netdata INFO : TH[0] : set name of thread 1318663 to TH[0]
+2023-01-29 17:08:04: netdata INFO : TH[1] : set name of thread 1318664 to TH[1]
+2023-01-29 17:08:04: netdata INFO : TH[2] : set name of thread 1318665 to TH[2]
+2023-01-29 17:08:04: netdata INFO : TH[3] : set name of thread 1318666 to TH[3]
+2023-01-29 17:08:04: netdata INFO : TH[4] : set name of thread 1318667 to TH[4]
+2023-01-29 17:08:04: netdata INFO : TH[5] : set name of thread 1318668 to TH[5]
+2023-01-29 17:08:04: netdata INFO : TH[6] : set name of thread 1318669 to TH[6]
+2023-01-29 17:08:04: netdata INFO : TH[7] : set name of thread 1318670 to TH[7]
+2023-01-29 17:08:04: netdata INFO : TH[8] : set name of thread 1318671 to TH[8]
+2023-01-29 17:08:04: netdata INFO : TH[9] : set name of thread 1318672 to TH[9]
+2023-01-29 17:08:04: netdata INFO : TH[10] : set name of thread 1318673 to TH[10]
+2023-01-29 17:08:04: netdata INFO : TH[11] : set name of thread 1318674 to TH[11]
+2023-01-29 17:08:04: netdata INFO : TH[12] : set name of thread 1318675 to TH[12]
+2023-01-29 17:08:04: netdata INFO : TH[13] : set name of thread 1318676 to TH[13]
+2023-01-29 17:08:04: netdata INFO : TH[14] : set name of thread 1318677 to TH[14]
+2023-01-29 17:08:04: netdata INFO : TH[15] : set name of thread 1318678 to TH[15]
+ARAL executes 11.77 M malloc and 11.62 M free operations/s
+ARAL executes 12.80 M malloc and 12.81 M free operations/s
+ARAL executes 13.26 M malloc and 13.25 M free operations/s
+ARAL executes 13.30 M malloc and 13.29 M free operations/s
+ARAL executes 13.23 M malloc and 13.25 M free operations/s
+Waiting the threads to finish...
+2023-01-29 17:08:09: netdata INFO : MAIN : ARAL: did 65302122 malloc, 65302122 free, using 16 threads, in 5066009 usecs
+```
+
+As you can see, the top performance is with 4 threads, almost double the single thread speed.
+16 threads performance is still better than single threaded, despite the intense concurrency.
diff --git a/libnetdata/aral/aral.c b/libnetdata/aral/aral.c
new file mode 100644
index 00000000..4505ee0f
--- /dev/null
+++ b/libnetdata/aral/aral.c
@@ -0,0 +1,1081 @@
+#include "../libnetdata.h"
+#include "aral.h"
+
+#ifdef NETDATA_TRACE_ALLOCATIONS
+#define TRACE_ALLOCATIONS_FUNCTION_DEFINITION_PARAMS , const char *file, const char *function, size_t line
+#define TRACE_ALLOCATIONS_FUNCTION_CALL_PARAMS , file, function, line
+#else
+#define TRACE_ALLOCATIONS_FUNCTION_DEFINITION_PARAMS
+#define TRACE_ALLOCATIONS_FUNCTION_CALL_PARAMS
+#endif
+
+#define ARAL_FREE_PAGES_DELTA_TO_REARRANGE_LIST 5
+
+// max file size
+#define ARAL_MAX_PAGE_SIZE_MMAP (1*1024*1024*1024)
+
+// max malloc size
+// optimal at current versions of libc is up to 256k
+// ideal to have the same overhead as libc is 4k
+#define ARAL_MAX_PAGE_SIZE_MALLOC (65*1024)
+
+typedef struct aral_free {
+ size_t size;
+ struct aral_free *next;
+} ARAL_FREE;
+
+typedef struct aral_page {
+ size_t size; // the allocation size of the page
+ const char *filename;
+ uint8_t *data;
+
+ uint32_t free_elements_to_move_first;
+ uint32_t max_elements; // the number of elements that can fit on this page
+
+ struct {
+ uint32_t used_elements; // the number of used elements on this page
+ uint32_t free_elements; // the number of free elements on this page
+
+ struct aral_page *prev; // the prev page on the list
+ struct aral_page *next; // the next page on the list
+ } aral_lock;
+
+ struct {
+ SPINLOCK spinlock;
+ ARAL_FREE *list;
+ } free;
+
+} ARAL_PAGE;
+
+typedef enum {
+ ARAL_LOCKLESS = (1 << 0),
+ ARAL_DEFRAGMENT = (1 << 1),
+ ARAL_ALLOCATED_STATS = (1 << 2),
+} ARAL_OPTIONS;
+
+struct aral {
+ struct {
+ char name[ARAL_MAX_NAME + 1];
+
+ ARAL_OPTIONS options;
+
+ size_t element_size; // calculated to take into account ARAL overheads
+ size_t max_allocation_size; // calculated in bytes
+ size_t max_page_elements; // calculated
+ size_t page_ptr_offset; // calculated
+ size_t natural_page_size; // calculated
+
+ size_t initial_page_elements;
+ size_t requested_element_size;
+ size_t requested_max_page_size;
+
+ struct {
+ bool enabled;
+ const char *filename;
+ char **cache_dir;
+ } mmap;
+ } config;
+
+ struct {
+ SPINLOCK spinlock;
+ size_t file_number; // for mmap
+ struct aral_page *pages; // linked list of pages
+
+ size_t user_malloc_operations;
+ size_t user_free_operations;
+ size_t defragment_operations;
+ size_t defragment_linked_list_traversals;
+ } aral_lock;
+
+ struct {
+ SPINLOCK spinlock;
+ size_t allocating_elements; // currently allocating elements
+ size_t allocation_size; // current / next allocation size
+ } adders;
+
+ struct {
+ size_t allocators; // the number of threads currently trying to allocate memory
+ } atomic;
+
+ struct aral_statistics *stats;
+};
+
+size_t aral_structures_from_stats(struct aral_statistics *stats) {
+ return __atomic_load_n(&stats->structures.allocated_bytes, __ATOMIC_RELAXED);
+}
+
+size_t aral_overhead_from_stats(struct aral_statistics *stats) {
+ return __atomic_load_n(&stats->malloc.allocated_bytes, __ATOMIC_RELAXED) -
+ __atomic_load_n(&stats->malloc.used_bytes, __ATOMIC_RELAXED);
+}
+
+size_t aral_overhead(ARAL *ar) {
+ return aral_overhead_from_stats(ar->stats);
+}
+
+size_t aral_structures(ARAL *ar) {
+ return aral_structures_from_stats(ar->stats);
+}
+
+struct aral_statistics *aral_statistics(ARAL *ar) {
+ return ar->stats;
+}
+
+#define ARAL_NATURAL_ALIGNMENT (sizeof(uintptr_t) * 2)
+static inline size_t natural_alignment(size_t size, size_t alignment) {
+ if(unlikely(size % alignment))
+ size = size + alignment - (size % alignment);
+
+ return size;
+}
+
+static size_t aral_align_alloc_size(ARAL *ar, uint64_t size) {
+ if(size % ar->config.natural_page_size)
+ size += ar->config.natural_page_size - (size % ar->config.natural_page_size) ;
+
+ if(size % ar->config.element_size)
+ size -= size % ar->config.element_size;
+
+ return size;
+}
+
+static inline void aral_lock(ARAL *ar) {
+ if(likely(!(ar->config.options & ARAL_LOCKLESS)))
+ netdata_spinlock_lock(&ar->aral_lock.spinlock);
+}
+
+static inline void aral_unlock(ARAL *ar) {
+ if(likely(!(ar->config.options & ARAL_LOCKLESS)))
+ netdata_spinlock_unlock(&ar->aral_lock.spinlock);
+}
+
+static inline void aral_page_free_lock(ARAL *ar, ARAL_PAGE *page) {
+ if(likely(!(ar->config.options & ARAL_LOCKLESS)))
+ netdata_spinlock_lock(&page->free.spinlock);
+}
+
+static inline void aral_page_free_unlock(ARAL *ar, ARAL_PAGE *page) {
+ if(likely(!(ar->config.options & ARAL_LOCKLESS)))
+ netdata_spinlock_unlock(&page->free.spinlock);
+}
+
+static inline bool aral_adders_trylock(ARAL *ar) {
+ if(likely(!(ar->config.options & ARAL_LOCKLESS)))
+ return netdata_spinlock_trylock(&ar->adders.spinlock);
+
+ return true;
+}
+
+static inline void aral_adders_lock(ARAL *ar) {
+ if(likely(!(ar->config.options & ARAL_LOCKLESS)))
+ netdata_spinlock_lock(&ar->adders.spinlock);
+}
+
+static inline void aral_adders_unlock(ARAL *ar) {
+ if(likely(!(ar->config.options & ARAL_LOCKLESS)))
+ netdata_spinlock_unlock(&ar->adders.spinlock);
+}
+
+static void aral_delete_leftover_files(const char *name, const char *path, const char *required_prefix) {
+ DIR *dir = opendir(path);
+ if(!dir) return;
+
+ char full_path[FILENAME_MAX + 1];
+ size_t len = strlen(required_prefix);
+
+ struct dirent *de = NULL;
+ while((de = readdir(dir))) {
+ if(de->d_type == DT_DIR)
+ continue;
+
+ if(strncmp(de->d_name, required_prefix, len) != 0)
+ continue;
+
+ snprintfz(full_path, FILENAME_MAX, "%s/%s", path, de->d_name);
+ info("ARAL: '%s' removing left-over file '%s'", name, full_path);
+ if(unlikely(unlink(full_path) == -1))
+ error("ARAL: '%s' cannot delete file '%s'", name, full_path);
+ }
+
+ closedir(dir);
+}
+
+// ----------------------------------------------------------------------------
+// check a free slot
+
+#ifdef NETDATA_INTERNAL_CHECKS
+static inline void aral_free_validate_internal_check(ARAL *ar, ARAL_FREE *fr) {
+ if(unlikely(fr->size < ar->config.element_size))
+ fatal("ARAL: '%s' free item of size %zu, less than the expected element size %zu",
+ ar->config.name, fr->size, ar->config.element_size);
+
+ if(unlikely(fr->size % ar->config.element_size))
+ fatal("ARAL: '%s' free item of size %zu is not multiple to element size %zu",
+ ar->config.name, fr->size, ar->config.element_size);
+}
+#else
+#define aral_free_validate_internal_check(ar, fr) debug_dummy()
+#endif
+
+// ----------------------------------------------------------------------------
+// find the page a pointer belongs to
+
+#ifdef NETDATA_INTERNAL_CHECKS
+static inline ARAL_PAGE *find_page_with_allocation_internal_check(ARAL *ar, void *ptr) {
+ aral_lock(ar);
+
+ uintptr_t seeking = (uintptr_t)ptr;
+ ARAL_PAGE *page;
+
+ for(page = ar->aral_lock.pages; page ; page = page->aral_lock.next) {
+ if(unlikely(seeking >= (uintptr_t)page->data && seeking < (uintptr_t)page->data + page->size))
+ break;
+ }
+
+ aral_unlock(ar);
+
+ return page;
+}
+#endif
+
+// ----------------------------------------------------------------------------
+// find a page with a free slot (there shouldn't be any)
+
+#ifdef NETDATA_ARAL_INTERNAL_CHECKS
+static inline ARAL_PAGE *find_page_with_free_slots_internal_check___with_aral_lock(ARAL *ar) {
+ ARAL_PAGE *page;
+
+ for(page = ar->aral_lock.pages; page ; page = page->next) {
+ if(page->aral_lock.free_elements)
+ break;
+
+ internal_fatal(page->size - page->aral_lock.used_elements * ar->config.element_size >= ar->config.element_size,
+ "ARAL: '%s' a page is marked full, but it is not!", ar->config.name);
+
+ internal_fatal(page->size < page->aral_lock.used_elements * ar->config.element_size,
+ "ARAL: '%s' a page has been overflown!", ar->config.name);
+ }
+
+ return page;
+}
+#endif
+
+size_t aral_next_allocation_size___adders_lock_needed(ARAL *ar) {
+ size_t size = ar->adders.allocation_size;
+
+ if(size > ar->config.max_allocation_size)
+ size = ar->config.max_allocation_size;
+ else
+ ar->adders.allocation_size = aral_align_alloc_size(ar, (uint64_t)ar->adders.allocation_size * 2);
+
+ return size;
+}
+
+static ARAL_PAGE *aral_create_page___no_lock_needed(ARAL *ar, size_t size TRACE_ALLOCATIONS_FUNCTION_DEFINITION_PARAMS) {
+ ARAL_PAGE *page = callocz(1, sizeof(ARAL_PAGE));
+ netdata_spinlock_init(&page->free.spinlock);
+ page->size = size;
+ page->max_elements = page->size / ar->config.element_size;
+ page->aral_lock.free_elements = page->max_elements;
+ page->free_elements_to_move_first = page->max_elements / 4;
+ if(unlikely(page->free_elements_to_move_first < 1))
+ page->free_elements_to_move_first = 1;
+
+ __atomic_add_fetch(&ar->stats->structures.allocations, 1, __ATOMIC_RELAXED);
+ __atomic_add_fetch(&ar->stats->structures.allocated_bytes, sizeof(ARAL_PAGE), __ATOMIC_RELAXED);
+
+ if(unlikely(ar->config.mmap.enabled)) {
+ ar->aral_lock.file_number++;
+ char filename[FILENAME_MAX + 1];
+ snprintfz(filename, FILENAME_MAX, "%s/array_alloc.mmap/%s.%zu", *ar->config.mmap.cache_dir, ar->config.mmap.filename, ar->aral_lock.file_number);
+ page->filename = strdupz(filename);
+ page->data = netdata_mmap(page->filename, page->size, MAP_SHARED, 0, false, NULL);
+ if (unlikely(!page->data))
+ fatal("ARAL: '%s' cannot allocate aral buffer of size %zu on filename '%s'",
+ ar->config.name, page->size, page->filename);
+ __atomic_add_fetch(&ar->stats->mmap.allocations, 1, __ATOMIC_RELAXED);
+ __atomic_add_fetch(&ar->stats->mmap.allocated_bytes, page->size, __ATOMIC_RELAXED);
+ }
+ else {
+#ifdef NETDATA_TRACE_ALLOCATIONS
+ page->data = mallocz_int(page->size TRACE_ALLOCATIONS_FUNCTION_CALL_PARAMS);
+#else
+ page->data = mallocz(page->size);
+#endif
+ __atomic_add_fetch(&ar->stats->malloc.allocations, 1, __ATOMIC_RELAXED);
+ __atomic_add_fetch(&ar->stats->malloc.allocated_bytes, page->size, __ATOMIC_RELAXED);
+ }
+
+ // link the free space to its page
+ ARAL_FREE *fr = (ARAL_FREE *)page->data;
+ fr->size = page->size;
+ fr->next = NULL;
+ page->free.list = fr;
+
+ aral_free_validate_internal_check(ar, fr);
+
+ return page;
+}
+
+void aral_del_page___no_lock_needed(ARAL *ar, ARAL_PAGE *page TRACE_ALLOCATIONS_FUNCTION_DEFINITION_PARAMS) {
+
+ // free it
+ if (ar->config.mmap.enabled) {
+ netdata_munmap(page->data, page->size);
+
+ if (unlikely(unlink(page->filename) == 1))
+ error("Cannot delete file '%s'", page->filename);
+
+ freez((void *)page->filename);
+
+ __atomic_sub_fetch(&ar->stats->mmap.allocations, 1, __ATOMIC_RELAXED);
+ __atomic_sub_fetch(&ar->stats->mmap.allocated_bytes, page->size, __ATOMIC_RELAXED);
+ }
+ else {
+#ifdef NETDATA_TRACE_ALLOCATIONS
+ freez_int(page->data TRACE_ALLOCATIONS_FUNCTION_CALL_PARAMS);
+#else
+ freez(page->data);
+#endif
+ __atomic_sub_fetch(&ar->stats->malloc.allocations, 1, __ATOMIC_RELAXED);
+ __atomic_sub_fetch(&ar->stats->malloc.allocated_bytes, page->size, __ATOMIC_RELAXED);
+ }
+
+ freez(page);
+
+ __atomic_sub_fetch(&ar->stats->structures.allocations, 1, __ATOMIC_RELAXED);
+ __atomic_sub_fetch(&ar->stats->structures.allocated_bytes, sizeof(ARAL_PAGE), __ATOMIC_RELAXED);
+}
+
+static inline void aral_insert_not_linked_page_with_free_items_to_proper_position___aral_lock_needed(ARAL *ar, ARAL_PAGE *page) {
+ ARAL_PAGE *first = ar->aral_lock.pages;
+
+ if (page->aral_lock.free_elements <= page->free_elements_to_move_first ||
+ !first ||
+ !first->aral_lock.free_elements ||
+ page->aral_lock.free_elements <= first->aral_lock.free_elements + ARAL_FREE_PAGES_DELTA_TO_REARRANGE_LIST) {
+ // first position
+ DOUBLE_LINKED_LIST_PREPEND_ITEM_UNSAFE(ar->aral_lock.pages, page, aral_lock.prev, aral_lock.next);
+ }
+ else {
+ ARAL_PAGE *second = first->aral_lock.next;
+
+ if (!second ||
+ !second->aral_lock.free_elements ||
+ page->aral_lock.free_elements <= second->aral_lock.free_elements)
+ // second position
+ DOUBLE_LINKED_LIST_INSERT_ITEM_AFTER_UNSAFE(ar->aral_lock.pages, first, page, aral_lock.prev, aral_lock.next);
+ else
+ // third position
+ DOUBLE_LINKED_LIST_INSERT_ITEM_AFTER_UNSAFE(ar->aral_lock.pages, second, page, aral_lock.prev, aral_lock.next);
+ }
+}
+
+static inline ARAL_PAGE *aral_acquire_a_free_slot(ARAL *ar TRACE_ALLOCATIONS_FUNCTION_DEFINITION_PARAMS) {
+ __atomic_add_fetch(&ar->atomic.allocators, 1, __ATOMIC_RELAXED);
+ aral_lock(ar);
+
+ ARAL_PAGE *page = ar->aral_lock.pages;
+
+ while(!page || !page->aral_lock.free_elements) {
+#ifdef NETDATA_ARAL_INTERNAL_CHECKS
+ internal_fatal(find_page_with_free_slots_internal_check___with_aral_lock(ar), "ARAL: '%s' found page with free slot!", ar->config.name);
+#endif
+ aral_unlock(ar);
+
+ if(aral_adders_trylock(ar)) {
+ if(ar->adders.allocating_elements < __atomic_load_n(&ar->atomic.allocators, __ATOMIC_RELAXED)) {
+
+ size_t size = aral_next_allocation_size___adders_lock_needed(ar);
+ ar->adders.allocating_elements += size / ar->config.element_size;
+ aral_adders_unlock(ar);
+
+ page = aral_create_page___no_lock_needed(ar, size TRACE_ALLOCATIONS_FUNCTION_CALL_PARAMS);
+
+ aral_lock(ar);
+ aral_insert_not_linked_page_with_free_items_to_proper_position___aral_lock_needed(ar, page);
+
+ aral_adders_lock(ar);
+ ar->adders.allocating_elements -= size / ar->config.element_size;
+ aral_adders_unlock(ar);
+
+ // we have a page that is all empty
+ // and only aral_lock() is held, so
+ // break the loop
+ break;
+ }
+
+ aral_adders_unlock(ar);
+ }
+
+ aral_lock(ar);
+ page = ar->aral_lock.pages;
+ }
+
+ __atomic_sub_fetch(&ar->atomic.allocators, 1, __ATOMIC_RELAXED);
+
+ // we have a page
+ // and aral locked
+
+ {
+ ARAL_PAGE *first = ar->aral_lock.pages;
+ ARAL_PAGE *second = first->aral_lock.next;
+
+ if (!second ||
+ !second->aral_lock.free_elements ||
+ first->aral_lock.free_elements <= second->aral_lock.free_elements + ARAL_FREE_PAGES_DELTA_TO_REARRANGE_LIST)
+ page = first;
+ else {
+ DOUBLE_LINKED_LIST_REMOVE_ITEM_UNSAFE(ar->aral_lock.pages, second, aral_lock.prev, aral_lock.next);
+ DOUBLE_LINKED_LIST_PREPEND_ITEM_UNSAFE(ar->aral_lock.pages, second, aral_lock.prev, aral_lock.next);
+ page = second;
+ }
+ }
+
+ internal_fatal(!page || !page->aral_lock.free_elements,
+ "ARAL: '%s' selected page does not have a free slot in it",
+ ar->config.name);
+
+ internal_fatal(page->max_elements != page->aral_lock.used_elements + page->aral_lock.free_elements,
+ "ARAL: '%s' page element counters do not match, "
+ "page says it can handle %zu elements, "
+ "but there are %zu used and %zu free items, "
+ "total %zu items",
+ ar->config.name,
+ (size_t)page->max_elements,
+ (size_t)page->aral_lock.used_elements, (size_t)page->aral_lock.free_elements,
+ (size_t)page->aral_lock.used_elements + (size_t)page->aral_lock.free_elements
+ );
+
+ ar->aral_lock.user_malloc_operations++;
+
+ // acquire a slot for the caller
+ page->aral_lock.used_elements++;
+ if(--page->aral_lock.free_elements == 0) {
+ // we are done with this page
+ // move the full page last
+ // so that pages with free items remain first in the list
+ DOUBLE_LINKED_LIST_REMOVE_ITEM_UNSAFE(ar->aral_lock.pages, page, aral_lock.prev, aral_lock.next);
+ DOUBLE_LINKED_LIST_APPEND_ITEM_UNSAFE(ar->aral_lock.pages, page, aral_lock.prev, aral_lock.next);
+ }
+
+ aral_unlock(ar);
+
+ return page;
+}
+
+void *aral_mallocz_internal(ARAL *ar TRACE_ALLOCATIONS_FUNCTION_DEFINITION_PARAMS) {
+
+ ARAL_PAGE *page = aral_acquire_a_free_slot(ar TRACE_ALLOCATIONS_FUNCTION_CALL_PARAMS);
+
+ aral_page_free_lock(ar, page);
+
+ internal_fatal(!page->free.list,
+ "ARAL: '%s' free item to use, cannot be NULL.", ar->config.name);
+
+ internal_fatal(page->free.list->size < ar->config.element_size,
+ "ARAL: '%s' free item size %zu, cannot be smaller than %zu",
+ ar->config.name, page->free.list->size, ar->config.element_size);
+
+ ARAL_FREE *found_fr = page->free.list;
+
+ // check if the remaining size (after we use this slot) is not enough for another element
+ if(unlikely(found_fr->size - ar->config.element_size < ar->config.element_size)) {
+ // we can use the entire free space entry
+
+ page->free.list = found_fr->next;
+ }
+ else {
+ // we can split the free space entry
+
+ uint8_t *data = (uint8_t *)found_fr;
+ ARAL_FREE *fr = (ARAL_FREE *)&data[ar->config.element_size];
+ fr->size = found_fr->size - ar->config.element_size;
+
+ // link the free slot first in the page
+ fr->next = found_fr->next;
+ page->free.list = fr;
+
+ aral_free_validate_internal_check(ar, fr);
+ }
+
+ aral_page_free_unlock(ar, page);
+
+ // put the page pointer after the element
+ uint8_t *data = (uint8_t *)found_fr;
+ ARAL_PAGE **page_ptr = (ARAL_PAGE **)&data[ar->config.page_ptr_offset];
+ *page_ptr = page;
+
+ if(unlikely(ar->config.mmap.enabled))
+ __atomic_add_fetch(&ar->stats->mmap.used_bytes, ar->config.element_size, __ATOMIC_RELAXED);
+ else
+ __atomic_add_fetch(&ar->stats->malloc.used_bytes, ar->config.element_size, __ATOMIC_RELAXED);
+
+ return (void *)found_fr;
+}
+
+static inline ARAL_PAGE *aral_ptr_to_page___must_NOT_have_aral_lock(ARAL *ar, void *ptr) {
+ // given a data pointer we returned before,
+ // find the ARAL_PAGE it belongs to
+
+ uint8_t *data = (uint8_t *)ptr;
+ ARAL_PAGE **page_ptr = (ARAL_PAGE **)&data[ar->config.page_ptr_offset];
+ ARAL_PAGE *page = *page_ptr;
+
+#ifdef NETDATA_INTERNAL_CHECKS
+ // make it NULL so that we will fail on double free
+ // do not enable this on production, because the MMAP file
+ // will need to be saved again!
+ *page_ptr = NULL;
+#endif
+
+#ifdef NETDATA_ARAL_INTERNAL_CHECKS
+ {
+ // find the page ptr belongs
+ ARAL_PAGE *page2 = find_page_with_allocation_internal_check(ar, ptr);
+
+ internal_fatal(page != page2,
+ "ARAL: '%s' page pointers do not match!",
+ ar->name);
+
+ internal_fatal(!page2,
+ "ARAL: '%s' free of pointer %p is not in ARAL address space.",
+ ar->name, ptr);
+ }
+#endif
+
+ internal_fatal(!page,
+ "ARAL: '%s' possible corruption or double free of pointer %p",
+ ar->config.name, ptr);
+
+ return page;
+}
+
+static void aral_defrag_sorted_page_position___aral_lock_needed(ARAL *ar, ARAL_PAGE *page) {
+ ARAL_PAGE *tmp;
+
+ int action = 0; (void)action;
+ size_t move_later = 0, move_earlier = 0;
+
+ for(tmp = page->aral_lock.next ;
+ tmp && tmp->aral_lock.free_elements && tmp->aral_lock.free_elements < page->aral_lock.free_elements ;
+ tmp = tmp->aral_lock.next)
+ move_later++;
+
+ if(!tmp && page->aral_lock.next) {
+ DOUBLE_LINKED_LIST_REMOVE_ITEM_UNSAFE(ar->aral_lock.pages, page, aral_lock.prev, aral_lock.next);
+ DOUBLE_LINKED_LIST_APPEND_ITEM_UNSAFE(ar->aral_lock.pages, page, aral_lock.prev, aral_lock.next);
+ action = 1;
+ }
+ else if(tmp != page->aral_lock.next) {
+ DOUBLE_LINKED_LIST_REMOVE_ITEM_UNSAFE(ar->aral_lock.pages, page, aral_lock.prev, aral_lock.next);
+ DOUBLE_LINKED_LIST_INSERT_ITEM_BEFORE_UNSAFE(ar->aral_lock.pages, tmp, page, aral_lock.prev, aral_lock.next);
+ action = 2;
+ }
+ else {
+ for(tmp = (page == ar->aral_lock.pages) ? NULL : page->aral_lock.prev ;
+ tmp && (!tmp->aral_lock.free_elements || tmp->aral_lock.free_elements > page->aral_lock.free_elements);
+ tmp = (tmp == ar->aral_lock.pages) ? NULL : tmp->aral_lock.prev)
+ move_earlier++;
+
+ if(!tmp) {
+ DOUBLE_LINKED_LIST_REMOVE_ITEM_UNSAFE(ar->aral_lock.pages, page, aral_lock.prev, aral_lock.next);
+ DOUBLE_LINKED_LIST_PREPEND_ITEM_UNSAFE(ar->aral_lock.pages, page, aral_lock.prev, aral_lock.next);
+ action = 3;
+ }
+ else if(tmp != page->aral_lock.prev){
+ DOUBLE_LINKED_LIST_REMOVE_ITEM_UNSAFE(ar->aral_lock.pages, page, aral_lock.prev, aral_lock.next);
+ DOUBLE_LINKED_LIST_INSERT_ITEM_AFTER_UNSAFE(ar->aral_lock.pages, tmp, page, aral_lock.prev, aral_lock.next);
+ action = 4;
+ }
+ }
+
+ ar->aral_lock.defragment_operations++;
+ ar->aral_lock.defragment_linked_list_traversals += move_earlier + move_later;
+
+ internal_fatal(page->aral_lock.next && page->aral_lock.next->aral_lock.free_elements && page->aral_lock.next->aral_lock.free_elements < page->aral_lock.free_elements,
+ "ARAL: '%s' item should be later in the list", ar->config.name);
+
+ internal_fatal(page != ar->aral_lock.pages && (!page->aral_lock.prev->aral_lock.free_elements || page->aral_lock.prev->aral_lock.free_elements > page->aral_lock.free_elements),
+ "ARAL: '%s' item should be earlier in the list", ar->config.name);
+}
+
+static inline void aral_move_page_with_free_list___aral_lock_needed(ARAL *ar, ARAL_PAGE *page) {
+ if(unlikely(page == ar->aral_lock.pages))
+ // we are the first already
+ return;
+
+ if(likely(!(ar->config.options & ARAL_DEFRAGMENT))) {
+ DOUBLE_LINKED_LIST_REMOVE_ITEM_UNSAFE(ar->aral_lock.pages, page, aral_lock.prev, aral_lock.next);
+ aral_insert_not_linked_page_with_free_items_to_proper_position___aral_lock_needed(ar, page);
+ }
+ else
+ aral_defrag_sorted_page_position___aral_lock_needed(ar, page);
+}
+
+void aral_freez_internal(ARAL *ar, void *ptr TRACE_ALLOCATIONS_FUNCTION_DEFINITION_PARAMS) {
+ if(unlikely(!ptr)) return;
+
+ // get the page pointer
+ ARAL_PAGE *page = aral_ptr_to_page___must_NOT_have_aral_lock(ar, ptr);
+
+ if(unlikely(ar->config.mmap.enabled))
+ __atomic_sub_fetch(&ar->stats->mmap.used_bytes, ar->config.element_size, __ATOMIC_RELAXED);
+ else
+ __atomic_sub_fetch(&ar->stats->malloc.used_bytes, ar->config.element_size, __ATOMIC_RELAXED);
+
+ // make this element available
+ ARAL_FREE *fr = (ARAL_FREE *)ptr;
+ fr->size = ar->config.element_size;
+
+ aral_page_free_lock(ar, page);
+ fr->next = page->free.list;
+ page->free.list = fr;
+ aral_page_free_unlock(ar, page);
+
+ aral_lock(ar);
+
+ internal_fatal(!page->aral_lock.used_elements,
+ "ARAL: '%s' pointer %p is inside a page without any active allocations.",
+ ar->config.name, ptr);
+
+ internal_fatal(page->max_elements != page->aral_lock.used_elements + page->aral_lock.free_elements,
+ "ARAL: '%s' page element counters do not match, "
+ "page says it can handle %zu elements, "
+ "but there are %zu used and %zu free items, "
+ "total %zu items",
+ ar->config.name,
+ (size_t)page->max_elements,
+ (size_t)page->aral_lock.used_elements, (size_t)page->aral_lock.free_elements,
+ (size_t)page->aral_lock.used_elements + (size_t)page->aral_lock.free_elements
+ );
+
+ page->aral_lock.used_elements--;
+ page->aral_lock.free_elements++;
+
+ ar->aral_lock.user_free_operations++;
+
+ // if the page is empty, release it
+ if(unlikely(!page->aral_lock.used_elements)) {
+ bool is_this_page_the_last_one = ar->aral_lock.pages == page && !page->aral_lock.next;
+
+ if(!is_this_page_the_last_one)
+ DOUBLE_LINKED_LIST_REMOVE_ITEM_UNSAFE(ar->aral_lock.pages, page, aral_lock.prev, aral_lock.next);
+
+ aral_unlock(ar);
+
+ if(!is_this_page_the_last_one)
+ aral_del_page___no_lock_needed(ar, page TRACE_ALLOCATIONS_FUNCTION_CALL_PARAMS);
+ }
+ else {
+ aral_move_page_with_free_list___aral_lock_needed(ar, page);
+ aral_unlock(ar);
+ }
+}
+
+void aral_destroy_internal(ARAL *ar TRACE_ALLOCATIONS_FUNCTION_DEFINITION_PARAMS) {
+ aral_lock(ar);
+
+ ARAL_PAGE *page;
+ while((page = ar->aral_lock.pages)) {
+ DOUBLE_LINKED_LIST_REMOVE_ITEM_UNSAFE(ar->aral_lock.pages, page, aral_lock.prev, aral_lock.next);
+ aral_del_page___no_lock_needed(ar, page TRACE_ALLOCATIONS_FUNCTION_CALL_PARAMS);
+ }
+
+ aral_unlock(ar);
+
+ if(ar->config.options & ARAL_ALLOCATED_STATS)
+ freez(ar->stats);
+
+ freez(ar);
+}
+
+size_t aral_element_size(ARAL *ar) {
+ return ar->config.requested_element_size;
+}
+
+ARAL *aral_create(const char *name, size_t element_size, size_t initial_page_elements, size_t max_page_size,
+ struct aral_statistics *stats, const char *filename, char **cache_dir, bool mmap, bool lockless) {
+ ARAL *ar = callocz(1, sizeof(ARAL));
+ ar->config.options = (lockless) ? ARAL_LOCKLESS : 0;
+ ar->config.requested_element_size = element_size;
+ ar->config.initial_page_elements = initial_page_elements;
+ ar->config.requested_max_page_size = max_page_size;
+ ar->config.mmap.filename = filename;
+ ar->config.mmap.cache_dir = cache_dir;
+ ar->config.mmap.enabled = mmap;
+ strncpyz(ar->config.name, name, ARAL_MAX_NAME);
+ netdata_spinlock_init(&ar->aral_lock.spinlock);
+
+ if(stats) {
+ ar->stats = stats;
+ ar->config.options &= ~ARAL_ALLOCATED_STATS;
+ }
+ else {
+ ar->stats = callocz(1, sizeof(struct aral_statistics));
+ ar->config.options |= ARAL_ALLOCATED_STATS;
+ }
+
+ long int page_size = sysconf(_SC_PAGE_SIZE);
+ if (unlikely(page_size == -1))
+ ar->config.natural_page_size = 4096;
+ else
+ ar->config.natural_page_size = page_size;
+
+ // we need to add a page pointer after the element
+ // so, first align the element size to the pointer size
+ ar->config.element_size = natural_alignment(ar->config.requested_element_size, sizeof(uintptr_t));
+
+ // then add the size of a pointer to it
+ ar->config.element_size += sizeof(uintptr_t);
+
+ // make sure it is at least what we need for an ARAL_FREE slot
+ if (ar->config.element_size < sizeof(ARAL_FREE))
+ ar->config.element_size = sizeof(ARAL_FREE);
+
+ // and finally align it to the natural alignment
+ ar->config.element_size = natural_alignment(ar->config.element_size, ARAL_NATURAL_ALIGNMENT);
+
+ ar->config.max_page_elements = ar->config.requested_max_page_size / ar->config.element_size;
+
+ // we write the page pointer just after each element
+ ar->config.page_ptr_offset = ar->config.element_size - sizeof(uintptr_t);
+
+ if(ar->config.requested_element_size + sizeof(uintptr_t) > ar->config.element_size)
+ fatal("ARAL: '%s' failed to calculate properly page_ptr_offset: "
+ "element size %zu, sizeof(uintptr_t) %zu, natural alignment %zu, "
+ "final element size %zu, page_ptr_offset %zu",
+ ar->config.name, ar->config.requested_element_size, sizeof(uintptr_t), ARAL_NATURAL_ALIGNMENT,
+ ar->config.element_size, ar->config.page_ptr_offset);
+
+ //info("ARAL: element size %zu, sizeof(uintptr_t) %zu, natural alignment %zu, final element size %zu, page_ptr_offset %zu",
+ // ar->element_size, sizeof(uintptr_t), ARAL_NATURAL_ALIGNMENT, ar->internal.element_size, ar->internal.page_ptr_offset);
+
+
+ if (ar->config.initial_page_elements < 2)
+ ar->config.initial_page_elements = 2;
+
+ if(ar->config.mmap.enabled && (!ar->config.mmap.cache_dir || !*ar->config.mmap.cache_dir)) {
+ error("ARAL: '%s' mmap cache directory is not configured properly, disabling mmap.", ar->config.name);
+ ar->config.mmap.enabled = false;
+ internal_fatal(true, "ARAL: '%s' mmap cache directory is not configured properly", ar->config.name);
+ }
+
+ uint64_t max_alloc_size;
+ if(!ar->config.max_page_elements)
+ max_alloc_size = ar->config.mmap.enabled ? ARAL_MAX_PAGE_SIZE_MMAP : ARAL_MAX_PAGE_SIZE_MALLOC;
+ else
+ max_alloc_size = ar->config.max_page_elements * ar->config.element_size;
+
+ ar->config.max_allocation_size = aral_align_alloc_size(ar, max_alloc_size);
+ ar->adders.allocation_size = aral_align_alloc_size(ar, (uint64_t)ar->config.element_size * ar->config.initial_page_elements);
+ ar->aral_lock.pages = NULL;
+ ar->aral_lock.file_number = 0;
+
+ if(ar->config.mmap.enabled) {
+ char directory_name[FILENAME_MAX + 1];
+ snprintfz(directory_name, FILENAME_MAX, "%s/array_alloc.mmap", *ar->config.mmap.cache_dir);
+ int r = mkdir(directory_name, 0775);
+ if (r != 0 && errno != EEXIST)
+ fatal("Cannot create directory '%s'", directory_name);
+
+ char file[FILENAME_MAX + 1];
+ snprintfz(file, FILENAME_MAX, "%s.", ar->config.mmap.filename);
+ aral_delete_leftover_files(ar->config.name, directory_name, file);
+ }
+
+ internal_error(true,
+ "ARAL: '%s' "
+ "element size %zu (requested %zu bytes), "
+ "min elements per page %zu (requested %zu), "
+ "max elements per page %zu, "
+ "max page size %zu bytes (requested %zu) "
+ , ar->config.name
+ , ar->config.element_size, ar->config.requested_element_size
+ , ar->adders.allocation_size / ar->config.element_size, ar->config.initial_page_elements
+ , ar->config.max_allocation_size / ar->config.element_size
+ , ar->config.max_allocation_size, ar->config.requested_max_page_size
+ );
+
+ __atomic_add_fetch(&ar->stats->structures.allocations, 1, __ATOMIC_RELAXED);
+ __atomic_add_fetch(&ar->stats->structures.allocated_bytes, sizeof(ARAL), __ATOMIC_RELAXED);
+ return ar;
+}
+
+// ----------------------------------------------------------------------------
+// global aral caching
+
+#define ARAL_BY_SIZE_MAX_SIZE 1024
+
+struct aral_by_size {
+ ARAL *ar;
+ int32_t refcount;
+};
+
+struct {
+ struct aral_statistics shared_statistics;
+ SPINLOCK spinlock;
+ struct aral_by_size array[ARAL_BY_SIZE_MAX_SIZE + 1];
+} aral_by_size_globals = {};
+
+struct aral_statistics *aral_by_size_statistics(void) {
+ return &aral_by_size_globals.shared_statistics;
+}
+
+size_t aral_by_size_structures(void) {
+ return aral_structures_from_stats(&aral_by_size_globals.shared_statistics);
+}
+
+size_t aral_by_size_overhead(void) {
+ return aral_overhead_from_stats(&aral_by_size_globals.shared_statistics);
+}
+
+ARAL *aral_by_size_acquire(size_t size) {
+ netdata_spinlock_lock(&aral_by_size_globals.spinlock);
+
+ ARAL *ar = NULL;
+
+ if(size <= ARAL_BY_SIZE_MAX_SIZE && aral_by_size_globals.array[size].ar) {
+ ar = aral_by_size_globals.array[size].ar;
+ aral_by_size_globals.array[size].refcount++;
+
+ internal_fatal(aral_element_size(ar) != size, "DICTIONARY: aral has size %zu but we want %zu",
+ aral_element_size(ar), size);
+ }
+
+ if(!ar) {
+ char buf[30 + 1];
+ snprintf(buf, 30, "size-%zu", size);
+ ar = aral_create(buf,
+ size,
+ 0,
+ 65536 * ((size / 150) + 1),
+ &aral_by_size_globals.shared_statistics,
+ NULL, NULL, false, false);
+
+ if(size <= ARAL_BY_SIZE_MAX_SIZE) {
+ aral_by_size_globals.array[size].ar = ar;
+ aral_by_size_globals.array[size].refcount = 1;
+ }
+ }
+
+ netdata_spinlock_unlock(&aral_by_size_globals.spinlock);
+
+ return ar;
+}
+
+void aral_by_size_release(ARAL *ar) {
+ size_t size = aral_element_size(ar);
+
+ if(size <= ARAL_BY_SIZE_MAX_SIZE) {
+ netdata_spinlock_lock(&aral_by_size_globals.spinlock);
+
+ internal_fatal(aral_by_size_globals.array[size].ar != ar,
+ "ARAL BY SIZE: aral pointers do not match");
+
+ if(aral_by_size_globals.array[size].refcount <= 0)
+ fatal("ARAL BY SIZE: double release detected");
+
+ aral_by_size_globals.array[size].refcount--;
+ if(!aral_by_size_globals.array[size].refcount) {
+ aral_destroy(aral_by_size_globals.array[size].ar);
+ aral_by_size_globals.array[size].ar = NULL;
+ }
+
+ netdata_spinlock_unlock(&aral_by_size_globals.spinlock);
+ }
+ else
+ aral_destroy(ar);
+}
+
+// ----------------------------------------------------------------------------
+// unittest
+
+struct aral_unittest_config {
+ bool single_threaded;
+ bool stop;
+ ARAL *ar;
+ size_t elements;
+ size_t threads;
+ int errors;
+};
+
+static void *aral_test_thread(void *ptr) {
+ struct aral_unittest_config *auc = ptr;
+ ARAL *ar = auc->ar;
+ size_t elements = auc->elements;
+
+ void **pointers = callocz(elements, sizeof(void *));
+
+ do {
+ for (size_t i = 0; i < elements; i++) {
+ pointers[i] = aral_mallocz(ar);
+ }
+
+ for (size_t div = 5; div >= 2; div--) {
+ for (size_t i = 0; i < elements / div; i++) {
+ aral_freez(ar, pointers[i]);
+ pointers[i] = NULL;
+ }
+
+ for (size_t i = 0; i < elements / div; i++) {
+ pointers[i] = aral_mallocz(ar);
+ }
+ }
+
+ for (size_t step = 50; step >= 10; step -= 10) {
+ for (size_t i = 0; i < elements; i += step) {
+ aral_freez(ar, pointers[i]);
+ pointers[i] = NULL;
+ }
+
+ for (size_t i = 0; i < elements; i += step) {
+ pointers[i] = aral_mallocz(ar);
+ }
+ }
+
+ for (size_t i = 0; i < elements; i++) {
+ aral_freez(ar, pointers[i]);
+ pointers[i] = NULL;
+ }
+
+ if (auc->single_threaded && ar->aral_lock.pages && ar->aral_lock.pages->aral_lock.used_elements) {
+ fprintf(stderr, "\n\nARAL leftovers detected (1)\n\n");
+ __atomic_add_fetch(&auc->errors, 1, __ATOMIC_RELAXED);
+ }
+
+ if(!auc->single_threaded && __atomic_load_n(&auc->stop, __ATOMIC_RELAXED))
+ break;
+
+ for (size_t i = 0; i < elements; i++) {
+ pointers[i] = aral_mallocz(ar);
+ }
+
+ size_t increment = elements / ar->config.max_page_elements;
+ for (size_t all = increment; all <= elements / 2; all += increment) {
+
+ size_t to_free = (all % ar->config.max_page_elements) + 1;
+ size_t step = elements / to_free;
+ if(!step) step = 1;
+
+ // fprintf(stderr, "all %zu, to free %zu, step %zu\n", all, to_free, step);
+
+ size_t free_list[to_free];
+ for (size_t i = 0; i < to_free; i++) {
+ size_t pos = step * i;
+ aral_freez(ar, pointers[pos]);
+ pointers[pos] = NULL;
+ free_list[i] = pos;
+ }
+
+ for (size_t i = 0; i < to_free; i++) {
+ size_t pos = free_list[i];
+ pointers[pos] = aral_mallocz(ar);
+ }
+ }
+
+ for (size_t i = 0; i < elements; i++) {
+ aral_freez(ar, pointers[i]);
+ pointers[i] = NULL;
+ }
+
+ if (auc->single_threaded && ar->aral_lock.pages && ar->aral_lock.pages->aral_lock.used_elements) {
+ fprintf(stderr, "\n\nARAL leftovers detected (2)\n\n");
+ __atomic_add_fetch(&auc->errors, 1, __ATOMIC_RELAXED);
+ }
+
+ } while(!auc->single_threaded && !__atomic_load_n(&auc->stop, __ATOMIC_RELAXED));
+
+ freez(pointers);
+
+ return ptr;
+}
+
+int aral_stress_test(size_t threads, size_t elements, size_t seconds) {
+ fprintf(stderr, "Running stress test of %zu threads, with %zu elements each, for %zu seconds...\n",
+ threads, elements, seconds);
+
+ struct aral_unittest_config auc = {
+ .single_threaded = false,
+ .threads = threads,
+ .ar = aral_create("aral-stress-test", 20, 0, 8192, NULL, "aral-stress-test", NULL, false, false),
+ .elements = elements,
+ .errors = 0,
+ };
+
+ usec_t started_ut = now_monotonic_usec();
+ netdata_thread_t thread_ptrs[threads];
+
+ for(size_t i = 0; i < threads ; i++) {
+ char tag[NETDATA_THREAD_NAME_MAX + 1];
+ snprintfz(tag, NETDATA_THREAD_NAME_MAX, "TH[%zu]", i);
+ netdata_thread_create(&thread_ptrs[i], tag,
+ NETDATA_THREAD_OPTION_JOINABLE | NETDATA_THREAD_OPTION_DONT_LOG,
+ aral_test_thread, &auc);
+ }
+
+ size_t malloc_done = 0;
+ size_t free_done = 0;
+ size_t countdown = seconds;
+ while(countdown-- > 0) {
+ sleep_usec(1 * USEC_PER_SEC);
+ aral_lock(auc.ar);
+ size_t m = auc.ar->aral_lock.user_malloc_operations;
+ size_t f = auc.ar->aral_lock.user_free_operations;
+ aral_unlock(auc.ar);
+ fprintf(stderr, "ARAL executes %0.2f M malloc and %0.2f M free operations/s\n",
+ (double)(m - malloc_done) / 1000000.0, (double)(f - free_done) / 1000000.0);
+ malloc_done = m;
+ free_done = f;
+ }
+
+ __atomic_store_n(&auc.stop, true, __ATOMIC_RELAXED);
+
+// fprintf(stderr, "Cancelling the threads...\n");
+// for(size_t i = 0; i < threads ; i++) {
+// netdata_thread_cancel(thread_ptrs[i]);
+// }
+
+ fprintf(stderr, "Waiting the threads to finish...\n");
+ for(size_t i = 0; i < threads ; i++) {
+ netdata_thread_join(thread_ptrs[i], NULL);
+ }
+
+ usec_t ended_ut = now_monotonic_usec();
+
+ if (auc.ar->aral_lock.pages && auc.ar->aral_lock.pages->aral_lock.used_elements) {
+ fprintf(stderr, "\n\nARAL leftovers detected (3)\n\n");
+ __atomic_add_fetch(&auc.errors, 1, __ATOMIC_RELAXED);
+ }
+
+ info("ARAL: did %zu malloc, %zu free, "
+ "using %zu threads, in %llu usecs",
+ auc.ar->aral_lock.user_malloc_operations,
+ auc.ar->aral_lock.user_free_operations,
+ threads,
+ ended_ut - started_ut);
+
+ aral_destroy(auc.ar);
+
+ return auc.errors;
+}
+
+int aral_unittest(size_t elements) {
+ char *cache_dir = "/tmp/";
+
+ struct aral_unittest_config auc = {
+ .single_threaded = true,
+ .threads = 1,
+ .ar = aral_create("aral-test", 20, 0, 8192, NULL, "aral-test", &cache_dir, false, false),
+ .elements = elements,
+ .errors = 0,
+ };
+
+ aral_test_thread(&auc);
+
+ aral_destroy(auc.ar);
+
+ int errors = aral_stress_test(2, elements, 5);
+
+ return auc.errors + errors;
+}
diff --git a/libnetdata/aral/aral.h b/libnetdata/aral/aral.h
new file mode 100644
index 00000000..96f5a9c4
--- /dev/null
+++ b/libnetdata/aral/aral.h
@@ -0,0 +1,69 @@
+
+#ifndef ARAL_H
+#define ARAL_H 1
+
+#include "../libnetdata.h"
+
+#define ARAL_MAX_NAME 23
+
+typedef struct aral ARAL;
+
+struct aral_statistics {
+ struct {
+ size_t allocations;
+ size_t allocated_bytes;
+ } structures;
+
+ struct {
+ size_t allocations;
+ size_t allocated_bytes;
+ size_t used_bytes;
+ } malloc;
+
+ struct {
+ size_t allocations;
+ size_t allocated_bytes;
+ size_t used_bytes;
+ } mmap;
+};
+
+ARAL *aral_create(const char *name, size_t element_size, size_t initial_page_elements, size_t max_page_size,
+ struct aral_statistics *stats, const char *filename, char **cache_dir, bool mmap, bool lockless);
+size_t aral_element_size(ARAL *ar);
+size_t aral_overhead(ARAL *ar);
+size_t aral_structures(ARAL *ar);
+struct aral_statistics *aral_statistics(ARAL *ar);
+size_t aral_structures_from_stats(struct aral_statistics *stats);
+size_t aral_overhead_from_stats(struct aral_statistics *stats);
+
+ARAL *aral_by_size_acquire(size_t size);
+void aral_by_size_release(ARAL *ar);
+size_t aral_by_size_structures(void);
+size_t aral_by_size_overhead(void);
+struct aral_statistics *aral_by_size_statistics(void);
+
+int aral_unittest(size_t elements);
+
+#ifdef NETDATA_TRACE_ALLOCATIONS
+
+#define aral_mallocz(ar) aral_mallocz_internal(ar, __FILE__, __FUNCTION__, __LINE__)
+#define aral_freez(ar, ptr) aral_freez_internal(ar, ptr, __FILE__, __FUNCTION__, __LINE__)
+#define aral_destroy(ar) aral_destroy_internal(ar, __FILE__, __FUNCTION__, __LINE__)
+
+void *aral_mallocz_internal(ARAL *ar, const char *file, const char *function, size_t line);
+void aral_freez_internal(ARAL *ar, void *ptr, const char *file, const char *function, size_t line);
+void aral_destroy_internal(ARAL *ar, const char *file, const char *function, size_t line);
+
+#else // NETDATA_TRACE_ALLOCATIONS
+
+#define aral_mallocz(ar) aral_mallocz_internal(ar)
+#define aral_freez(ar, ptr) aral_freez_internal(ar, ptr)
+#define aral_destroy(ar) aral_destroy_internal(ar)
+
+void *aral_mallocz_internal(ARAL *ar);
+void aral_freez_internal(ARAL *ar, void *ptr);
+void aral_destroy_internal(ARAL *ar);
+
+#endif // NETDATA_TRACE_ALLOCATIONS
+
+#endif // ARAL_H
diff --git a/libnetdata/arrayalloc/README.md b/libnetdata/arrayalloc/README.md
deleted file mode 100644
index 2f21bf3f..00000000
--- a/libnetdata/arrayalloc/README.md
+++ /dev/null
@@ -1,7 +0,0 @@
-<!--
-title: "Array Allocator"
-custom_edit_url: https://github.com/netdata/netdata/edit/master/libnetdata/arrayalloc/README.md
--->
-
-# Array Allocator
-
diff --git a/libnetdata/arrayalloc/arrayalloc.c b/libnetdata/arrayalloc/arrayalloc.c
deleted file mode 100644
index f337279a..00000000
--- a/libnetdata/arrayalloc/arrayalloc.c
+++ /dev/null
@@ -1,489 +0,0 @@
-#include "../libnetdata.h"
-#include "arrayalloc.h"
-#include "daemon/common.h"
-
-// max file size
-#define ARAL_MAX_PAGE_SIZE_MMAP (1*1024*1024*1024)
-
-// max malloc size
-// optimal at current versions of libc is up to 256k
-// ideal to have the same overhead as libc is 4k
-#define ARAL_MAX_PAGE_SIZE_MALLOC (64*1024)
-
-typedef struct arrayalloc_free {
- size_t size;
- struct arrayalloc_page *page;
- struct arrayalloc_free *next;
-} ARAL_FREE;
-
-typedef struct arrayalloc_page {
- const char *filename;
- size_t size; // the total size of the page
- size_t used_elements; // the total number of used elements on this page
- uint8_t *data;
- ARAL_FREE *free_list;
- struct arrayalloc_page *prev; // the prev page on the list
- struct arrayalloc_page *next; // the next page on the list
-} ARAL_PAGE;
-
-#define ARAL_NATURAL_ALIGNMENT (sizeof(uintptr_t) * 2)
-static inline size_t natural_alignment(size_t size, size_t alignment) {
- if(unlikely(size % alignment))
- size = size + alignment - (size % alignment);
-
- return size;
-}
-
-static void arrayalloc_delete_leftover_files(const char *path, const char *required_prefix) {
- DIR *dir = opendir(path);
- if(!dir) return;
-
- char fullpath[FILENAME_MAX + 1];
- size_t len = strlen(required_prefix);
-
- struct dirent *de = NULL;
- while((de = readdir(dir))) {
- if(de->d_type == DT_DIR)
- continue;
-
- if(strncmp(de->d_name, required_prefix, len) != 0)
- continue;
-
- snprintfz(fullpath, FILENAME_MAX, "%s/%s", path, de->d_name);
- info("ARRAYALLOC: removing left-over file '%s'", fullpath);
- if(unlikely(unlink(fullpath) == -1))
- error("Cannot delete file '%s'", fullpath);
- }
-
- closedir(dir);
-}
-
-// ----------------------------------------------------------------------------
-// arrayalloc_init()
-
-static void arrayalloc_init(ARAL *ar) {
- static netdata_mutex_t mutex = NETDATA_MUTEX_INITIALIZER;
- netdata_mutex_lock(&mutex);
-
- if(!ar->internal.initialized) {
- netdata_mutex_init(&ar->internal.mutex);
-
- long int page_size = sysconf(_SC_PAGE_SIZE);
- if (unlikely(page_size == -1))
- ar->internal.natural_page_size = 4096;
- else
- ar->internal.natural_page_size = page_size;
-
- // we need to add a page pointer after the element
- // so, first align the element size to the pointer size
- ar->internal.element_size = natural_alignment(ar->requested_element_size, sizeof(uintptr_t));
-
- // then add the size of a pointer to it
- ar->internal.element_size += sizeof(uintptr_t);
-
- // make sure it is at least what we need for an ARAL_FREE slot
- if (ar->internal.element_size < sizeof(ARAL_FREE))
- ar->internal.element_size = sizeof(ARAL_FREE);
-
- // and finally align it to the natural alignment
- ar->internal.element_size = natural_alignment(ar->internal.element_size, ARAL_NATURAL_ALIGNMENT);
-
- // we write the page pointer just after each element
- ar->internal.page_ptr_offset = ar->internal.element_size - sizeof(uintptr_t);
-
- if(ar->requested_element_size + sizeof(uintptr_t) > ar->internal.element_size)
- fatal("ARRAYALLOC: failed to calculate properly page_ptr_offset: element size %zu, sizeof(uintptr_t) %zu, natural alignment %zu, final element size %zu, page_ptr_offset %zu",
- ar->requested_element_size, sizeof(uintptr_t), ARAL_NATURAL_ALIGNMENT, ar->internal.element_size, ar->internal.page_ptr_offset);
-
- //info("ARRAYALLOC: element size %zu, sizeof(uintptr_t) %zu, natural alignment %zu, final element size %zu, page_ptr_offset %zu",
- // ar->element_size, sizeof(uintptr_t), ARAL_NATURAL_ALIGNMENT, ar->internal.element_size, ar->internal.page_ptr_offset);
-
- if (ar->initial_elements < 10)
- ar->initial_elements = 10;
-
- ar->internal.mmap = (ar->use_mmap && ar->cache_dir && *ar->cache_dir) ? true : false;
- ar->internal.max_alloc_size = ar->internal.mmap ? ARAL_MAX_PAGE_SIZE_MMAP : ARAL_MAX_PAGE_SIZE_MALLOC;
-
- if(ar->internal.max_alloc_size % ar->internal.natural_page_size)
- ar->internal.max_alloc_size += ar->internal.natural_page_size - (ar->internal.max_alloc_size % ar->internal.natural_page_size) ;
-
- if(ar->internal.max_alloc_size % ar->internal.element_size)
- ar->internal.max_alloc_size -= ar->internal.max_alloc_size % ar->internal.element_size;
-
- ar->internal.pages = NULL;
- ar->internal.allocation_multiplier = 1;
- ar->internal.file_number = 0;
-
- if(ar->internal.mmap) {
- char directory_name[FILENAME_MAX + 1];
- snprintfz(directory_name, FILENAME_MAX, "%s/array_alloc.mmap", *ar->cache_dir);
- int r = mkdir(directory_name, 0775);
- if (r != 0 && errno != EEXIST)
- fatal("Cannot create directory '%s'", directory_name);
-
- char filename[FILENAME_MAX + 1];
- snprintfz(filename, FILENAME_MAX, "%s.", ar->filename);
- arrayalloc_delete_leftover_files(directory_name, filename);
- }
-
- ar->internal.initialized = true;
- }
-
- netdata_mutex_unlock(&mutex);
-}
-
-// ----------------------------------------------------------------------------
-// check a free slot
-
-#ifdef NETDATA_INTERNAL_CHECKS
-static inline void arrayalloc_free_validate_internal_check(ARAL *ar, ARAL_FREE *fr) {
- if(fr->size < ar->internal.element_size)
- fatal("ARRAYALLOC: free item of size %zu, less than the expected element size %zu", fr->size, ar->internal.element_size);
-
- if(fr->size % ar->internal.element_size)
- fatal("ARRAYALLOC: free item of size %zu is not multiple to element size %zu", fr->size, ar->internal.element_size);
-}
-#else
-#define arrayalloc_free_validate_internal_check(ar, fr) debug_dummy()
-#endif
-
-// ----------------------------------------------------------------------------
-// find the page a pointer belongs to
-
-#ifdef NETDATA_INTERNAL_CHECKS
-static inline ARAL_PAGE *find_page_with_allocation_internal_check(ARAL *ar, void *ptr) {
- uintptr_t seeking = (uintptr_t)ptr;
- ARAL_PAGE *page;
-
- for(page = ar->internal.pages; page ; page = page->next) {
- if(unlikely(seeking >= (uintptr_t)page->data && seeking < (uintptr_t)page->data + page->size))
- break;
- }
-
- return page;
-}
-#endif
-
-// ----------------------------------------------------------------------------
-// find a page with a free slot (there shouldn't be any)
-
-#ifdef NETDATA_INTERNAL_CHECKS
-static inline ARAL_PAGE *find_page_with_free_slots_internal_check(ARAL *ar) {
- ARAL_PAGE *page;
-
- for(page = ar->internal.pages; page ; page = page->next) {
- if(page->free_list)
- break;
-
- internal_fatal(page->size - page->used_elements * ar->internal.element_size >= ar->internal.element_size,
- "ARRAYALLOC: a page is marked full, but it is not!");
-
- internal_fatal(page->size < page->used_elements * ar->internal.element_size,
- "ARRAYALLOC: a page has been overflown!");
- }
-
- return page;
-}
-#endif
-
-#ifdef NETDATA_TRACE_ALLOCATIONS
-static void arrayalloc_add_page(ARAL *ar, const char *file, const char *function, size_t line) {
-#else
-static void arrayalloc_add_page(ARAL *ar) {
-#endif
- if(unlikely(!ar->internal.initialized))
- arrayalloc_init(ar);
-
- ARAL_PAGE *page = callocz(1, sizeof(ARAL_PAGE));
- page->size = ar->initial_elements * ar->internal.element_size * ar->internal.allocation_multiplier;
- if(page->size > ar->internal.max_alloc_size)
- page->size = ar->internal.max_alloc_size;
- else
- ar->internal.allocation_multiplier *= 2;
-
- if(ar->internal.mmap) {
- ar->internal.file_number++;
- char filename[FILENAME_MAX + 1];
- snprintfz(filename, FILENAME_MAX, "%s/array_alloc.mmap/%s.%zu", *ar->cache_dir, ar->filename, ar->internal.file_number);
- page->filename = strdupz(filename);
- page->data = netdata_mmap(page->filename, page->size, MAP_SHARED, 0);
- if (unlikely(!page->data))
- fatal("Cannot allocate arrayalloc buffer of size %zu on filename '%s'", page->size, page->filename);
- }
- else {
-#ifdef NETDATA_TRACE_ALLOCATIONS
- page->data = mallocz_int(page->size, file, function, line);
-#else
- page->data = mallocz(page->size);
-#endif
- }
-
- // link the free space to its page
- ARAL_FREE *fr = (ARAL_FREE *)page->data;
- fr->size = page->size;
- fr->page = page;
- fr->next = NULL;
- page->free_list = fr;
-
- // link the new page at the front of the list of pages
- DOUBLE_LINKED_LIST_PREPEND_UNSAFE(ar->internal.pages, page, prev, next);
-
- arrayalloc_free_validate_internal_check(ar, fr);
-}
-
-static void arrayalloc_lock(ARAL *ar) {
- if(!ar->internal.lockless)
- netdata_mutex_lock(&ar->internal.mutex);
-}
-
-static void arrayalloc_unlock(ARAL *ar) {
- if(!ar->internal.lockless)
- netdata_mutex_unlock(&ar->internal.mutex);
-}
-
-ARAL *arrayalloc_create(size_t element_size, size_t elements, const char *filename, char **cache_dir, bool mmap) {
- ARAL *ar = callocz(1, sizeof(ARAL));
- ar->requested_element_size = element_size;
- ar->initial_elements = elements;
- ar->filename = filename;
- ar->cache_dir = cache_dir;
- ar->use_mmap = mmap;
- return ar;
-}
-
-#ifdef NETDATA_TRACE_ALLOCATIONS
-void *arrayalloc_mallocz_int(ARAL *ar, const char *file, const char *function, size_t line) {
-#else
-void *arrayalloc_mallocz(ARAL *ar) {
-#endif
- if(unlikely(!ar->internal.initialized))
- arrayalloc_init(ar);
-
- arrayalloc_lock(ar);
-
- if(unlikely(!ar->internal.pages || !ar->internal.pages->free_list)) {
- internal_fatal(find_page_with_free_slots_internal_check(ar) != NULL,
- "ARRAYALLOC: first page does not have any free slots, but there is another that has!");
-
-#ifdef NETDATA_TRACE_ALLOCATIONS
- arrayalloc_add_page(ar, file, function, line);
-#else
- arrayalloc_add_page(ar);
-#endif
- }
-
- ARAL_PAGE *page = ar->internal.pages;
- ARAL_FREE *found_fr = page->free_list;
-
- internal_fatal(!found_fr,
- "ARRAYALLOC: free item to use, cannot be NULL.");
-
- internal_fatal(found_fr->size < ar->internal.element_size,
- "ARRAYALLOC: free item size %zu, cannot be smaller than %zu",
- found_fr->size, ar->internal.element_size);
-
- if(unlikely(found_fr->size - ar->internal.element_size < ar->internal.element_size)) {
- // we can use the entire free space entry
-
- page->free_list = found_fr->next;
-
- if(unlikely(!page->free_list)) {
- // we are done with this page
- // move the full page last
- // so that pages with free items remain first in the list
- DOUBLE_LINKED_LIST_REMOVE_UNSAFE(ar->internal.pages, page, prev, next);
- DOUBLE_LINKED_LIST_APPEND_UNSAFE(ar->internal.pages, page, prev, next);
- }
- }
- else {
- // we can split the free space entry
-
- uint8_t *data = (uint8_t *)found_fr;
- ARAL_FREE *fr = (ARAL_FREE *)&data[ar->internal.element_size];
- fr->page = page;
- fr->size = found_fr->size - ar->internal.element_size;
-
- // link the free slot first in the page
- fr->next = found_fr->next;
- page->free_list = fr;
-
- arrayalloc_free_validate_internal_check(ar, fr);
- }
-
- page->used_elements++;
-
- // put the page pointer after the element
- uint8_t *data = (uint8_t *)found_fr;
- ARAL_PAGE **page_ptr = (ARAL_PAGE **)&data[ar->internal.page_ptr_offset];
- *page_ptr = page;
-
- arrayalloc_unlock(ar);
- return (void *)found_fr;
-}
-
-#ifdef NETDATA_TRACE_ALLOCATIONS
-void arrayalloc_freez_int(ARAL *ar, void *ptr, const char *file, const char *function, size_t line) {
-#else
-void arrayalloc_freez(ARAL *ar, void *ptr) {
-#endif
- if(unlikely(!ptr)) return;
- arrayalloc_lock(ar);
-
- // get the page pointer
- ARAL_PAGE *page;
- {
- uint8_t *data = (uint8_t *)ptr;
- ARAL_PAGE **page_ptr = (ARAL_PAGE **)&data[ar->internal.page_ptr_offset];
- page = *page_ptr;
-
-#ifdef NETDATA_INTERNAL_CHECKS
- // make it NULL so that we will fail on double free
- // do not enable this on production, because the MMAP file
- // will need to be saved again!
- *page_ptr = NULL;
-#endif
- }
-
-#ifdef NETDATA_ARRAYALLOC_INTERNAL_CHECKS
- {
- // find the page ptr belongs
- ARAL_PAGE *page2 = find_page_with_allocation_internal_check(ar, ptr);
-
- if(unlikely(page != page2))
- fatal("ARRAYALLOC: page pointers do not match!");
-
- if (unlikely(!page2))
- fatal("ARRAYALLOC: free of pointer %p is not in arrayalloc address space.", ptr);
- }
-#endif
-
- if(unlikely(!page))
- fatal("ARRAYALLOC: possible corruption or double free of pointer %p", ptr);
-
- if (unlikely(!page->used_elements))
- fatal("ARRAYALLOC: free of pointer %p is inside a page without any active allocations.", ptr);
-
- page->used_elements--;
-
- // make this element available
- ARAL_FREE *fr = (ARAL_FREE *)ptr;
- fr->page = page;
- fr->size = ar->internal.element_size;
- fr->next = page->free_list;
- page->free_list = fr;
-
- // if the page is empty, release it
- if(!page->used_elements) {
- DOUBLE_LINKED_LIST_REMOVE_UNSAFE(ar->internal.pages, page, prev, next);
-
- // free it
- if(ar->internal.mmap) {
- netdata_munmap(page->data, page->size);
- if (unlikely(unlink(page->filename) == 1))
- error("Cannot delete file '%s'", page->filename);
- freez((void *)page->filename);
- }
- else {
-#ifdef NETDATA_TRACE_ALLOCATIONS
- freez_int(page->data, file, function, line);
-#else
- freez(page->data);
-#endif
- }
-
- freez(page);
- }
- else if(page != ar->internal.pages) {
- // move the page with free item first
- // so that the next allocation will use this page
- DOUBLE_LINKED_LIST_REMOVE_UNSAFE(ar->internal.pages, page, prev, next);
- DOUBLE_LINKED_LIST_PREPEND_UNSAFE(ar->internal.pages, page, prev, next);
- }
-
- arrayalloc_unlock(ar);
-}
-
-int aral_unittest(size_t elements) {
- char *cache_dir = "/tmp/";
- ARAL *ar = arrayalloc_create(20, 10, "test-aral", &cache_dir, false);
-
- void *pointers[elements];
-
- for(size_t i = 0; i < elements ;i++) {
- pointers[i] = arrayalloc_mallocz(ar);
- }
-
- for(size_t div = 5; div >= 2 ;div--) {
- for (size_t i = 0; i < elements / div; i++) {
- arrayalloc_freez(ar, pointers[i]);
- }
-
- for (size_t i = 0; i < elements / div; i++) {
- pointers[i] = arrayalloc_mallocz(ar);
- }
- }
-
- for(size_t step = 50; step >= 10 ;step -= 10) {
- for (size_t i = 0; i < elements; i += step) {
- arrayalloc_freez(ar, pointers[i]);
- }
-
- for (size_t i = 0; i < elements; i += step) {
- pointers[i] = arrayalloc_mallocz(ar);
- }
- }
-
- for(size_t i = 0; i < elements ;i++) {
- arrayalloc_freez(ar, pointers[i]);
- }
-
- if(ar->internal.pages) {
- fprintf(stderr, "ARAL leftovers detected (1)");
- return 1;
- }
-
- size_t ops = 0;
- size_t increment = elements / 10;
- size_t allocated = 0;
- for(size_t all = increment; all <= elements ; all += increment) {
-
- for(; allocated < all ; allocated++) {
- pointers[allocated] = arrayalloc_mallocz(ar);
- ops++;
- }
-
- size_t to_free = now_realtime_usec() % all;
- size_t free_list[to_free];
- for(size_t i = 0; i < to_free ;i++) {
- size_t pos;
- do {
- pos = now_realtime_usec() % all;
- } while(!pointers[pos]);
-
- arrayalloc_freez(ar, pointers[pos]);
- pointers[pos] = NULL;
- free_list[i] = pos;
- ops++;
- }
-
- for(size_t i = 0; i < to_free ;i++) {
- size_t pos = free_list[i];
- pointers[pos] = arrayalloc_mallocz(ar);
- ops++;
- }
- }
-
- for(size_t i = 0; i < allocated - 1 ;i++) {
- arrayalloc_freez(ar, pointers[i]);
- ops++;
- }
-
- arrayalloc_freez(ar, pointers[allocated - 1]);
-
- if(ar->internal.pages) {
- fprintf(stderr, "ARAL leftovers detected (2)");
- return 1;
- }
-
- return 0;
-}
diff --git a/libnetdata/arrayalloc/arrayalloc.h b/libnetdata/arrayalloc/arrayalloc.h
deleted file mode 100644
index cf80b73f..00000000
--- a/libnetdata/arrayalloc/arrayalloc.h
+++ /dev/null
@@ -1,48 +0,0 @@
-
-#ifndef ARRAYALLOC_H
-#define ARRAYALLOC_H 1
-
-#include "../libnetdata.h"
-
-typedef struct arrayalloc {
- size_t requested_element_size;
- size_t initial_elements;
- const char *filename;
- char **cache_dir;
- bool use_mmap;
-
- // private members - do not touch
- struct {
- bool mmap;
- bool lockless;
- bool initialized;
- size_t element_size;
- size_t page_ptr_offset;
- size_t file_number;
- size_t natural_page_size;
- size_t allocation_multiplier;
- size_t max_alloc_size;
- netdata_mutex_t mutex;
- struct arrayalloc_page *pages;
- } internal;
-} ARAL;
-
-ARAL *arrayalloc_create(size_t element_size, size_t elements, const char *filename, char **cache_dir, bool mmap);
-int aral_unittest(size_t elements);
-
-#ifdef NETDATA_TRACE_ALLOCATIONS
-
-#define arrayalloc_mallocz(ar) arrayalloc_mallocz_int(ar, __FILE__, __FUNCTION__, __LINE__)
-#define arrayalloc_freez(ar, ptr) arrayalloc_freez_int(ar, ptr, __FILE__, __FUNCTION__, __LINE__)
-
-void *arrayalloc_mallocz_int(ARAL *ar, const char *file, const char *function, size_t line);
-void arrayalloc_freez_int(ARAL *ar, void *ptr, const char *file, const char *function, size_t line);
-
-#else // NETDATA_TRACE_ALLOCATIONS
-
-void *arrayalloc_mallocz(ARAL *ar);
-void arrayalloc_freez(ARAL *ar, void *ptr);
-
-#endif // NETDATA_TRACE_ALLOCATIONS
-
-#endif // ARRAYALLOC_H
diff --git a/libnetdata/avl/README.md b/libnetdata/avl/README.md
index 36392bd7..2b03fec4 100644
--- a/libnetdata/avl/README.md
+++ b/libnetdata/avl/README.md
@@ -1,6 +1,10 @@
<!--
title: "AVL"
custom_edit_url: https://github.com/netdata/netdata/edit/master/libnetdata/avl/README.md
+sidebar_label: "AVL"
+learn_status: "Published"
+learn_topic_type: "Tasks"
+learn_rel_path: "Developers/libnetdata libraries"
-->
# AVL
diff --git a/libnetdata/buffer/README.md b/libnetdata/buffer/README.md
index c5f66e6e..6a84fd8a 100644
--- a/libnetdata/buffer/README.md
+++ b/libnetdata/buffer/README.md
@@ -1,6 +1,10 @@
<!--
title: "BUFFER"
custom_edit_url: https://github.com/netdata/netdata/edit/master/libnetdata/buffer/README.md
+sidebar_label: "BUFFER library"
+learn_status: "Published"
+learn_topic_type: "Tasks"
+learn_rel_path: "Developers/libnetdata libraries"
-->
# BUFFER
diff --git a/libnetdata/buffer/buffer.c b/libnetdata/buffer/buffer.c
index d0940588..eeb28320 100644
--- a/libnetdata/buffer/buffer.c
+++ b/libnetdata/buffer/buffer.c
@@ -442,28 +442,28 @@ void buffer_date(BUFFER *wb, int year, int month, int day, int hours, int minute
buffer_need_bytes(wb, 36);
char *b = &wb->buffer[wb->len];
- char *p = b;
-
- *p++ = '0' + year / 1000; year %= 1000;
- *p++ = '0' + year / 100; year %= 100;
- *p++ = '0' + year / 10;
- *p++ = '0' + year % 10;
- *p++ = '-';
- *p++ = '0' + month / 10;
- *p++ = '0' + month % 10;
- *p++ = '-';
- *p++ = '0' + day / 10;
- *p++ = '0' + day % 10;
- *p++ = ' ';
- *p++ = '0' + hours / 10;
- *p++ = '0' + hours % 10;
- *p++ = ':';
- *p++ = '0' + minutes / 10;
- *p++ = '0' + minutes % 10;
- *p++ = ':';
- *p++ = '0' + seconds / 10;
- *p++ = '0' + seconds % 10;
- *p = '\0';
+ char *p = b;
+
+ *p++ = '0' + year / 1000; year %= 1000;
+ *p++ = '0' + year / 100; year %= 100;
+ *p++ = '0' + year / 10;
+ *p++ = '0' + year % 10;
+ *p++ = '-';
+ *p++ = '0' + month / 10;
+ *p++ = '0' + month % 10;
+ *p++ = '-';
+ *p++ = '0' + day / 10;
+ *p++ = '0' + day % 10;
+ *p++ = ' ';
+ *p++ = '0' + hours / 10;
+ *p++ = '0' + hours % 10;
+ *p++ = ':';
+ *p++ = '0' + minutes / 10;
+ *p++ = '0' + minutes % 10;
+ *p++ = ':';
+ *p++ = '0' + seconds / 10;
+ *p++ = '0' + seconds % 10;
+ *p = '\0';
wb->len += (size_t)(p - b);
@@ -472,7 +472,7 @@ void buffer_date(BUFFER *wb, int year, int month, int day, int hours, int minute
buffer_overflow_check(wb);
}
-BUFFER *buffer_create(size_t size)
+BUFFER *buffer_create(size_t size, size_t *statistics)
{
BUFFER *b;
@@ -483,9 +483,13 @@ BUFFER *buffer_create(size_t size)
b->buffer[0] = '\0';
b->size = size;
b->contenttype = CT_TEXT_PLAIN;
+ b->statistics = statistics;
buffer_overflow_init(b);
buffer_overflow_check(b);
+ if(b->statistics)
+ __atomic_add_fetch(b->statistics, b->size + sizeof(BUFFER) + sizeof(BUFFER_OVERFLOW_EOF) + 2, __ATOMIC_RELAXED);
+
return(b);
}
@@ -496,6 +500,9 @@ void buffer_free(BUFFER *b) {
debug(D_WEB_BUFFER, "Freeing web buffer of size %zu.", b->size);
+ if(b->statistics)
+ __atomic_sub_fetch(b->statistics, b->size + sizeof(BUFFER) + sizeof(BUFFER_OVERFLOW_EOF) + 2, __ATOMIC_RELAXED);
+
freez(b->buffer);
freez(b);
}
@@ -510,9 +517,7 @@ void buffer_increase(BUFFER *b, size_t free_size_required) {
size_t minimum = WEB_DATA_LENGTH_INCREASE_STEP;
if(minimum > wanted) wanted = minimum;
- size_t optimal = b->size;
- if(b->size > 5*1024*1024) optimal = b->size / 2;
-
+ size_t optimal = (b->size > 5*1024*1024) ? b->size / 2 : b->size;
if(optimal > wanted) wanted = optimal;
debug(D_WEB_BUFFER, "Increasing data buffer from size %zu to %zu.", b->size, b->size + wanted);
@@ -520,6 +525,9 @@ void buffer_increase(BUFFER *b, size_t free_size_required) {
b->buffer = reallocz(b->buffer, b->size + wanted + sizeof(BUFFER_OVERFLOW_EOF) + 2);
b->size += wanted;
+ if(b->statistics)
+ __atomic_add_fetch(b->statistics, wanted, __ATOMIC_RELAXED);
+
buffer_overflow_init(b);
buffer_overflow_check(b);
}
diff --git a/libnetdata/buffer/buffer.h b/libnetdata/buffer/buffer.h
index ce6f5289..0fa3495b 100644
--- a/libnetdata/buffer/buffer.h
+++ b/libnetdata/buffer/buffer.h
@@ -15,6 +15,7 @@ typedef struct web_buffer {
uint8_t options; // options related to the content
time_t date; // the timestamp this content has been generated
time_t expires; // the timestamp this content expires
+ size_t *statistics;
} BUFFER;
// options
@@ -61,7 +62,7 @@ void buffer_rrd_value(BUFFER *wb, NETDATA_DOUBLE value);
void buffer_date(BUFFER *wb, int year, int month, int day, int hours, int minutes, int seconds);
void buffer_jsdate(BUFFER *wb, int year, int month, int day, int hours, int minutes, int seconds);
-BUFFER *buffer_create(size_t size);
+BUFFER *buffer_create(size_t size, size_t *statistics);
void buffer_free(BUFFER *b);
void buffer_increase(BUFFER *b, size_t free_size_required);
diff --git a/libnetdata/circular_buffer/README.md b/libnetdata/circular_buffer/README.md
index 4482173d..23980dff 100644
--- a/libnetdata/circular_buffer/README.md
+++ b/libnetdata/circular_buffer/README.md
@@ -1,6 +1,10 @@
<!--
-title: "circular_buffer"
+title: "Circular Buffer"
custom_edit_url: https://github.com/netdata/netdata/edit/master/libnetdata/circular_buffer/README.md
+sidebar_label: "Circular Buffer"
+learn_status: "Published"
+learn_topic_type: "Tasks"
+learn_rel_path: "Developers/libnetdata libraries"
-->
# Circular Buffer
diff --git a/libnetdata/circular_buffer/circular_buffer.c b/libnetdata/circular_buffer/circular_buffer.c
index c791b420..b2bded17 100644
--- a/libnetdata/circular_buffer/circular_buffer.c
+++ b/libnetdata/circular_buffer/circular_buffer.c
@@ -1,16 +1,24 @@
#include "../libnetdata.h"
-struct circular_buffer *cbuffer_new(size_t initial, size_t max) {
- struct circular_buffer *result = mallocz(sizeof(*result));
- result->size = initial;
- result->data = mallocz(initial);
- result->write = 0;
- result->read = 0;
- result->max_size = max;
- return result;
+struct circular_buffer *cbuffer_new(size_t initial, size_t max, size_t *statistics) {
+ struct circular_buffer *buf = mallocz(sizeof(struct circular_buffer));
+ buf->size = initial;
+ buf->data = mallocz(initial);
+ buf->write = 0;
+ buf->read = 0;
+ buf->max_size = max;
+ buf->statistics = statistics;
+
+ if(buf->statistics)
+ __atomic_add_fetch(buf->statistics, sizeof(struct circular_buffer) + buf->size, __ATOMIC_RELAXED);
+
+ return buf;
}
void cbuffer_free(struct circular_buffer *buf) {
+ if(buf && buf->statistics)
+ __atomic_sub_fetch(buf->statistics, sizeof(struct circular_buffer) + buf->size, __ATOMIC_RELAXED);
+
freez(buf->data);
freez(buf);
}
@@ -19,6 +27,8 @@ static int cbuffer_realloc_unsafe(struct circular_buffer *buf) {
// Check that we can grow
if (buf->size >= buf->max_size)
return 1;
+
+ size_t old_size = buf->size;
size_t new_size = buf->size * 2;
if (new_size > buf->max_size)
new_size = buf->max_size;
@@ -43,6 +53,10 @@ static int cbuffer_realloc_unsafe(struct circular_buffer *buf) {
freez(buf->data);
buf->data = new_data;
buf->size = new_size;
+
+ if(buf->statistics)
+ __atomic_add_fetch(buf->statistics, new_size - old_size, __ATOMIC_RELAXED);
+
return 0;
}
diff --git a/libnetdata/circular_buffer/circular_buffer.h b/libnetdata/circular_buffer/circular_buffer.h
index 8c42aa80..9d29a84d 100644
--- a/libnetdata/circular_buffer/circular_buffer.h
+++ b/libnetdata/circular_buffer/circular_buffer.h
@@ -5,10 +5,11 @@
struct circular_buffer {
size_t size, write, read, max_size;
+ size_t *statistics;
char *data;
};
-struct circular_buffer *cbuffer_new(size_t initial, size_t max);
+struct circular_buffer *cbuffer_new(size_t initial, size_t max, size_t *statistics);
void cbuffer_free(struct circular_buffer *buf);
int cbuffer_add_unsafe(struct circular_buffer *buf, const char *d, size_t d_len);
void cbuffer_remove_unsafe(struct circular_buffer *buf, size_t num);
diff --git a/libnetdata/clocks/clocks.c b/libnetdata/clocks/clocks.c
index cabc0000..19c66f0a 100644
--- a/libnetdata/clocks/clocks.c
+++ b/libnetdata/clocks/clocks.c
@@ -189,9 +189,13 @@ void sleep_to_absolute_time(usec_t usec) {
.tv_nsec = (suseconds_t)((usec % USEC_PER_SEC) * NSEC_PER_USEC)
};
+ errno = 0;
int ret = 0;
while( (ret = clock_nanosleep(clock, TIMER_ABSTIME, &req, NULL)) != 0 ) {
- if(ret == EINTR) continue;
+ if(ret == EINTR) {
+ errno = 0;
+ continue;
+ }
else {
if (ret == EINVAL) {
if (!einval_printed) {
@@ -296,7 +300,9 @@ usec_t heartbeat_next(heartbeat_t *hb, usec_t tick) {
if(unlikely(hb->randomness > tick / 2)) {
// TODO: The heartbeat tick should be specified at the heartbeat_init() function
usec_t tmp = (now_realtime_usec() * clock_realtime_resolution) % (tick / 2);
- info("heartbeat randomness of %llu is too big for a tick of %llu - setting it to %llu", hb->randomness, tick, tmp);
+
+ error_limit_static_global_var(erl, 10, 0);
+ error_limit(&erl, "heartbeat randomness of %llu is too big for a tick of %llu - setting it to %llu", hb->randomness, tick, tmp);
hb->randomness = tmp;
}
@@ -311,7 +317,7 @@ usec_t heartbeat_next(heartbeat_t *hb, usec_t tick) {
// sleep_usec() has a loop to guarantee we will sleep for at least the requested time.
// According the specs, when we sleep for a relative time, clock adjustments should not affect the duration
// we sleep.
- sleep_usec(next - now);
+ sleep_usec_with_now(next - now, now);
now = now_realtime_usec();
dt = now - hb->realtime;
@@ -322,11 +328,13 @@ usec_t heartbeat_next(heartbeat_t *hb, usec_t tick) {
if(unlikely(now < next)) {
errno = 0;
- error("heartbeat clock: woke up %llu microseconds earlier than expected (can be due to the CLOCK_REALTIME set to the past).", next - now);
+ error_limit_static_global_var(erl, 10, 0);
+ error_limit(&erl, "heartbeat clock: woke up %llu microseconds earlier than expected (can be due to the CLOCK_REALTIME set to the past).", next - now);
}
else if(unlikely(now - next > tick / 2)) {
errno = 0;
- error("heartbeat clock: woke up %llu microseconds later than expected (can be due to system load or the CLOCK_REALTIME set to the future).", now - next);
+ error_limit_static_global_var(erl, 10, 0);
+ error_limit(&erl, "heartbeat clock: woke up %llu microseconds later than expected (can be due to system load or the CLOCK_REALTIME set to the future).", now - next);
}
if(unlikely(!hb->realtime)) {
@@ -338,7 +346,7 @@ usec_t heartbeat_next(heartbeat_t *hb, usec_t tick) {
return dt;
}
-void sleep_usec(usec_t usec) {
+void sleep_usec_with_now(usec_t usec, usec_t started_ut) {
// we expect microseconds (1.000.000 per second)
// but timespec is nanoseconds (1.000.000.000 per second)
struct timespec rem = { 0, 0 }, req = {
@@ -346,21 +354,37 @@ void sleep_usec(usec_t usec) {
.tv_nsec = (suseconds_t) ((usec % USEC_PER_SEC) * NSEC_PER_USEC)
};
-#ifdef __linux__
- while (clock_nanosleep(CLOCK_REALTIME, 0, &req, &rem) != 0) {
-#else
+ // make sure errno is not EINTR
+ errno = 0;
+
+ if(!started_ut)
+ started_ut = now_realtime_usec();
+
+ usec_t end_ut = started_ut + usec;
+
while (nanosleep(&req, &rem) != 0) {
-#endif
if (likely(errno == EINTR && (rem.tv_sec || rem.tv_nsec))) {
req = rem;
rem = (struct timespec){ 0, 0 };
+
+ // break an infinite loop
+ errno = 0;
+
+ usec_t now_ut = now_realtime_usec();
+ if(now_ut >= end_ut)
+ break;
+
+ usec_t remaining_ut = (usec_t)req.tv_sec * USEC_PER_SEC + (usec_t)req.tv_nsec * NSEC_PER_USEC > usec;
+ usec_t check_ut = now_ut - started_ut;
+ if(remaining_ut > check_ut) {
+ req = (struct timespec){
+ .tv_sec = (time_t) ( check_ut / USEC_PER_SEC),
+ .tv_nsec = (suseconds_t) ((check_ut % USEC_PER_SEC) * NSEC_PER_USEC)
+ };
+ }
}
else {
-#ifdef __linux__
- error("Cannot clock_nanosleep(CLOCK_REALTIME) for %llu microseconds.", usec);
-#else
error("Cannot nanosleep() for %llu microseconds.", usec);
-#endif
break;
}
}
diff --git a/libnetdata/clocks/clocks.h b/libnetdata/clocks/clocks.h
index 7738a2c8..b050b625 100644
--- a/libnetdata/clocks/clocks.h
+++ b/libnetdata/clocks/clocks.h
@@ -141,7 +141,8 @@ usec_t heartbeat_next(heartbeat_t *hb, usec_t tick);
void heartbeat_statistics(usec_t *min_ptr, usec_t *max_ptr, usec_t *average_ptr, size_t *count_ptr);
-void sleep_usec(usec_t usec);
+void sleep_usec_with_now(usec_t usec, usec_t started_ut);
+#define sleep_usec(usec) sleep_usec_with_now(usec, 0);
void clocks_init(void);
diff --git a/libnetdata/completion/completion.c b/libnetdata/completion/completion.c
index b5ac86e4..6257e029 100644
--- a/libnetdata/completion/completion.c
+++ b/libnetdata/completion/completion.c
@@ -5,6 +5,7 @@
void completion_init(struct completion *p)
{
p->completed = 0;
+ p->completed_jobs = 0;
fatal_assert(0 == uv_cond_init(&p->cond));
fatal_assert(0 == uv_mutex_init(&p->mutex));
}
@@ -32,3 +33,32 @@ void completion_mark_complete(struct completion *p)
uv_cond_broadcast(&p->cond);
uv_mutex_unlock(&p->mutex);
}
+
+unsigned completion_wait_for_a_job(struct completion *p, unsigned completed_jobs)
+{
+ uv_mutex_lock(&p->mutex);
+ while (0 == p->completed && p->completed_jobs <= completed_jobs) {
+ uv_cond_wait(&p->cond, &p->mutex);
+ }
+ completed_jobs = p->completed_jobs;
+ uv_mutex_unlock(&p->mutex);
+
+ return completed_jobs;
+}
+
+void completion_mark_complete_a_job(struct completion *p)
+{
+ uv_mutex_lock(&p->mutex);
+ p->completed_jobs++;
+ uv_cond_broadcast(&p->cond);
+ uv_mutex_unlock(&p->mutex);
+}
+
+bool completion_is_done(struct completion *p)
+{
+ bool ret;
+ uv_mutex_lock(&p->mutex);
+ ret = p->completed;
+ uv_mutex_unlock(&p->mutex);
+ return ret;
+}
diff --git a/libnetdata/completion/completion.h b/libnetdata/completion/completion.h
index 667360a4..723f7368 100644
--- a/libnetdata/completion/completion.h
+++ b/libnetdata/completion/completion.h
@@ -9,6 +9,7 @@ struct completion {
uv_mutex_t mutex;
uv_cond_t cond;
volatile unsigned completed;
+ volatile unsigned completed_jobs;
};
void completion_init(struct completion *p);
@@ -19,4 +20,8 @@ void completion_wait_for(struct completion *p);
void completion_mark_complete(struct completion *p);
+unsigned completion_wait_for_a_job(struct completion *p, unsigned completed_jobs);
+void completion_mark_complete_a_job(struct completion *p);
+bool completion_is_done(struct completion *p);
+
#endif /* NETDATA_COMPLETION_H */
diff --git a/libnetdata/config/README.md b/libnetdata/config/README.md
index 2eccf7a2..c34cf925 100644
--- a/libnetdata/config/README.md
+++ b/libnetdata/config/README.md
@@ -1,6 +1,10 @@
<!--
title: "Netdata ini config files"
custom_edit_url: https://github.com/netdata/netdata/edit/master/libnetdata/config/README.md
+sidebar_label: "Netdata ini config files"
+learn_status: "Published"
+learn_topic_type: "Tasks"
+learn_rel_path: "Developers/libnetdata libraries"
-->
# Netdata ini config files
diff --git a/libnetdata/dictionary/README.md b/libnetdata/dictionary/README.md
index 6d7e5539..508c4e03 100644
--- a/libnetdata/dictionary/README.md
+++ b/libnetdata/dictionary/README.md
@@ -1,5 +1,9 @@
<!--
custom_edit_url: https://github.com/netdata/netdata/edit/master/libnetdata/dictionary/README.md
+sidebar_label: "Dictionaries"
+learn_status: "Published"
+learn_topic_type: "Tasks"
+learn_rel_path: "Developers/libnetdata libraries"
-->
# Dictionaries
diff --git a/libnetdata/dictionary/dictionary.c b/libnetdata/dictionary/dictionary.c
index 0277e067..061b671a 100644
--- a/libnetdata/dictionary/dictionary.c
+++ b/libnetdata/dictionary/dictionary.c
@@ -143,6 +143,8 @@ struct dictionary {
DICT_OPTIONS options; // the configuration flags of the dictionary (they never change - no atomics)
DICT_FLAGS flags; // run time flags for the dictionary (they change all the time - atomics needed)
+ ARAL *value_aral;
+
struct { // support for multiple indexing engines
Pvoid_t JudyHSArray; // the hash table
netdata_rwlock_t rwlock; // protect the index
@@ -179,7 +181,9 @@ struct dictionary {
#endif
};
+// ----------------------------------------------------------------------------
// forward definitions of functions used in reverse order in the code
+
static void garbage_collect_pending_deletes(DICTIONARY *dict);
static inline void item_linked_list_remove(DICTIONARY *dict, DICTIONARY_ITEM *item);
static size_t dict_item_free_with_hooks(DICTIONARY *dict, DICTIONARY_ITEM *item);
@@ -260,7 +264,7 @@ static inline void pointer_del(DICTIONARY *dict __maybe_unused, DICTIONARY_ITEM
static inline void DICTIONARY_STATS_PLUS_MEMORY(DICTIONARY *dict, size_t key_size, size_t item_size, size_t value_size) {
if(key_size)
- __atomic_fetch_add(&dict->stats->memory.indexed, (long)key_size, __ATOMIC_RELAXED);
+ __atomic_fetch_add(&dict->stats->memory.index, (long)JUDYHS_INDEX_SIZE_ESTIMATE(key_size), __ATOMIC_RELAXED);
if(item_size)
__atomic_fetch_add(&dict->stats->memory.dict, (long)item_size, __ATOMIC_RELAXED);
@@ -270,7 +274,7 @@ static inline void DICTIONARY_STATS_PLUS_MEMORY(DICTIONARY *dict, size_t key_siz
}
static inline void DICTIONARY_STATS_MINUS_MEMORY(DICTIONARY *dict, size_t key_size, size_t item_size, size_t value_size) {
if(key_size)
- __atomic_fetch_sub(&dict->stats->memory.indexed, (long)key_size, __ATOMIC_RELAXED);
+ __atomic_fetch_sub(&dict->stats->memory.index, (long)JUDYHS_INDEX_SIZE_ESTIMATE(key_size), __ATOMIC_RELAXED);
if(item_size)
__atomic_fetch_sub(&dict->stats->memory.dict, (long)item_size, __ATOMIC_RELAXED);
@@ -380,7 +384,7 @@ size_t dictionary_referenced_items(DICTIONARY *dict) {
long int dictionary_stats_for_registry(DICTIONARY *dict) {
if(unlikely(!dict)) return 0;
- return (dict->stats->memory.indexed + dict->stats->memory.dict);
+ return (dict->stats->memory.index + dict->stats->memory.dict);
}
void dictionary_version_increment(DICTIONARY *dict) {
__atomic_fetch_add(&dict->version, 1, __ATOMIC_SEQ_CST);
@@ -789,7 +793,7 @@ static void garbage_collect_pending_deletes(DICTIONARY *dict) {
// we didn't get a reference
if(item_is_not_referenced_and_can_be_removed(dict, item)) {
- DOUBLE_LINKED_LIST_REMOVE_UNSAFE(dict->items.list, item, prev, next);
+ DOUBLE_LINKED_LIST_REMOVE_ITEM_UNSAFE(dict->items.list, item, prev, next);
dict_item_free_with_hooks(dict, item);
deleted++;
@@ -1167,9 +1171,9 @@ static inline void item_linked_list_add(DICTIONARY *dict, DICTIONARY_ITEM *item)
ll_recursive_lock(dict, DICTIONARY_LOCK_WRITE);
if(dict->options & DICT_OPTION_ADD_IN_FRONT)
- DOUBLE_LINKED_LIST_PREPEND_UNSAFE(dict->items.list, item, prev, next);
+ DOUBLE_LINKED_LIST_PREPEND_ITEM_UNSAFE(dict->items.list, item, prev, next);
else
- DOUBLE_LINKED_LIST_APPEND_UNSAFE(dict->items.list, item, prev, next);
+ DOUBLE_LINKED_LIST_APPEND_ITEM_UNSAFE(dict->items.list, item, prev, next);
#ifdef NETDATA_INTERNAL_CHECKS
item->ll_adder_pid = gettid();
@@ -1186,7 +1190,7 @@ static inline void item_linked_list_add(DICTIONARY *dict, DICTIONARY_ITEM *item)
static inline void item_linked_list_remove(DICTIONARY *dict, DICTIONARY_ITEM *item) {
ll_recursive_lock(dict, DICTIONARY_LOCK_WRITE);
- DOUBLE_LINKED_LIST_REMOVE_UNSAFE(dict->items.list, item, prev, next);
+ DOUBLE_LINKED_LIST_REMOVE_ITEM_UNSAFE(dict->items.list, item, prev, next);
#ifdef NETDATA_INTERNAL_CHECKS
item->ll_remover_pid = gettid();
@@ -1234,11 +1238,45 @@ static inline size_t item_get_name_len(const DICTIONARY_ITEM *item) {
return strlen(item->caller_name);
}
+static ARAL *dict_items_aral = NULL;
+static ARAL *dict_shared_items_aral = NULL;
+
+void dictionary_static_items_aral_init(void) {
+ static SPINLOCK spinlock;
+
+ if(unlikely(!dict_items_aral || !dict_shared_items_aral)) {
+ netdata_spinlock_lock(&spinlock);
+
+ // we have to check again
+ if(!dict_items_aral)
+ dict_items_aral = aral_create(
+ "dict-items",
+ sizeof(DICTIONARY_ITEM),
+ 0,
+ 65536,
+ aral_by_size_statistics(),
+ NULL, NULL, false, false);
+
+ // we have to check again
+ if(!dict_shared_items_aral)
+ dict_shared_items_aral = aral_create(
+ "dict-shared-items",
+ sizeof(DICTIONARY_ITEM_SHARED),
+ 0,
+ 65536,
+ aral_by_size_statistics(),
+ NULL, NULL, false, false);
+
+ netdata_spinlock_unlock(&spinlock);
+ }
+}
+
static DICTIONARY_ITEM *dict_item_create(DICTIONARY *dict __maybe_unused, size_t *allocated_bytes, DICTIONARY_ITEM *master_item) {
DICTIONARY_ITEM *item;
size_t size = sizeof(DICTIONARY_ITEM);
- item = callocz(1, size);
+ item = aral_mallocz(dict_items_aral);
+ memset(item, 0, sizeof(DICTIONARY_ITEM));
#ifdef NETDATA_INTERNAL_CHECKS
item->creator_pid = gettid();
@@ -1257,7 +1295,9 @@ static DICTIONARY_ITEM *dict_item_create(DICTIONARY *dict __maybe_unused, size_t
}
else {
size = sizeof(DICTIONARY_ITEM_SHARED);
- item->shared = callocz(1, size);
+ item->shared = aral_mallocz(dict_shared_items_aral);
+ memset(item->shared, 0, sizeof(DICTIONARY_ITEM_SHARED));
+
item->shared->links = 1;
*allocated_bytes += size;
}
@@ -1268,20 +1308,39 @@ static DICTIONARY_ITEM *dict_item_create(DICTIONARY *dict __maybe_unused, size_t
return item;
}
-static void *dict_item_value_create(void *value, size_t value_len) {
+static inline void *dict_item_value_mallocz(DICTIONARY *dict, size_t value_len) {
+ if(dict->value_aral) {
+ internal_fatal(aral_element_size(dict->value_aral) != value_len,
+ "DICTIONARY: item value size %zu does not match the configured fixed one %zu",
+ value_len, aral_element_size(dict->value_aral));
+ return aral_mallocz(dict->value_aral);
+ }
+ else
+ return mallocz(value_len);
+}
+
+static inline void dict_item_value_freez(DICTIONARY *dict, void *ptr) {
+ if(dict->value_aral)
+ aral_freez(dict->value_aral, ptr);
+ else
+ freez(ptr);
+}
+
+static void *dict_item_value_create(DICTIONARY *dict, void *value, size_t value_len) {
void *ptr = NULL;
if(likely(value_len)) {
if (likely(value)) {
// a value has been supplied
// copy it
- ptr = mallocz(value_len);
+ ptr = dict_item_value_mallocz(dict, value_len);
memcpy(ptr, value, value_len);
}
else {
// no value has been supplied
// allocate a clear memory block
- ptr = callocz(1, value_len);
+ ptr = dict_item_value_mallocz(dict, value_len);
+ memset(ptr, 0, value_len);
}
}
// else
@@ -1320,7 +1379,7 @@ static DICTIONARY_ITEM *dict_item_create_with_hooks(DICTIONARY *dict, const char
if(unlikely(dict->options & DICT_OPTION_VALUE_LINK_DONT_CLONE))
item->shared->value = value;
else
- item->shared->value = dict_item_value_create(value, value_len);
+ item->shared->value = dict_item_value_create(dict, value, value_len);
item->shared->value_len = value_len;
value_size += value_len;
@@ -1360,7 +1419,7 @@ static void dict_item_reset_value_with_hooks(DICTIONARY *dict, DICTIONARY_ITEM *
void *old_value = item->shared->value;
void *new_value = NULL;
if(value_len) {
- new_value = mallocz(value_len);
+ new_value = dict_item_value_mallocz(dict, value_len);
if(value) memcpy(new_value, value, value_len);
else memset(new_value, 0, value_len);
}
@@ -1368,7 +1427,7 @@ static void dict_item_reset_value_with_hooks(DICTIONARY *dict, DICTIONARY_ITEM *
item->shared->value_len = value_len;
debug(D_DICTIONARY, "Dictionary: freeing old value of '%s'", item_get_name(item));
- freez(old_value);
+ dict_item_value_freez(dict, old_value);
}
dictionary_execute_insert_callback(dict, item, constructor_data);
@@ -1391,17 +1450,18 @@ static size_t dict_item_free_with_hooks(DICTIONARY *dict, DICTIONARY_ITEM *item)
if(unlikely(!(dict->options & DICT_OPTION_VALUE_LINK_DONT_CLONE))) {
debug(D_DICTIONARY, "Dictionary freeing value of '%s'", item_get_name(item));
- freez(item->shared->value);
+ dict_item_value_freez(dict, item->shared->value);
item->shared->value = NULL;
}
value_size += item->shared->value_len;
- freez(item->shared);
+ aral_freez(dict_shared_items_aral, item->shared);
item->shared = NULL;
item_size += sizeof(DICTIONARY_ITEM_SHARED);
}
- freez(item);
+ aral_freez(dict_items_aral, item);
+
item_size += sizeof(DICTIONARY_ITEM);
DICTIONARY_STATS_MINUS_MEMORY(dict, key_size, item_size, value_size);
@@ -1749,6 +1809,9 @@ static bool dictionary_free_all_resources(DICTIONARY *dict, size_t *mem, bool fo
dict_size += sizeof(DICTIONARY);
DICTIONARY_STATS_MINUS_MEMORY(dict, 0, sizeof(DICTIONARY), 0);
+ if(dict->value_aral)
+ aral_by_size_release(dict->value_aral);
+
freez(dict);
internal_error(
@@ -1934,19 +1997,34 @@ static bool api_is_name_good_with_trace(DICTIONARY *dict __maybe_unused, const c
// ----------------------------------------------------------------------------
// API - dictionary management
-static DICTIONARY *dictionary_create_internal(DICT_OPTIONS options, struct dictionary_stats *stats) {
+static DICTIONARY *dictionary_create_internal(DICT_OPTIONS options, struct dictionary_stats *stats, size_t fixed_size) {
cleanup_destroyed_dictionaries();
DICTIONARY *dict = callocz(1, sizeof(DICTIONARY));
dict->options = options;
dict->stats = stats;
+ if((dict->options & DICT_OPTION_FIXED_SIZE) && !fixed_size) {
+ dict->options &= ~DICT_OPTION_FIXED_SIZE;
+ internal_fatal(true, "DICTIONARY: requested fixed size dictionary, without setting the size");
+ }
+ if(!(dict->options & DICT_OPTION_FIXED_SIZE) && fixed_size) {
+ dict->options |= DICT_OPTION_FIXED_SIZE;
+ internal_fatal(true, "DICTIONARY: set a fixed size for the items, without setting DICT_OPTION_FIXED_SIZE flag");
+ }
+
+ if(dict->options & DICT_OPTION_FIXED_SIZE)
+ dict->value_aral = aral_by_size_acquire(fixed_size);
+ else
+ dict->value_aral = NULL;
+
size_t dict_size = 0;
dict_size += sizeof(DICTIONARY);
dict_size += dictionary_locks_init(dict);
dict_size += reference_counter_init(dict);
dict_size += hashtable_init_unsafe(dict);
+ dictionary_static_items_aral_init();
pointer_index_init(dict);
DICTIONARY_STATS_PLUS_MEMORY(dict, 0, dict_size, 0);
@@ -1955,12 +2033,12 @@ static DICTIONARY *dictionary_create_internal(DICT_OPTIONS options, struct dicti
}
#ifdef NETDATA_INTERNAL_CHECKS
-DICTIONARY *dictionary_create_advanced_with_trace(DICT_OPTIONS options, struct dictionary_stats *stats, const char *function, size_t line, const char *file) {
+DICTIONARY *dictionary_create_advanced_with_trace(DICT_OPTIONS options, struct dictionary_stats *stats, size_t fixed_size, const char *function, size_t line, const char *file) {
#else
-DICTIONARY *dictionary_create_advanced(DICT_OPTIONS options, struct dictionary_stats *stats) {
+DICTIONARY *dictionary_create_advanced(DICT_OPTIONS options, struct dictionary_stats *stats, size_t fixed_size) {
#endif
- DICTIONARY *dict = dictionary_create_internal(options, stats?stats:&dictionary_stats_category_other);
+ DICTIONARY *dict = dictionary_create_internal(options, stats?stats:&dictionary_stats_category_other, fixed_size);
#ifdef NETDATA_INTERNAL_CHECKS
dict->creation_function = function;
@@ -1978,7 +2056,9 @@ DICTIONARY *dictionary_create_view_with_trace(DICTIONARY *master, const char *fu
DICTIONARY *dictionary_create_view(DICTIONARY *master) {
#endif
- DICTIONARY *dict = dictionary_create_internal(master->options, master->stats);
+ DICTIONARY *dict = dictionary_create_internal(master->options, master->stats,
+ master->value_aral ? aral_element_size(master->value_aral) : 0);
+
dict->master = master;
dictionary_hooks_allocate(master);
@@ -3295,7 +3375,7 @@ static int dictionary_unittest_view_threads() {
// threads testing of dictionary
struct dictionary_stats stats_master = {};
struct dictionary_stats stats_view = {};
- tv.master = dictionary_create_advanced(DICT_OPTION_NAME_LINK_DONT_CLONE | DICT_OPTION_DONT_OVERWRITE_VALUE, &stats_master);
+ tv.master = dictionary_create_advanced(DICT_OPTION_NAME_LINK_DONT_CLONE | DICT_OPTION_DONT_OVERWRITE_VALUE, &stats_master, 0);
tv.view = dictionary_create_view(tv.master);
tv.view->stats = &stats_view;
@@ -3388,7 +3468,7 @@ static int dictionary_unittest_view_threads() {
size_t dictionary_unittest_views(void) {
size_t errors = 0;
struct dictionary_stats stats = {};
- DICTIONARY *master = dictionary_create_advanced(DICT_OPTION_NONE, &stats);
+ DICTIONARY *master = dictionary_create_advanced(DICT_OPTION_NONE, &stats, 0);
DICTIONARY *view = dictionary_create_view(master);
fprintf(stderr, "\n\nChecking dictionary views...\n");
diff --git a/libnetdata/dictionary/dictionary.h b/libnetdata/dictionary/dictionary.h
index 0e7b3d39..58220def 100644
--- a/libnetdata/dictionary/dictionary.h
+++ b/libnetdata/dictionary/dictionary.h
@@ -53,6 +53,7 @@ typedef enum dictionary_options {
DICT_OPTION_NAME_LINK_DONT_CLONE = (1 << 2), // don't copy the name, just point to the one provided (default: copy)
DICT_OPTION_DONT_OVERWRITE_VALUE = (1 << 3), // don't overwrite values of dictionary items (default: overwrite)
DICT_OPTION_ADD_IN_FRONT = (1 << 4), // add dictionary items at the front of the linked list (default: at the end)
+ DICT_OPTION_FIXED_SIZE = (1 << 5), // the items of the dictionary have a fixed size
} DICT_OPTIONS;
struct dictionary_stats {
@@ -91,7 +92,7 @@ struct dictionary_stats {
// memory
struct {
- long indexed; // bytes of keys indexed (indication of the index size)
+ long index; // bytes of keys indexed (indication of the index size)
long values; // bytes of caller structures
long dict; // bytes of the structures dictionary needs
} memory;
@@ -107,12 +108,12 @@ struct dictionary_stats {
// Create a dictionary
#ifdef NETDATA_INTERNAL_CHECKS
-#define dictionary_create(options) dictionary_create_advanced_with_trace(options, NULL, __FUNCTION__, __LINE__, __FILE__)
-#define dictionary_create_advanced(options, stats) dictionary_create_advanced_with_trace(options, stats, __FUNCTION__, __LINE__, __FILE__)
-DICTIONARY *dictionary_create_advanced_with_trace(DICT_OPTIONS options, struct dictionary_stats *stats, const char *function, size_t line, const char *file);
+#define dictionary_create(options) dictionary_create_advanced_with_trace(options, NULL, 0, __FUNCTION__, __LINE__, __FILE__)
+#define dictionary_create_advanced(options, stats, fixed_size) dictionary_create_advanced_with_trace(options, stats, fixed_size, __FUNCTION__, __LINE__, __FILE__)
+DICTIONARY *dictionary_create_advanced_with_trace(DICT_OPTIONS options, struct dictionary_stats *stats, size_t fixed_size, const char *function, size_t line, const char *file);
#else
-#define dictionary_create(options) dictionary_create_advanced(options, NULL);
-DICTIONARY *dictionary_create_advanced(DICT_OPTIONS options, struct dictionary_stats *stats);
+#define dictionary_create(options) dictionary_create_advanced(options, NULL, 0);
+DICTIONARY *dictionary_create_advanced(DICT_OPTIONS options, struct dictionary_stats *stats, size_t fixed_size);
#endif
// Create a view on a dictionary
diff --git a/libnetdata/ebpf/README.md b/libnetdata/ebpf/README.md
index 534867f3..c2dabe10 100644
--- a/libnetdata/ebpf/README.md
+++ b/libnetdata/ebpf/README.md
@@ -1,5 +1,13 @@
<!--
+title "eBPF"
custom_edit_url: https://github.com/netdata/netdata/edit/master/libnetdata/ebpf/README.md
+sidebar_label: "eBPF"
+learn_status: "Published"
+learn_topic_type: "Tasks"
+learn_rel_path: "Developers/libnetdata libraries"
-->
+# eBPF library
+
+Netdata's eBPF library supports the [eBPF collector](https://github.com/netdata/netdata/blob/master/collectors/ebpf.plugin/README.md).
diff --git a/libnetdata/ebpf/ebpf.c b/libnetdata/ebpf/ebpf.c
index 382485e5..7cad5978 100644
--- a/libnetdata/ebpf/ebpf.c
+++ b/libnetdata/ebpf/ebpf.c
@@ -809,7 +809,7 @@ static void ebpf_select_mode_string(char *output, size_t len, netdata_run_mode_t
*
* Convert the string given as argument to value present in enum.
*
- * @param str value read from configuraion file.
+ * @param str value read from configuration file.
*
* @return It returns the value to be used.
*/
@@ -901,7 +901,7 @@ netdata_ebpf_program_loaded_t ebpf_convert_core_type(char *str, netdata_run_mode
/**
* Adjust Thread Load
*
- * Adjust thread configuraton according specified load.
+ * Adjust thread configuration according specified load.
*
* @param mod the main structure that will be adjusted.
* @param file the btf file used with thread.
@@ -1060,7 +1060,7 @@ static netdata_ebpf_load_mode_t ebpf_select_load_mode(struct btf *btf_file, netd
* Update configuration for a specific thread.
*
* @param modules structure that will be updated
- * @oaram origin specify the configuration file loaded
+ * @param origin specify the configuration file loaded
* @param btf_file a pointer to the loaded btf file.
* @param is_rhf is Red Hat family?
*/
@@ -1124,7 +1124,7 @@ void ebpf_update_module(ebpf_module_t *em, struct btf *btf_file, int kver, int i
error("Cannot load the ebpf configuration file %s", em->config_file);
return;
}
- // If user defined data globaly, we will have here EBPF_LOADED_FROM_USER, we need to consider this, to avoid
+ // If user defined data globally, we will have here EBPF_LOADED_FROM_USER, we need to consider this, to avoid
// forcing users to configure thread by thread.
origin = (!(em->load & NETDATA_EBPF_LOAD_SOURCE)) ? EBPF_LOADED_FROM_STOCK : em->load & NETDATA_EBPF_LOAD_SOURCE;
} else
@@ -1139,7 +1139,7 @@ void ebpf_update_module(ebpf_module_t *em, struct btf *btf_file, int kver, int i
* Apps and cgroup has internal cleanup that needs attaching tracers to release_task, to avoid overload the function
* we will enable this integration by default, if and only if, we are running with trampolines.
*
- * @param em a poiter to the main thread structure.
+ * @param em a pointer to the main thread structure.
* @param mode is the mode used with different
*/
void ebpf_adjust_apps_cgroup(ebpf_module_t *em, netdata_ebpf_program_loaded_t mode)
@@ -1160,7 +1160,8 @@ void ebpf_adjust_apps_cgroup(ebpf_module_t *em, netdata_ebpf_program_loaded_t mo
* Helper used to get address from /proc/kallsym
*
* @param fa address structure
- * @param fd file descriptor loaded inside kernel.
+ * @param fd file descriptor loaded inside kernel. If a negative value is given
+ * the function will load address and it won't update hash table.
*/
void ebpf_load_addresses(ebpf_addresses_t *fa, int fd)
{
@@ -1182,11 +1183,15 @@ void ebpf_load_addresses(ebpf_addresses_t *fa, int fd)
char *fcnt = procfile_lineword(ff, l, 2);
uint32_t hash = simple_hash(fcnt);
if (fa->hash == hash && !strcmp(fcnt, fa->function)) {
- char addr[128];
- snprintf(addr, 127, "0x%s", procfile_lineword(ff, l, 0));
- fa->addr = (unsigned long) strtoul(addr, NULL, 16);
- uint32_t key = 0;
- bpf_map_update_elem(fd, &key, &fa->addr, BPF_ANY);
+ if (fd > 0) {
+ char addr[128];
+ snprintf(addr, 127, "0x%s", procfile_lineword(ff, l, 0));
+ fa->addr = (unsigned long) strtoul(addr, NULL, 16);
+ uint32_t key = 0;
+ bpf_map_update_elem(fd, &key, &fa->addr, BPF_ANY);
+ } else
+ fa->addr = 1;
+ break;
}
}
diff --git a/libnetdata/ebpf/ebpf.h b/libnetdata/ebpf/ebpf.h
index 5cff5134..cf3fa7cc 100644
--- a/libnetdata/ebpf/ebpf.h
+++ b/libnetdata/ebpf/ebpf.h
@@ -206,7 +206,7 @@ typedef struct ebpf_specify_name {
typedef enum netdata_ebpf_load_mode {
EBPF_LOAD_LEGACY = 1<<0, // Select legacy mode, this means we will load binaries
- EBPF_LOAD_CORE = 1<<1, // When CO-RE is used, it is necessary to use the souce code
+ EBPF_LOAD_CORE = 1<<1, // When CO-RE is used, it is necessary to use the source code
EBPF_LOAD_PLAY_DICE = 1<<2, // Take a look on environment and choose the best option
EBPF_LOADED_FROM_STOCK = 1<<3, // Configuration loaded from Stock file
EBPF_LOADED_FROM_USER = 1<<4 // Configuration loaded from user
diff --git a/libnetdata/eval/eval.c b/libnetdata/eval/eval.c
index 0e429a08..c7570bd2 100644
--- a/libnetdata/eval/eval.c
+++ b/libnetdata/eval/eval.c
@@ -1126,7 +1126,7 @@ EVAL_EXPRESSION *expression_parse(const char *string, const char **failed_at, in
return NULL;
}
- BUFFER *out = buffer_create(1024);
+ BUFFER *out = buffer_create(1024, NULL);
print_parsed_as_node(out, op, &err);
if(err != EVAL_ERROR_OK) {
error("failed to re-generate expression '%s' with reason: %s", string, expression_strerror(err));
@@ -1141,7 +1141,7 @@ EVAL_EXPRESSION *expression_parse(const char *string, const char **failed_at, in
exp->parsed_as = strdupz(buffer_tostring(out));
buffer_free(out);
- exp->error_msg = buffer_create(100);
+ exp->error_msg = buffer_create(100, NULL);
exp->nodes = (void *)op;
return exp;
diff --git a/libnetdata/json/README.md b/libnetdata/json/README.md
index 2e04b8b6..e772f114 100644
--- a/libnetdata/json/README.md
+++ b/libnetdata/json/README.md
@@ -1,6 +1,10 @@
<!--
title: "json"
custom_edit_url: https://github.com/netdata/netdata/edit/master/libnetdata/json/README.md
+sidebar_label: "json"
+learn_status: "Published"
+learn_topic_type: "Tasks"
+learn_rel_path: "Developers/libnetdata libraries"
-->
# json
diff --git a/libnetdata/json/json.c b/libnetdata/json/json.c
index d5f62eda..532b677c 100644
--- a/libnetdata/json/json.c
+++ b/libnetdata/json/json.c
@@ -90,7 +90,7 @@ jsmntok_t *json_tokenise(char *js, size_t len, size_t *count)
*/
int json_callback_print(JSON_ENTRY *e)
{
- BUFFER *wb=buffer_create(300);
+ BUFFER *wb=buffer_create(300, NULL);
buffer_sprintf(wb,"%s = ", e->name);
char txt[50];
diff --git a/libnetdata/july/Makefile.am b/libnetdata/july/Makefile.am
new file mode 100644
index 00000000..161784b8
--- /dev/null
+++ b/libnetdata/july/Makefile.am
@@ -0,0 +1,8 @@
+# SPDX-License-Identifier: GPL-3.0-or-later
+
+AUTOMAKE_OPTIONS = subdir-objects
+MAINTAINERCLEANFILES = $(srcdir)/Makefile.in
+
+dist_noinst_DATA = \
+ README.md \
+ $(NULL)
diff --git a/libnetdata/july/README.md b/libnetdata/july/README.md
new file mode 100644
index 00000000..df2a3d38
--- /dev/null
+++ b/libnetdata/july/README.md
@@ -0,0 +1,14 @@
+<!--
+custom_edit_url: https://github.com/netdata/netdata/edit/master/libnetdata/july/README.md
+sidebar_label: "July interface"
+learn_status: "Published"
+learn_topic_type: "Tasks"
+learn_rel_path: "Developers/libnetdata libraries"
+-->
+
+
+# July
+
+An interface similar to `Judy` that uses minimal allocations (that can be cached)
+for items that are mainly appended (just a few insertions in the middle)
+
diff --git a/libnetdata/july/july.c b/libnetdata/july/july.c
new file mode 100644
index 00000000..0ad5f13e
--- /dev/null
+++ b/libnetdata/july/july.c
@@ -0,0 +1,453 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "july.h"
+
+#define JULYL_MIN_ENTRIES 10
+
+struct JulyL_item {
+ Word_t index;
+ void *value;
+};
+
+struct JulyL {
+ size_t entries;
+ size_t used;
+
+ // statistics
+ size_t bytes;
+ size_t bytes_moved;
+ size_t reallocs;
+
+ struct {
+ struct JulyL *prev;
+ struct JulyL *next;
+ } cache;
+
+ struct JulyL_item array[];
+};
+
+// ----------------------------------------------------------------------------
+// JulyL cache
+
+static struct {
+ struct {
+ SPINLOCK spinlock;
+ struct JulyL *available_items;
+ size_t available;
+ } protected;
+
+ struct {
+ size_t bytes;
+ size_t allocated;
+ size_t bytes_moved;
+ size_t reallocs;
+ } atomics;
+} julyl_globals = {
+ .protected = {
+ .spinlock = NETDATA_SPINLOCK_INITIALIZER,
+ .available_items = NULL,
+ .available = 0,
+ },
+ .atomics = {
+ .bytes = 0,
+ .allocated = 0,
+ .bytes_moved = 0,
+ .reallocs = 0,
+ },
+};
+
+void julyl_cleanup1(void) {
+ struct JulyL *item = NULL;
+
+ if(!netdata_spinlock_trylock(&julyl_globals.protected.spinlock))
+ return;
+
+ if(julyl_globals.protected.available_items && julyl_globals.protected.available > 10) {
+ item = julyl_globals.protected.available_items;
+ DOUBLE_LINKED_LIST_REMOVE_ITEM_UNSAFE(julyl_globals.protected.available_items, item, cache.prev, cache.next);
+ julyl_globals.protected.available--;
+ }
+
+ netdata_spinlock_unlock(&julyl_globals.protected.spinlock);
+
+ if(item) {
+ size_t bytes = item->bytes;
+ freez(item);
+ __atomic_sub_fetch(&julyl_globals.atomics.bytes, bytes, __ATOMIC_RELAXED);
+ __atomic_sub_fetch(&julyl_globals.atomics.allocated, 1, __ATOMIC_RELAXED);
+ }
+}
+
+struct JulyL *julyl_get(void) {
+ struct JulyL *j;
+
+ netdata_spinlock_lock(&julyl_globals.protected.spinlock);
+
+ j = julyl_globals.protected.available_items;
+ if(likely(j)) {
+ DOUBLE_LINKED_LIST_REMOVE_ITEM_UNSAFE(julyl_globals.protected.available_items, j, cache.prev, cache.next);
+ julyl_globals.protected.available--;
+ }
+
+ netdata_spinlock_unlock(&julyl_globals.protected.spinlock);
+
+ if(unlikely(!j)) {
+ size_t bytes = sizeof(struct JulyL) + JULYL_MIN_ENTRIES * sizeof(struct JulyL_item);
+ j = mallocz(bytes);
+ j->bytes = bytes;
+ j->entries = JULYL_MIN_ENTRIES;
+ __atomic_add_fetch(&julyl_globals.atomics.bytes, bytes, __ATOMIC_RELAXED);
+ __atomic_add_fetch(&julyl_globals.atomics.allocated, 1, __ATOMIC_RELAXED);
+ }
+
+ j->used = 0;
+ j->bytes_moved = 0;
+ j->reallocs = 0;
+ j->cache.next = j->cache.prev = NULL;
+ return j;
+}
+
+static void julyl_release(struct JulyL *j) {
+ if(unlikely(!j)) return;
+
+ __atomic_add_fetch(&julyl_globals.atomics.bytes_moved, j->bytes_moved, __ATOMIC_RELAXED);
+ __atomic_add_fetch(&julyl_globals.atomics.reallocs, j->reallocs, __ATOMIC_RELAXED);
+
+ netdata_spinlock_lock(&julyl_globals.protected.spinlock);
+ DOUBLE_LINKED_LIST_APPEND_ITEM_UNSAFE(julyl_globals.protected.available_items, j, cache.prev, cache.next);
+ julyl_globals.protected.available++;
+ netdata_spinlock_unlock(&julyl_globals.protected.spinlock);
+}
+
+size_t julyl_cache_size(void) {
+ return __atomic_load_n(&julyl_globals.atomics.bytes, __ATOMIC_RELAXED);
+}
+
+size_t julyl_bytes_moved(void) {
+ return __atomic_load_n(&julyl_globals.atomics.bytes_moved, __ATOMIC_RELAXED);
+}
+
+// ----------------------------------------------------------------------------
+// JulyL
+
+size_t JulyLGet_binary_search_position_of_index(const struct JulyL *July, Word_t Index) {
+ // return the position of the first item >= Index
+
+ size_t left = 0;
+ size_t right = July->used;
+ while(left < right) {
+ size_t middle = (left + right) >> 1;
+
+ if(July->array[middle].index > Index)
+ right = middle;
+
+ else
+ left = middle + 1;
+ }
+
+ internal_fatal(left > July->used, "JULY: invalid position returned");
+
+ if(left > 0 && July->array[left - 1].index == Index)
+ return left - 1;
+
+ internal_fatal( (left < July->used && July->array[left].index < Index) ||
+ (left > 0 && July->array[left - 1].index >= Index)
+ , "JULY: wrong item returned");
+
+ return left;
+}
+
+PPvoid_t JulyLGet(Pcvoid_t PArray, Word_t Index, PJError_t PJError __maybe_unused) {
+ const struct JulyL *July = PArray;
+ if(!July)
+ return NULL;
+
+ size_t pos = JulyLGet_binary_search_position_of_index(July, Index);
+
+ if(unlikely(pos >= July->used || July->array[pos].index != Index))
+ return NULL;
+
+ return (PPvoid_t)&July->array[pos].value;
+}
+
+PPvoid_t JulyLIns(PPvoid_t PPArray, Word_t Index, PJError_t PJError __maybe_unused) {
+ struct JulyL *July = *PPArray;
+ if(unlikely(!July)) {
+ July = julyl_get();
+ July->used = 0;
+ *PPArray = July;
+ }
+
+ size_t pos = JulyLGet_binary_search_position_of_index(July, Index);
+
+ if((pos == July->used || July->array[pos].index != Index)) {
+ // we have to add this entry
+
+ if (unlikely(July->used == July->entries)) {
+ // we have to expand the array
+ size_t bytes = sizeof(struct JulyL) + July->entries * 2 * sizeof(struct JulyL_item);
+ __atomic_add_fetch(&julyl_globals.atomics.bytes, bytes - July->bytes, __ATOMIC_RELAXED);
+ July = reallocz(July, bytes);
+ July->bytes = bytes;
+ July->entries *= 2;
+ July->reallocs++;
+ *PPArray = July;
+ }
+
+ if (unlikely(pos != July->used)) {
+ // we have to shift some members to make room
+ size_t size = (July->used - pos) * sizeof(struct JulyL_item);
+ memmove(&July->array[pos + 1], &July->array[pos], size);
+ July->bytes_moved += size;
+ }
+
+ July->used++;
+ July->array[pos].value = NULL;
+ July->array[pos].index = Index;
+ }
+
+ return &July->array[pos].value;
+}
+
+PPvoid_t JulyLFirst(Pcvoid_t PArray, Word_t *Index, PJError_t PJError __maybe_unused) {
+ const struct JulyL *July = PArray;
+ if(!July)
+ return NULL;
+
+ size_t pos = JulyLGet_binary_search_position_of_index(July, *Index);
+ // pos is >= Index
+
+ if(unlikely(pos == July->used))
+ return NULL;
+
+ *Index = July->array[pos].index;
+ return (PPvoid_t)&July->array[pos].value;
+}
+
+PPvoid_t JulyLNext(Pcvoid_t PArray, Word_t *Index, PJError_t PJError __maybe_unused) {
+ const struct JulyL *July = PArray;
+ if(!July)
+ return NULL;
+
+ size_t pos = JulyLGet_binary_search_position_of_index(July, *Index);
+ // pos is >= Index
+
+ if(unlikely(pos == July->used))
+ return NULL;
+
+ if(July->array[pos].index == *Index) {
+ pos++;
+
+ if(unlikely(pos == July->used))
+ return NULL;
+ }
+
+ *Index = July->array[pos].index;
+ return (PPvoid_t)&July->array[pos].value;
+}
+
+PPvoid_t JulyLLast(Pcvoid_t PArray, Word_t *Index, PJError_t PJError __maybe_unused) {
+ const struct JulyL *July = PArray;
+ if(!July)
+ return NULL;
+
+ size_t pos = JulyLGet_binary_search_position_of_index(July, *Index);
+ // pos is >= Index
+
+ if(pos > 0 && (pos == July->used || July->array[pos].index > *Index))
+ pos--;
+
+ if(unlikely(pos == 0 && July->array[0].index > *Index))
+ return NULL;
+
+ *Index = July->array[pos].index;
+ return (PPvoid_t)&July->array[pos].value;
+}
+
+PPvoid_t JulyLPrev(Pcvoid_t PArray, Word_t *Index, PJError_t PJError __maybe_unused) {
+ const struct JulyL *July = PArray;
+ if(!July)
+ return NULL;
+
+ size_t pos = JulyLGet_binary_search_position_of_index(July, *Index);
+ // pos is >= Index
+
+ if(unlikely(pos == 0 || July->used == 0))
+ return NULL;
+
+ // get the previous one
+ pos--;
+
+ *Index = July->array[pos].index;
+ return (PPvoid_t)&July->array[pos].value;
+}
+
+Word_t JulyLFreeArray(PPvoid_t PPArray, PJError_t PJError __maybe_unused) {
+ struct JulyL *July = *PPArray;
+ if(unlikely(!July))
+ return 0;
+
+ size_t bytes = July->bytes;
+ julyl_release(July);
+ *PPArray = NULL;
+ return bytes;
+}
+
+// ----------------------------------------------------------------------------
+// unittest
+
+#define item_index(i) (((i) * 2) + 100)
+
+int julytest(void) {
+ Word_t entries = 10000;
+ Pvoid_t array = NULL;
+
+ // test additions
+ for(Word_t i = 0; i < entries ;i++) {
+ Pvoid_t *PValue = JulyLIns(&array, item_index(i), PJE0);
+ if(!PValue)
+ fatal("JULY: cannot insert item %lu", item_index(i));
+
+ *PValue = (void *)(item_index(i));
+ }
+
+ // test successful finds
+ for(Word_t i = 0; i < entries ;i++) {
+ Pvoid_t *PValue = JulyLGet(array, item_index(i), PJE0);
+ if(!PValue)
+ fatal("JULY: cannot find item %lu", item_index(i));
+
+ if(*PValue != (void *)(item_index(i)))
+ fatal("JULY: item %lu has the value %lu", item_index(i), (unsigned long)(*PValue));
+ }
+
+ // test finding the first item
+ for(Word_t i = 0; i < entries ;i++) {
+ Word_t index = item_index(i);
+ Pvoid_t *PValue = JulyLFirst(array, &index, PJE0);
+ if(!PValue)
+ fatal("JULY: cannot find first item %lu", item_index(i));
+
+ if(*PValue != (void *)(item_index(i)))
+ fatal("JULY: item %lu has the value %lu", item_index(i), (unsigned long)(*PValue));
+
+ if(index != item_index(i))
+ fatal("JULY: item %lu has index %lu", item_index(i), index);
+ }
+
+ // test finding the next item
+ for(Word_t i = 0; i < entries - 1 ;i++) {
+ Word_t index = item_index(i);
+ Pvoid_t *PValue = JulyLNext(array, &index, PJE0);
+ if(!PValue)
+ fatal("JULY: cannot find next item %lu", item_index(i));
+
+ if(*PValue != (void *)(item_index(i + 1)))
+ fatal("JULY: item %lu next has the value %lu", item_index(i), (unsigned long)(*PValue));
+
+ if(index != item_index(i + 1))
+ fatal("JULY: item %lu next has index %lu", item_index(i), index);
+ }
+
+ // test finding the last item
+ for(Word_t i = 0; i < entries ;i++) {
+ Word_t index = item_index(i);
+ Pvoid_t *PValue = JulyLLast(array, &index, PJE0);
+ if(!PValue)
+ fatal("JULY: cannot find last item %lu", item_index(i));
+
+ if(*PValue != (void *)(item_index(i)))
+ fatal("JULY: item %lu has the value %lu", item_index(i), (unsigned long)(*PValue));
+
+ if(index != item_index(i))
+ fatal("JULY: item %lu has index %lu", item_index(i), index);
+ }
+
+ // test finding the prev item
+ for(Word_t i = 1; i < entries ;i++) {
+ Word_t index = item_index(i);
+ Pvoid_t *PValue = JulyLPrev(array, &index, PJE0);
+ if(!PValue)
+ fatal("JULY: cannot find prev item %lu", item_index(i));
+
+ if(*PValue != (void *)(item_index(i - 1)))
+ fatal("JULY: item %lu prev has the value %lu", item_index(i), (unsigned long)(*PValue));
+
+ if(index != item_index(i - 1))
+ fatal("JULY: item %lu prev has index %lu", item_index(i), index);
+ }
+
+ // test full traversal forward
+ {
+ Word_t i = 0;
+ Word_t index = 0;
+ bool first = true;
+ Pvoid_t *PValue;
+ while((PValue = JulyLFirstThenNext(array, &index, &first))) {
+ if(*PValue != (void *)(item_index(i)))
+ fatal("JULY: item %lu traversal has the value %lu", item_index(i), (unsigned long)(*PValue));
+
+ if(index != item_index(i))
+ fatal("JULY: item %lu traversal has index %lu", item_index(i), index);
+
+ i++;
+ }
+
+ if(i != entries)
+ fatal("JULY: expected to forward traverse %lu entries, but traversed %lu", entries, i);
+ }
+
+ // test full traversal backward
+ {
+ Word_t i = 0;
+ Word_t index = (Word_t)(-1);
+ bool first = true;
+ Pvoid_t *PValue;
+ while((PValue = JulyLLastThenPrev(array, &index, &first))) {
+ if(*PValue != (void *)(item_index(entries - i - 1)))
+ fatal("JULY: item %lu traversal has the value %lu", item_index(i), (unsigned long)(*PValue));
+
+ if(index != item_index(entries - i - 1))
+ fatal("JULY: item %lu traversal has index %lu", item_index(i), index);
+
+ i++;
+ }
+
+ if(i != entries)
+ fatal("JULY: expected to back traverse %lu entries, but traversed %lu", entries, i);
+ }
+
+ // test finding non-existing first item
+ for(Word_t i = 0; i < entries ;i++) {
+ Word_t index = item_index(i) - 1;
+ Pvoid_t *PValue = JulyLFirst(array, &index, PJE0);
+ if(!PValue)
+ fatal("JULY: cannot find first item %lu", item_index(i) - 1);
+
+ if(*PValue != (void *)(item_index(i)))
+ fatal("JULY: item %lu has the value %lu", item_index(i), (unsigned long)(*PValue));
+
+ if(index != item_index(i))
+ fatal("JULY: item %lu has index %lu", item_index(i), index);
+ }
+
+ // test finding non-existing last item
+ for(Word_t i = 0; i < entries ;i++) {
+ Word_t index = item_index(i) + 1;
+ Pvoid_t *PValue = JulyLLast(array, &index, PJE0);
+ if(!PValue)
+ fatal("JULY: cannot find last item %lu", item_index(i) + 1);
+
+ if(*PValue != (void *)(item_index(i)))
+ fatal("JULY: item %lu has the value %lu", item_index(i), (unsigned long)(*PValue));
+
+ if(index != item_index(i))
+ fatal("JULY: item %lu has index %lu", item_index(i), index);
+ }
+
+ JulyLFreeArray(&array, PJE0);
+
+ return 0;
+}
+
+
diff --git a/libnetdata/july/july.h b/libnetdata/july/july.h
new file mode 100644
index 00000000..672ed44e
--- /dev/null
+++ b/libnetdata/july/july.h
@@ -0,0 +1,40 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#ifndef NETDATA_JULY_H
+#define NETDATA_JULY_H 1
+
+#include "../libnetdata.h"
+
+// #define PDC_USE_JULYL 1
+
+PPvoid_t JulyLGet(Pcvoid_t PArray, Word_t Index, PJError_t PJError);
+PPvoid_t JulyLIns(PPvoid_t PPArray, Word_t Index, PJError_t PJError);
+PPvoid_t JulyLFirst(Pcvoid_t PArray, Word_t *Index, PJError_t PJError);
+PPvoid_t JulyLNext(Pcvoid_t PArray, Word_t *Index, PJError_t PJError);
+PPvoid_t JulyLLast(Pcvoid_t PArray, Word_t *Index, PJError_t PJError);
+PPvoid_t JulyLPrev(Pcvoid_t PArray, Word_t *Index, PJError_t PJError);
+Word_t JulyLFreeArray(PPvoid_t PPArray, PJError_t PJError);
+
+static inline PPvoid_t JulyLFirstThenNext(Pcvoid_t PArray, Word_t * PIndex, bool *first) {
+ if(unlikely(*first)) {
+ *first = false;
+ return JulyLFirst(PArray, PIndex, PJE0);
+ }
+
+ return JulyLNext(PArray, PIndex, PJE0);
+}
+
+static inline PPvoid_t JulyLLastThenPrev(Pcvoid_t PArray, Word_t * PIndex, bool *first) {
+ if(unlikely(*first)) {
+ *first = false;
+ return JulyLLast(PArray, PIndex, PJE0);
+ }
+
+ return JulyLPrev(PArray, PIndex, PJE0);
+}
+
+void julyl_cleanup1(void);
+size_t julyl_cache_size(void);
+size_t julyl_bytes_moved(void);
+
+#endif // NETDATA_JULY_H
diff --git a/libnetdata/libnetdata.c b/libnetdata/libnetdata.c
index cc04a97e..f6b6b026 100644
--- a/libnetdata/libnetdata.c
+++ b/libnetdata/libnetdata.c
@@ -21,6 +21,81 @@ int enable_ksm = 0;
volatile sig_atomic_t netdata_exit = 0;
const char *program_version = VERSION;
+#define MAX_JUDY_SIZE_TO_ARAL 24
+static bool judy_sizes_config[MAX_JUDY_SIZE_TO_ARAL + 1] = {
+ [3] = true,
+ [4] = true,
+ [5] = true,
+ [6] = true,
+ [7] = true,
+ [8] = true,
+ [10] = true,
+ [11] = true,
+ [15] = true,
+ [23] = true,
+};
+static ARAL *judy_sizes_aral[MAX_JUDY_SIZE_TO_ARAL + 1] = {};
+
+struct aral_statistics judy_sizes_aral_statistics = {};
+
+void aral_judy_init(void) {
+ for(size_t Words = 0; Words <= MAX_JUDY_SIZE_TO_ARAL; Words++)
+ if(judy_sizes_config[Words]) {
+ char buf[30+1];
+ snprintfz(buf, 30, "judy-%zu", Words * sizeof(Word_t));
+ judy_sizes_aral[Words] = aral_create(
+ buf,
+ Words * sizeof(Word_t),
+ 0,
+ 65536,
+ &judy_sizes_aral_statistics,
+ NULL, NULL, false, false);
+ }
+}
+
+size_t judy_aral_overhead(void) {
+ return aral_overhead_from_stats(&judy_sizes_aral_statistics);
+}
+
+size_t judy_aral_structures(void) {
+ return aral_structures_from_stats(&judy_sizes_aral_statistics);
+}
+
+static ARAL *judy_size_aral(Word_t Words) {
+ if(Words <= MAX_JUDY_SIZE_TO_ARAL && judy_sizes_aral[Words])
+ return judy_sizes_aral[Words];
+
+ return NULL;
+}
+
+inline Word_t JudyMalloc(Word_t Words) {
+ Word_t Addr;
+
+ ARAL *ar = judy_size_aral(Words);
+ if(ar)
+ Addr = (Word_t) aral_mallocz(ar);
+ else
+ Addr = (Word_t) mallocz(Words * sizeof(Word_t));
+
+ return(Addr);
+}
+
+inline void JudyFree(void * PWord, Word_t Words) {
+ ARAL *ar = judy_size_aral(Words);
+ if(ar)
+ aral_freez(ar, PWord);
+ else
+ freez(PWord);
+}
+
+Word_t JudyMallocVirtual(Word_t Words) {
+ return JudyMalloc(Words);
+}
+
+void JudyFreeVirtual(void * PWord, Word_t Words) {
+ JudyFree(PWord, Words);
+}
+
// ----------------------------------------------------------------------------
// memory allocation functions that handle failures
@@ -150,27 +225,6 @@ void posix_memfree(void *ptr) {
libc_free(ptr);
}
-Word_t JudyMalloc(Word_t Words) {
- Word_t Addr;
-
- Addr = (Word_t) mallocz(Words * sizeof(Word_t));
- return(Addr);
-}
-void JudyFree(void * PWord, Word_t Words) {
- (void)Words;
- freez(PWord);
-}
-Word_t JudyMallocVirtual(Word_t Words) {
- Word_t Addr;
-
- Addr = (Word_t) mallocz(Words * sizeof(Word_t));
- return(Addr);
-}
-void JudyFreeVirtual(void * PWord, Word_t Words) {
- (void)Words;
- freez(PWord);
-}
-
#define MALLOC_ALIGNMENT (sizeof(uintptr_t) * 2)
#define size_t_atomic_count(op, var, size) __atomic_## op ##_fetch(&(var), size, __ATOMIC_RELAXED)
#define size_t_atomic_bytes(op, var, size) __atomic_## op ##_fetch(&(var), ((size) % MALLOC_ALIGNMENT)?((size) + MALLOC_ALIGNMENT - ((size) % MALLOC_ALIGNMENT)):(size), __ATOMIC_RELAXED)
@@ -1176,7 +1230,7 @@ static int memory_file_open(const char *filename, size_t size) {
return fd;
}
-static inline int madvise_sequential(void *mem, size_t len) {
+inline int madvise_sequential(void *mem, size_t len) {
static int logger = 1;
int ret = madvise(mem, len, MADV_SEQUENTIAL);
@@ -1184,7 +1238,15 @@ static inline int madvise_sequential(void *mem, size_t len) {
return ret;
}
-static inline int madvise_dontfork(void *mem, size_t len) {
+inline int madvise_random(void *mem, size_t len) {
+ static int logger = 1;
+ int ret = madvise(mem, len, MADV_RANDOM);
+
+ if (ret != 0 && logger-- > 0) error("madvise(MADV_RANDOM) failed.");
+ return ret;
+}
+
+inline int madvise_dontfork(void *mem, size_t len) {
static int logger = 1;
int ret = madvise(mem, len, MADV_DONTFORK);
@@ -1192,7 +1254,7 @@ static inline int madvise_dontfork(void *mem, size_t len) {
return ret;
}
-static inline int madvise_willneed(void *mem, size_t len) {
+inline int madvise_willneed(void *mem, size_t len) {
static int logger = 1;
int ret = madvise(mem, len, MADV_WILLNEED);
@@ -1200,24 +1262,27 @@ static inline int madvise_willneed(void *mem, size_t len) {
return ret;
}
+inline int madvise_dontneed(void *mem, size_t len) {
+ static int logger = 1;
+ int ret = madvise(mem, len, MADV_DONTNEED);
+
+ if (ret != 0 && logger-- > 0) error("madvise(MADV_DONTNEED) failed.");
+ return ret;
+}
+
+inline int madvise_dontdump(void *mem __maybe_unused, size_t len __maybe_unused) {
#if __linux__
-static inline int madvise_dontdump(void *mem, size_t len) {
static int logger = 1;
int ret = madvise(mem, len, MADV_DONTDUMP);
if (ret != 0 && logger-- > 0) error("madvise(MADV_DONTDUMP) failed.");
return ret;
-}
#else
-static inline int madvise_dontdump(void *mem, size_t len) {
- UNUSED(mem);
- UNUSED(len);
-
return 0;
-}
#endif
+}
-static inline int madvise_mergeable(void *mem, size_t len) {
+inline int madvise_mergeable(void *mem __maybe_unused, size_t len __maybe_unused) {
#ifdef MADV_MERGEABLE
static int logger = 1;
int ret = madvise(mem, len, MADV_MERGEABLE);
@@ -1225,14 +1290,12 @@ static inline int madvise_mergeable(void *mem, size_t len) {
if (ret != 0 && logger-- > 0) error("madvise(MADV_MERGEABLE) failed.");
return ret;
#else
- UNUSED(mem);
- UNUSED(len);
-
return 0;
#endif
}
-void *netdata_mmap(const char *filename, size_t size, int flags, int ksm) {
+void *netdata_mmap(const char *filename, size_t size, int flags, int ksm, bool read_only, int *open_fd)
+{
// info("netdata_mmap('%s', %zu", filename, size);
// MAP_SHARED is used in memory mode map
@@ -1271,7 +1334,7 @@ void *netdata_mmap(const char *filename, size_t size, int flags, int ksm) {
fd_for_mmap = -1;
}
- mem = mmap(NULL, size, PROT_READ | PROT_WRITE, flags, fd_for_mmap, 0);
+ mem = mmap(NULL, size, read_only ? PROT_READ : PROT_READ | PROT_WRITE, flags, fd_for_mmap, 0);
if (mem != MAP_FAILED) {
#ifdef NETDATA_TRACE_ALLOCATIONS
@@ -1288,15 +1351,20 @@ void *netdata_mmap(const char *filename, size_t size, int flags, int ksm) {
else info("Cannot seek to beginning of file '%s'.", filename);
}
- madvise_sequential(mem, size);
+ // madvise_sequential(mem, size);
madvise_dontfork(mem, size);
madvise_dontdump(mem, size);
- if(flags & MAP_SHARED) madvise_willneed(mem, size);
+ // if(flags & MAP_SHARED) madvise_willneed(mem, size);
if(ksm) madvise_mergeable(mem, size);
}
cleanup:
- if(fd != -1) close(fd);
+ if(fd != -1) {
+ if (open_fd)
+ *open_fd = fd;
+ else
+ close(fd);
+ }
if(mem == MAP_FAILED) return NULL;
errno = 0;
return mem;
@@ -1934,3 +2002,70 @@ bool run_command_and_copy_output_to_stdout(const char *command, int max_line_len
netdata_pclose(NULL, fp, pid);
return true;
}
+
+void for_each_open_fd(OPEN_FD_ACTION action, OPEN_FD_EXCLUDE excluded_fds){
+ int fd;
+
+ switch(action){
+ case OPEN_FD_ACTION_CLOSE:
+ if(!(excluded_fds & OPEN_FD_EXCLUDE_STDIN)) (void)close(STDIN_FILENO);
+ if(!(excluded_fds & OPEN_FD_EXCLUDE_STDOUT)) (void)close(STDOUT_FILENO);
+ if(!(excluded_fds & OPEN_FD_EXCLUDE_STDERR)) (void)close(STDERR_FILENO);
+ break;
+ case OPEN_FD_ACTION_FD_CLOEXEC:
+ if(!(excluded_fds & OPEN_FD_EXCLUDE_STDIN)) (void)fcntl(STDIN_FILENO, F_SETFD, FD_CLOEXEC);
+ if(!(excluded_fds & OPEN_FD_EXCLUDE_STDOUT)) (void)fcntl(STDOUT_FILENO, F_SETFD, FD_CLOEXEC);
+ if(!(excluded_fds & OPEN_FD_EXCLUDE_STDERR)) (void)fcntl(STDERR_FILENO, F_SETFD, FD_CLOEXEC);
+ break;
+ default:
+ break; // do nothing
+ }
+
+#if defined(HAVE_CLOSE_RANGE)
+ if(close_range(STDERR_FILENO + 1, ~0U, (action == OPEN_FD_ACTION_FD_CLOEXEC ? CLOSE_RANGE_CLOEXEC : 0)) == 0) return;
+ error("close_range() failed, will try to close fds manually");
+#endif
+
+ DIR *dir = opendir("/proc/self/fd");
+ if (dir == NULL) {
+ struct rlimit rl;
+ int open_max = -1;
+
+ if(getrlimit(RLIMIT_NOFILE, &rl) == 0 && rl.rlim_max != RLIM_INFINITY) open_max = rl.rlim_max;
+#ifdef _SC_OPEN_MAX
+ else open_max = sysconf(_SC_OPEN_MAX);
+#endif
+
+ if (open_max == -1) open_max = 65535; // 65535 arbitrary default if everything else fails
+
+ for (fd = STDERR_FILENO + 1; fd < open_max; fd++) {
+ switch(action){
+ case OPEN_FD_ACTION_CLOSE:
+ if(fd_is_valid(fd)) (void)close(fd);
+ break;
+ case OPEN_FD_ACTION_FD_CLOEXEC:
+ (void)fcntl(fd, F_SETFD, FD_CLOEXEC);
+ break;
+ default:
+ break; // do nothing
+ }
+ }
+ } else {
+ struct dirent *entry;
+ while ((entry = readdir(dir)) != NULL) {
+ fd = str2i(entry->d_name);
+ if(unlikely((fd == STDIN_FILENO ) || (fd == STDOUT_FILENO) || (fd == STDERR_FILENO) )) continue;
+ switch(action){
+ case OPEN_FD_ACTION_CLOSE:
+ if(fd_is_valid(fd)) (void)close(fd);
+ break;
+ case OPEN_FD_ACTION_FD_CLOEXEC:
+ (void)fcntl(fd, F_SETFD, FD_CLOEXEC);
+ break;
+ default:
+ break; // do nothing
+ }
+ }
+ closedir(dir);
+ }
+}
diff --git a/libnetdata/libnetdata.h b/libnetdata/libnetdata.h
index 58eaa9de..c504bd4b 100644
--- a/libnetdata/libnetdata.h
+++ b/libnetdata/libnetdata.h
@@ -11,10 +11,18 @@ extern "C" {
#include <config.h>
#endif
+#define JUDYHS_INDEX_SIZE_ESTIMATE(key_bytes) (((key_bytes) + sizeof(Word_t) - 1) / sizeof(Word_t) * 4)
+
#if defined(NETDATA_DEV_MODE) && !defined(NETDATA_INTERNAL_CHECKS)
#define NETDATA_INTERNAL_CHECKS 1
#endif
+#if SIZEOF_VOID_P == 4
+#define ENV32BIT 1
+#else
+#define ENV64BIT 1
+#endif
+
// NETDATA_TRACE_ALLOCATIONS does not work under musl libc, so don't enable it
//#if defined(NETDATA_INTERNAL_CHECKS) && !defined(NETDATA_TRACE_ALLOCATIONS)
//#define NETDATA_TRACE_ALLOCATIONS 1
@@ -217,6 +225,10 @@ extern "C" {
#define WARNUNUSED
#endif
+void aral_judy_init(void);
+size_t judy_aral_overhead(void);
+size_t judy_aral_structures(void);
+
#define ABS(x) (((x) < 0)? (-(x)) : (x))
#define MIN(a,b) (((a)<(b))?(a):(b))
#define MAX(a,b) (((a)>(b))?(a):(b))
@@ -225,8 +237,9 @@ extern "C" {
// ---------------------------------------------------------------------------------------------
// double linked list management
+// inspired by https://github.com/troydhanson/uthash/blob/master/src/utlist.h
-#define DOUBLE_LINKED_LIST_PREPEND_UNSAFE(head, item, prev, next) \
+#define DOUBLE_LINKED_LIST_PREPEND_ITEM_UNSAFE(head, item, prev, next) \
do { \
(item)->next = (head); \
\
@@ -240,7 +253,7 @@ extern "C" {
(head) = (item); \
} while (0)
-#define DOUBLE_LINKED_LIST_APPEND_UNSAFE(head, item, prev, next) \
+#define DOUBLE_LINKED_LIST_APPEND_ITEM_UNSAFE(head, item, prev, next) \
do { \
if(likely(head)) { \
(item)->prev = (head)->prev; \
@@ -256,39 +269,97 @@ extern "C" {
\
} while (0)
-#define DOUBLE_LINKED_LIST_REMOVE_UNSAFE(head, item, prev, next) \
+#define DOUBLE_LINKED_LIST_REMOVE_ITEM_UNSAFE(head, item, prev, next) \
do { \
fatal_assert((head) != NULL); \
fatal_assert((item)->prev != NULL); \
\
- if((item)->prev == (item)) { \
+ if((item)->prev == (item)) \
/* it is the only item in the list */ \
(head) = NULL; \
- } \
+ \
else if((item) == (head)) { \
/* it is the first item */ \
+ fatal_assert((item)->next != NULL); \
(item)->next->prev = (item)->prev; \
(head) = (item)->next; \
} \
else { \
+ /* it is any other item */ \
(item)->prev->next = (item)->next; \
- if ((item)->next) { \
+ \
+ if ((item)->next) \
(item)->next->prev = (item)->prev; \
- } \
- else { \
+ else \
(head)->prev = (item)->prev; \
- } \
} \
\
(item)->next = NULL; \
(item)->prev = NULL; \
} while (0)
+#define DOUBLE_LINKED_LIST_INSERT_ITEM_BEFORE_UNSAFE(head, existing, item, prev, next) \
+ do { \
+ if (existing) { \
+ fatal_assert((head) != NULL); \
+ fatal_assert((item) != NULL); \
+ \
+ (item)->next = (existing); \
+ (item)->prev = (existing)->prev; \
+ (existing)->prev = (item); \
+ \
+ if ((head) == (existing)) \
+ (head) = (item); \
+ else \
+ (item)->prev->next = (item); \
+ \
+ } \
+ else \
+ DOUBLE_LINKED_LIST_APPEND_ITEM_UNSAFE(head, item, prev, next); \
+ \
+ } while (0)
+
+#define DOUBLE_LINKED_LIST_INSERT_ITEM_AFTER_UNSAFE(head, existing, item, prev, next) \
+ do { \
+ if (existing) { \
+ fatal_assert((head) != NULL); \
+ fatal_assert((item) != NULL); \
+ \
+ (item)->next = (existing)->next; \
+ (item)->prev = (existing); \
+ (existing)->next = (item); \
+ \
+ if ((item)->next) \
+ (item)->next->prev = (item); \
+ else \
+ (head)->prev = (item); \
+ } \
+ else \
+ DOUBLE_LINKED_LIST_PREPEND_ITEM_UNSAFE(head, item, prev, next); \
+ \
+ } while (0)
+
+#define DOUBLE_LINKED_LIST_APPEND_LIST_UNSAFE(head, head2, prev, next) \
+ do { \
+ if (head2) { \
+ if (head) { \
+ __typeof(head2) _head2_last_item = (head2)->prev; \
+ \
+ (head2)->prev = (head)->prev; \
+ (head)->prev->next = (head2); \
+ \
+ (head)->prev = _head2_last_item; \
+ } \
+ else \
+ (head) = (head2); \
+ } \
+ } while (0)
+
#define DOUBLE_LINKED_LIST_FOREACH_FORWARD(head, var, prev, next) \
for ((var) = (head); (var) ; (var) = (var)->next)
#define DOUBLE_LINKED_LIST_FOREACH_BACKWARD(head, var, prev, next) \
- for ((var) = (head)?(head)->prev:NULL; (var) && (var) != (head)->prev ; (var) = (var)->prev)
+ for ((var) = (head) ? (head)->prev : NULL ; (var) ; (var) = ((var) == (head)) ? NULL : (var)->prev)
// ---------------------------------------------------------------------------------------------
@@ -301,6 +372,14 @@ char *mystrsep(char **ptr, char *s);
char *trim(char *s); // remove leading and trailing spaces; may return NULL
char *trim_all(char *buffer); // like trim(), but also remove duplicate spaces inside the string; may return NULL
+int madvise_sequential(void *mem, size_t len);
+int madvise_random(void *mem, size_t len);
+int madvise_dontfork(void *mem, size_t len);
+int madvise_willneed(void *mem, size_t len);
+int madvise_dontneed(void *mem, size_t len);
+int madvise_dontdump(void *mem, size_t len);
+int madvise_mergeable(void *mem, size_t len);
+
int vsnprintfz(char *dst, size_t n, const char *fmt, va_list args);
int snprintfz(char *dst, size_t n, const char *fmt, ...) PRINTFLIKE(3, 4);
@@ -335,7 +414,7 @@ void posix_memfree(void *ptr);
void json_escape_string(char *dst, const char *src, size_t size);
void json_fix_string(char *s);
-void *netdata_mmap(const char *filename, size_t size, int flags, int ksm);
+void *netdata_mmap(const char *filename, size_t size, int flags, int ksm, bool read_only, int *open_fd);
int netdata_munmap(void *ptr, size_t size);
int memory_file_save(const char *filename, void *mem, size_t size);
@@ -418,10 +497,22 @@ static inline char *get_word(char **words, size_t num_words, size_t index) {
bool run_command_and_copy_output_to_stdout(const char *command, int max_line_length);
+typedef enum {
+ OPEN_FD_ACTION_CLOSE,
+ OPEN_FD_ACTION_FD_CLOEXEC
+} OPEN_FD_ACTION;
+typedef enum {
+ OPEN_FD_EXCLUDE_STDIN = 0x01,
+ OPEN_FD_EXCLUDE_STDOUT = 0x02,
+ OPEN_FD_EXCLUDE_STDERR = 0x04
+} OPEN_FD_EXCLUDE;
+void for_each_open_fd(OPEN_FD_ACTION action, OPEN_FD_EXCLUDE excluded_fds);
+
void netdata_cleanup_and_exit(int ret) NORETURN;
void send_statistics(const char *action, const char *action_result, const char *action_data);
extern char *netdata_configured_host_prefix;
#include "libjudy/src/Judy.h"
+#include "july/july.h"
#include "os.h"
#include "storage_number/storage_number.h"
#include "threads/threads.h"
@@ -453,7 +544,7 @@ extern char *netdata_configured_host_prefix;
#include "json/json.h"
#include "health/health.h"
#include "string/utf8.h"
-#include "arrayalloc/arrayalloc.h"
+#include "libnetdata/aral/aral.h"
#include "onewayalloc/onewayalloc.h"
#include "worker_utilization/worker_utilization.h"
@@ -500,6 +591,76 @@ struct malloc_trace {
};
#endif // NETDATA_TRACE_ALLOCATIONS
+static inline PPvoid_t JudyLFirstThenNext(Pcvoid_t PArray, Word_t * PIndex, bool *first) {
+ if(unlikely(*first)) {
+ *first = false;
+ return JudyLFirst(PArray, PIndex, PJE0);
+ }
+
+ return JudyLNext(PArray, PIndex, PJE0);
+}
+
+static inline PPvoid_t JudyLLastThenPrev(Pcvoid_t PArray, Word_t * PIndex, bool *first) {
+ if(unlikely(*first)) {
+ *first = false;
+ return JudyLLast(PArray, PIndex, PJE0);
+ }
+
+ return JudyLPrev(PArray, PIndex, PJE0);
+}
+
+static inline size_t indexing_partition_old(Word_t ptr, Word_t modulo) {
+ size_t total = 0;
+
+ total += (ptr & 0xff) >> 0;
+ total += (ptr & 0xff00) >> 8;
+ total += (ptr & 0xff0000) >> 16;
+ total += (ptr & 0xff000000) >> 24;
+
+ if(sizeof(Word_t) > 4) {
+ total += (ptr & 0xff00000000) >> 32;
+ total += (ptr & 0xff0000000000) >> 40;
+ total += (ptr & 0xff000000000000) >> 48;
+ total += (ptr & 0xff00000000000000) >> 56;
+ }
+
+ return (total % modulo);
+}
+
+static uint32_t murmur32(uint32_t h) __attribute__((const));
+static inline uint32_t murmur32(uint32_t h) {
+ h ^= h >> 16;
+ h *= 0x85ebca6b;
+ h ^= h >> 13;
+ h *= 0xc2b2ae35;
+ h ^= h >> 16;
+
+ return h;
+}
+
+static uint64_t murmur64(uint64_t h) __attribute__((const));
+static inline uint64_t murmur64(uint64_t k) {
+ k ^= k >> 33;
+ k *= 0xff51afd7ed558ccdUL;
+ k ^= k >> 33;
+ k *= 0xc4ceb9fe1a85ec53UL;
+ k ^= k >> 33;
+
+ return k;
+}
+
+static inline size_t indexing_partition(Word_t ptr, Word_t modulo) __attribute__((const));
+static inline size_t indexing_partition(Word_t ptr, Word_t modulo) {
+ if(sizeof(Word_t) == 8) {
+ uint64_t hash = murmur64(ptr);
+ return hash % modulo;
+ }
+ else {
+ uint32_t hash = murmur32(ptr);
+ return hash % modulo;
+ }
+}
+
# ifdef __cplusplus
}
# endif
diff --git a/libnetdata/locks/README.md b/libnetdata/locks/README.md
index 9132edc4..8810e3d1 100644
--- a/libnetdata/locks/README.md
+++ b/libnetdata/locks/README.md
@@ -1,5 +1,10 @@
<!--
+title: "Locks"
custom_edit_url: https://github.com/netdata/netdata/edit/master/libnetdata/locks/README.md
+sidebar_label: "Locks"
+learn_status: "Published"
+learn_topic_type: "Tasks"
+learn_rel_path: "Developers/libnetdata libraries"
-->
## How to trace netdata locks
diff --git a/libnetdata/locks/locks.c b/libnetdata/locks/locks.c
index f7191be5..e73456d7 100644
--- a/libnetdata/locks/locks.c
+++ b/libnetdata/locks/locks.c
@@ -15,8 +15,6 @@
#ifndef NETDATA_THREAD_LOCKS_ARRAY_SIZE
#define NETDATA_THREAD_LOCKS_ARRAY_SIZE 10
#endif
-static __thread netdata_rwlock_t *netdata_thread_locks[NETDATA_THREAD_LOCKS_ARRAY_SIZE];
-
#endif // NETDATA_TRACE_RWLOCKS
@@ -120,8 +118,6 @@ int __netdata_mutex_unlock(netdata_mutex_t *mutex) {
#ifdef NETDATA_TRACE_RWLOCKS
-#warning NETDATA_TRACE_RWLOCKS ENABLED - EXPECT A LOT OF OUTPUT
-
int netdata_mutex_init_debug(const char *file __maybe_unused, const char *function __maybe_unused,
const unsigned long line __maybe_unused, netdata_mutex_t *mutex) {
debug(D_LOCKS, "MUTEX_LOCK: netdata_mutex_init(%p) from %lu@%s, %s()", mutex, line, file, function);
@@ -283,12 +279,16 @@ int __netdata_rwlock_trywrlock(netdata_rwlock_t *rwlock) {
// https://www.youtube.com/watch?v=rmGJc9PXpuE&t=41s
void netdata_spinlock_init(SPINLOCK *spinlock) {
- *spinlock = NETDATA_SPINLOCK_INITIALIZER;
+ memset(spinlock, 0, sizeof(SPINLOCK));
}
void netdata_spinlock_lock(SPINLOCK *spinlock) {
static const struct timespec ns = { .tv_sec = 0, .tv_nsec = 1 };
+#ifdef NETDATA_INTERNAL_CHECKS
+ size_t spins = 0;
+#endif
+
netdata_thread_disable_cancelability();
for(int i = 1;
@@ -296,254 +296,104 @@ void netdata_spinlock_lock(SPINLOCK *spinlock) {
__atomic_test_and_set(&spinlock->locked, __ATOMIC_ACQUIRE)
; i++
) {
+
+#ifdef NETDATA_INTERNAL_CHECKS
+ spins++;
+#endif
if(unlikely(i == 8)) {
i = 0;
nanosleep(&ns, NULL);
}
}
+
// we have the lock
+
+#ifdef NETDATA_INTERNAL_CHECKS
+ spinlock->spins += spins;
+ spinlock->locker_pid = gettid();
+#endif
}
void netdata_spinlock_unlock(SPINLOCK *spinlock) {
+#ifdef NETDATA_INTERNAL_CHECKS
+ spinlock->locker_pid = 0;
+#endif
__atomic_clear(&spinlock->locked, __ATOMIC_RELEASE);
netdata_thread_enable_cancelability();
}
-#ifdef NETDATA_TRACE_RWLOCKS
+bool netdata_spinlock_trylock(SPINLOCK *spinlock) {
+ netdata_thread_disable_cancelability();
-// ----------------------------------------------------------------------------
-// lockers list
+ if(!__atomic_load_n(&spinlock->locked, __ATOMIC_RELAXED) &&
+ !__atomic_test_and_set(&spinlock->locked, __ATOMIC_ACQUIRE))
+ // we got the lock
+ return true;
-void not_supported_by_posix_rwlocks(const char *file, const char *function, const unsigned long line, netdata_rwlock_t *rwlock, char locktype, const char *reason) {
- __netdata_mutex_lock(&rwlock->lockers_mutex);
- fprintf(stderr,
- "RW_LOCK FATAL ON LOCK %p: %d '%s' (function %s() %lu@%s) attempts to acquire a '%c' lock, but it is not supported by POSIX because: %s. At this attempt, the task is holding %zu rwlocks and %zu mutexes. There are %zu readers and %zu writers holding this lock:\n",
- rwlock,
- gettid(), netdata_thread_tag(),
- function, line, file,
- locktype,
- reason,
- netdata_locks_acquired_rwlocks, netdata_locks_acquired_mutexes,
- rwlock->readers, rwlock->writers);
-
- int i;
- usec_t now = now_monotonic_high_precision_usec();
- netdata_rwlock_locker *p;
- for(i = 1, p = rwlock->lockers; p ;p = p->next, i++) {
- fprintf(stderr,
- " => %i: RW_LOCK %p: process %d '%s' (function %s() %lu@%s) is having %zu '%c' lock for %llu usec.\n",
- i, rwlock,
- p->pid, p->tag,
- p->function, p->line, p->file,
- p->callers, p->lock,
- (now - p->start_s));
- }
- __netdata_mutex_unlock(&rwlock->lockers_mutex);
+ // we didn't get the lock
+ return false;
}
-static void log_rwlock_lockers(const char *file, const char *function, const unsigned long line, netdata_rwlock_t *rwlock, const char *reason, char locktype) {
-
- // this function can only be used by one thread at a time
- // because otherwise, the threads may deadlock waiting for each other
- static netdata_mutex_t log_lockers_mutex = NETDATA_MUTEX_INITIALIZER;
- __netdata_mutex_lock(&log_lockers_mutex);
-
- // now work on this locker
- __netdata_mutex_lock(&rwlock->lockers_mutex);
- fprintf(stderr,
- "RW_LOCK ON LOCK %p: %d '%s' (function %s() %lu@%s) %s a '%c' lock (while holding %zu rwlocks and %zu mutexes). There are %zu readers and %zu writers holding this lock:\n",
- rwlock,
- gettid(), netdata_thread_tag(),
- function, line, file,
- reason, locktype,
- netdata_locks_acquired_rwlocks, netdata_locks_acquired_mutexes,
- rwlock->readers, rwlock->writers);
-
- int i;
- usec_t now = now_monotonic_high_precision_usec();
- netdata_rwlock_locker *p;
- for(i = 1, p = rwlock->lockers; p ;p = p->next, i++) {
- fprintf(stderr,
- " => %i: RW_LOCK %p: process %d '%s' (function %s() %lu@%s) is having %zu '%c' lock for %llu usec.\n",
- i, rwlock,
- p->pid, p->tag,
- p->function, p->line, p->file,
- p->callers, p->lock,
- (now - p->start_s));
-
- if(p->all_caller_locks) {
- // find the lock in the netdata_thread_locks[]
- // and remove it
- int k;
- for(k = 0; k < NETDATA_THREAD_LOCKS_ARRAY_SIZE ;k++) {
- if (p->all_caller_locks[k] && p->all_caller_locks[k] != rwlock) {
-
- // lock the other lock lockers list
- __netdata_mutex_lock(&p->all_caller_locks[k]->lockers_mutex);
-
- // print the list of lockers of the other lock
- netdata_rwlock_locker *r;
- int j;
- for(j = 1, r = p->all_caller_locks[k]->lockers; r ;r = r->next, j++) {
- fprintf(
- stderr,
- " ~~~> %i: RW_LOCK %p: process %d '%s' (function %s() %lu@%s) is having %zu '%c' lock for %llu usec.\n",
- j,
- p->all_caller_locks[k],
- r->pid,
- r->tag,
- r->function,
- r->line,
- r->file,
- r->callers,
- r->lock,
- (now - r->start_s));
- }
-
- // unlock the other lock lockers list
- __netdata_mutex_unlock(&p->all_caller_locks[k]->lockers_mutex);
- }
- }
- }
+#ifdef NETDATA_TRACE_RWLOCKS
- }
- __netdata_mutex_unlock(&rwlock->lockers_mutex);
+// ----------------------------------------------------------------------------
+// lockers list
- // unlock this function for other threads
- __netdata_mutex_unlock(&log_lockers_mutex);
-}
-
-static netdata_rwlock_locker *add_rwlock_locker(const char *file, const char *function, const unsigned long line, netdata_rwlock_t *rwlock, char lock_type) {
- netdata_rwlock_locker *p = mallocz(sizeof(netdata_rwlock_locker));
- p->pid = gettid();
- p->tag = netdata_thread_tag();
- p->lock = lock_type;
- p->file = file;
- p->function = function;
- p->line = line;
- p->callers = 1;
- p->all_caller_locks = netdata_thread_locks;
- p->start_s = now_monotonic_high_precision_usec();
-
- // find a slot in the netdata_thread_locks[]
- int i;
- for(i = 0; i < NETDATA_THREAD_LOCKS_ARRAY_SIZE ;i++) {
- if (!netdata_thread_locks[i]) {
- netdata_thread_locks[i] = rwlock;
- break;
- }
- }
+static netdata_rwlock_locker *find_rwlock_locker(const char *file __maybe_unused, const char *function __maybe_unused, const unsigned long line __maybe_unused, netdata_rwlock_t *rwlock) {
+ pid_t pid = gettid();
+ netdata_rwlock_locker *locker = NULL;
__netdata_mutex_lock(&rwlock->lockers_mutex);
- p->next = rwlock->lockers;
- rwlock->lockers = p;
- if(lock_type == 'R') rwlock->readers++;
- if(lock_type == 'W') rwlock->writers++;
+ Pvoid_t *PValue = JudyLGet(rwlock->lockers_pid_JudyL, pid, PJE0);
+ if(PValue && *PValue)
+ locker = *PValue;
__netdata_mutex_unlock(&rwlock->lockers_mutex);
- return p;
+ return locker;
}
-static void remove_rwlock_locker(const char *file __maybe_unused, const char *function __maybe_unused, const unsigned long line __maybe_unused, netdata_rwlock_t *rwlock, netdata_rwlock_locker *locker) {
- usec_t end_s = now_monotonic_high_precision_usec();
+static netdata_rwlock_locker *add_rwlock_locker(const char *file, const char *function, const unsigned long line, netdata_rwlock_t *rwlock, LOCKER_REQUEST lock_type) {
+ netdata_rwlock_locker *locker;
- if(locker->callers == 0)
- fprintf(stderr,
- "RW_LOCK ON LOCK %p: %d, '%s' (function %s() %lu@%s) callers should be positive but it is zero\n",
- rwlock,
- locker->pid, locker->tag,
- locker->function, locker->line, locker->file);
-
- if(locker->callers > 1 && locker->lock != 'R')
- fprintf(stderr,
- "RW_LOCK ON LOCK %p: %d, '%s' (function %s() %lu@%s) only 'R' locks support multiple holders, but here we have %zu callers holding a '%c' lock.\n",
- rwlock,
- locker->pid, locker->tag,
- locker->function, locker->line, locker->file,
- locker->callers, locker->lock);
-
- __netdata_mutex_lock(&rwlock->lockers_mutex);
- locker->callers--;
-
- if(!locker->callers) {
- int doit = 0;
-
- if (rwlock->lockers == locker) {
- rwlock->lockers = locker->next;
- doit = 1;
- } else {
- netdata_rwlock_locker *p;
- for (p = rwlock->lockers; p && p->next != locker; p = p->next)
- ;
- if (p && p->next == locker) {
- p->next = locker->next;
- doit = 1;
- }
- }
- if(doit) {
- if(locker->lock == 'R') rwlock->readers--;
- if(locker->lock == 'W') rwlock->writers--;
- }
+ locker = find_rwlock_locker(file, function, line, rwlock);
+ if(locker) {
+ locker->lock |= lock_type;
+ locker->refcount++;
+ }
+ else {
+ locker = mallocz(sizeof(netdata_rwlock_locker));
+ locker->pid = gettid();
+ locker->tag = netdata_thread_tag();
+ locker->refcount = 1;
+ locker->lock = lock_type;
+ locker->got_it = false;
+ locker->file = file;
+ locker->function = function;
+ locker->line = line;
- if(!doit) {
- fprintf(stderr,
- "RW_LOCK ON LOCK %p: %d, '%s' (function %s() %lu@%s) with %zu x '%c' lock is not found.\n",
- rwlock,
- locker->pid, locker->tag,
- locker->function, locker->line, locker->file,
- locker->callers, locker->lock);
- }
- else {
- // find the lock in the netdata_thread_locks[]
- // and remove it
- int i;
- for(i = 0; i < NETDATA_THREAD_LOCKS_ARRAY_SIZE ;i++) {
- if (netdata_thread_locks[i] == rwlock)
- netdata_thread_locks[i] = NULL;
- }
-
- if(end_s - locker->start_s >= NETDATA_TRACE_RWLOCKS_HOLD_TIME_TO_IGNORE_USEC)
- fprintf(stderr,
- "RW_LOCK ON LOCK %p: %d, '%s' (function %s() %lu@%s) holded a '%c' for %llu usec.\n",
- rwlock,
- locker->pid, locker->tag,
- locker->function, locker->line, locker->file,
- locker->lock, end_s - locker->start_s);
-
- freez(locker);
- }
+ __netdata_mutex_lock(&rwlock->lockers_mutex);
+ DOUBLE_LINKED_LIST_APPEND_UNSAFE(rwlock->lockers, locker, prev, next);
+ Pvoid_t *PValue = JudyLIns(&rwlock->lockers_pid_JudyL, locker->pid, PJE0);
+ *PValue = locker;
+ if (lock_type == RWLOCK_REQUEST_READ || lock_type == RWLOCK_REQUEST_TRYREAD) rwlock->readers++;
+ if (lock_type == RWLOCK_REQUEST_WRITE || lock_type == RWLOCK_REQUEST_TRYWRITE) rwlock->writers++;
+ __netdata_mutex_unlock(&rwlock->lockers_mutex);
}
- __netdata_mutex_unlock(&rwlock->lockers_mutex);
+ return locker;
}
-static netdata_rwlock_locker *find_rwlock_locker(const char *file __maybe_unused, const char *function __maybe_unused, const unsigned long line __maybe_unused, netdata_rwlock_t *rwlock) {
- pid_t pid = gettid();
- netdata_rwlock_locker *p;
-
+static void remove_rwlock_locker(const char *file __maybe_unused, const char *function __maybe_unused, const unsigned long line __maybe_unused, netdata_rwlock_t *rwlock, netdata_rwlock_locker *locker) {
__netdata_mutex_lock(&rwlock->lockers_mutex);
- for(p = rwlock->lockers; p ;p = p->next) {
- if(p->pid == pid) break;
+ locker->refcount--;
+ if(!locker->refcount) {
+ DOUBLE_LINKED_LIST_REMOVE_UNSAFE(rwlock->lockers, locker, prev, next);
+ JudyLDel(&rwlock->lockers_pid_JudyL, locker->pid, PJE0);
+ if (locker->lock == RWLOCK_REQUEST_READ || locker->lock == RWLOCK_REQUEST_TRYREAD) rwlock->readers--;
+ else if (locker->lock == RWLOCK_REQUEST_WRITE || locker->lock == RWLOCK_REQUEST_TRYWRITE) rwlock->writers--;
+ freez(locker);
}
__netdata_mutex_unlock(&rwlock->lockers_mutex);
-
- return p;
-}
-
-static netdata_rwlock_locker *update_or_add_rwlock_locker(const char *file, const char *function, const unsigned long line, netdata_rwlock_t *rwlock, netdata_rwlock_locker *locker, char locktype) {
- if(!locker) {
- return add_rwlock_locker(file, function, line, rwlock, locktype);
- }
- else if(locker->lock == 'R' && locktype == 'R') {
- __netdata_mutex_lock(&rwlock->lockers_mutex);
- locker->callers++;
- __netdata_mutex_unlock(&rwlock->lockers_mutex);
- return locker;
- }
- else {
- not_supported_by_posix_rwlocks(file, function, line, rwlock, locktype, "DEADLOCK - WANTS TO CHANGE LOCK TYPE BUT ALREADY HAS THIS LOCKED");
- return locker;
- }
}
// ----------------------------------------------------------------------------
@@ -551,84 +401,41 @@ static netdata_rwlock_locker *update_or_add_rwlock_locker(const char *file, cons
int netdata_rwlock_destroy_debug(const char *file __maybe_unused, const char *function __maybe_unused,
const unsigned long line __maybe_unused, netdata_rwlock_t *rwlock) {
- debug(D_LOCKS, "RW_LOCK: netdata_rwlock_destroy(%p) from %lu@%s, %s()", rwlock, line, file, function);
-
- if(rwlock->readers)
- error("RW_LOCK: destroying a rwlock with %zu readers in it", rwlock->readers);
- if(rwlock->writers)
- error("RW_LOCK: destroying a rwlock with %zu writers in it", rwlock->writers);
int ret = __netdata_rwlock_destroy(rwlock);
if(!ret) {
while (rwlock->lockers)
remove_rwlock_locker(file, function, line, rwlock, rwlock->lockers);
-
- if (rwlock->readers)
- error("RW_LOCK: internal error - empty rwlock with %zu readers in it", rwlock->readers);
- if (rwlock->writers)
- error("RW_LOCK: internal error - empty rwlock with %zu writers in it", rwlock->writers);
}
- debug(D_LOCKS, "RW_LOCK: netdata_rwlock_destroy(%p) = %d, from %lu@%s, %s()", rwlock, ret, line, file, function);
-
return ret;
}
int netdata_rwlock_init_debug(const char *file __maybe_unused, const char *function __maybe_unused,
const unsigned long line __maybe_unused, netdata_rwlock_t *rwlock) {
- debug(D_LOCKS, "RW_LOCK: netdata_rwlock_init(%p) from %lu@%s, %s()", rwlock, line, file, function);
int ret = __netdata_rwlock_init(rwlock);
if(!ret) {
__netdata_mutex_init(&rwlock->lockers_mutex);
+ rwlock->lockers_pid_JudyL = NULL;
rwlock->lockers = NULL;
rwlock->readers = 0;
rwlock->writers = 0;
}
- debug(D_LOCKS, "RW_LOCK: netdata_rwlock_init(%p) = %d, from %lu@%s, %s()", rwlock, ret, line, file, function);
-
return ret;
}
int netdata_rwlock_rdlock_debug(const char *file __maybe_unused, const char *function __maybe_unused,
const unsigned long line __maybe_unused, netdata_rwlock_t *rwlock) {
- debug(D_LOCKS, "RW_LOCK: netdata_rwlock_rdlock(%p) from %lu@%s, %s()", rwlock, line, file, function);
-
- netdata_rwlock_locker *locker = find_rwlock_locker(file, function, line, rwlock);
-
-#ifdef NETDATA_TRACE_RWLOCKS_LOG_NESTED
- if(locker && locker->lock == 'R') {
- log_rwlock_lockers(file, function, line, rwlock, "NESTED READ LOCK REQUEST", 'R');
- }
-#endif // NETDATA_TRACE_RWLOCKS_LOG_NESTED
+ netdata_rwlock_locker *locker = add_rwlock_locker(file, function, line, rwlock, RWLOCK_REQUEST_READ);
- int log = 0;
- if(rwlock->writers) {
- log_rwlock_lockers(file, function, line, rwlock, "WANTS", 'R');
- log = 1;
- }
-
- usec_t start_s = now_monotonic_high_precision_usec();
int ret = __netdata_rwlock_rdlock(rwlock);
- usec_t end_s = now_monotonic_high_precision_usec();
-
- if(!ret) {
- locker = update_or_add_rwlock_locker(file, function, line, rwlock, locker, 'R');
- if(log) log_rwlock_lockers(file, function, line, rwlock, "GOT", 'R');
-
- }
-
- if(end_s - start_s >= NETDATA_TRACE_RWLOCKS_WAIT_TIME_TO_IGNORE_USEC)
- fprintf(stderr,
- "RW_LOCK ON LOCK %p: %d, '%s' (function %s() %lu@%s) WAITED for a READ lock for %llu usec.\n",
- rwlock,
- gettid(), netdata_thread_tag(),
- function, line, file,
- end_s - start_s);
-
- debug(D_LOCKS, "RW_LOCK: netdata_rwlock_rdlock(%p) = %d in %llu usec, from %lu@%s, %s()", rwlock, ret, end_s - start_s, line, file, function);
+ if(!ret)
+ locker->got_it = true;
+ else
+ remove_rwlock_locker(file, function, line, rwlock, locker);
return ret;
}
@@ -636,36 +443,13 @@ int netdata_rwlock_rdlock_debug(const char *file __maybe_unused, const char *fun
int netdata_rwlock_wrlock_debug(const char *file __maybe_unused, const char *function __maybe_unused,
const unsigned long line __maybe_unused, netdata_rwlock_t *rwlock) {
- debug(D_LOCKS, "RW_LOCK: netdata_rwlock_wrlock(%p) from %lu@%s, %s()", rwlock, line, file, function);
-
- netdata_rwlock_locker *locker = find_rwlock_locker(file, function, line, rwlock);
- if(locker)
- not_supported_by_posix_rwlocks(file, function, line, rwlock, 'W', "DEADLOCK - WANTS A WRITE LOCK BUT ALREADY HAVE THIS LOCKED");
+ netdata_rwlock_locker *locker = add_rwlock_locker(file, function, line, rwlock, RWLOCK_REQUEST_WRITE);
- int log = 0;
- if(rwlock->readers) {
- log_rwlock_lockers(file, function, line, rwlock, "WANTS", 'W');
- log = 1;
- }
-
- usec_t start_s = now_monotonic_high_precision_usec();
int ret = __netdata_rwlock_wrlock(rwlock);
- usec_t end_s = now_monotonic_high_precision_usec();
-
- if(!ret){
- locker = update_or_add_rwlock_locker(file, function, line, rwlock, locker, 'W');
- if(log) log_rwlock_lockers(file, function, line, rwlock, "GOT", 'W');
- }
-
- if(end_s - start_s >= NETDATA_TRACE_RWLOCKS_WAIT_TIME_TO_IGNORE_USEC)
- fprintf(stderr,
- "RW_LOCK ON LOCK %p: %d, '%s' (function %s() %lu@%s) WAITED for a WRITE lock for %llu usec.\n",
- rwlock,
- gettid(), netdata_thread_tag(),
- function, line, file,
- end_s - start_s);
-
- debug(D_LOCKS, "RW_LOCK: netdata_rwlock_wrlock(%p) = %d in %llu usec, from %lu@%s, %s()", rwlock, ret, end_s - start_s, line, file, function);
+ if(!ret)
+ locker->got_it = true;
+ else
+ remove_rwlock_locker(file, function, line, rwlock, locker);
return ret;
}
@@ -673,83 +457,42 @@ int netdata_rwlock_wrlock_debug(const char *file __maybe_unused, const char *fun
int netdata_rwlock_unlock_debug(const char *file __maybe_unused, const char *function __maybe_unused,
const unsigned long line __maybe_unused, netdata_rwlock_t *rwlock) {
- debug(D_LOCKS, "RW_LOCK: netdata_rwlock_unlock(%p) from %lu@%s, %s()", rwlock, line, file, function);
-
netdata_rwlock_locker *locker = find_rwlock_locker(file, function, line, rwlock);
+
if(unlikely(!locker))
- not_supported_by_posix_rwlocks(file, function, line, rwlock, 'U', "UNLOCK WITHOUT LOCK");
+ fatal("UNLOCK WITHOUT LOCK");
- usec_t start_s = now_monotonic_high_precision_usec();
int ret = __netdata_rwlock_unlock(rwlock);
- usec_t end_s = now_monotonic_high_precision_usec();
-
- if(end_s - start_s >= NETDATA_TRACE_RWLOCKS_WAIT_TIME_TO_IGNORE_USEC)
- fprintf(stderr,
- "RW_LOCK ON LOCK %p: %d, '%s' (function %s() %lu@%s) WAITED to UNLOCK for %llu usec.\n",
- rwlock,
- gettid(), netdata_thread_tag(),
- function, line, file,
- end_s - start_s);
-
- if(likely(!ret && locker)) remove_rwlock_locker(file, function, line, rwlock, locker);
-
- debug(D_LOCKS, "RW_LOCK: netdata_rwlock_unlock(%p) = %d in %llu usec, from %lu@%s, %s()", rwlock, ret, end_s - start_s, line, file, function);
+ if(likely(!ret))
+ remove_rwlock_locker(file, function, line, rwlock, locker);
return ret;
}
int netdata_rwlock_tryrdlock_debug(const char *file __maybe_unused, const char *function __maybe_unused,
const unsigned long line __maybe_unused, netdata_rwlock_t *rwlock) {
- debug(D_LOCKS, "RW_LOCK: netdata_rwlock_tryrdlock(%p) from %lu@%s, %s()", rwlock, line, file, function);
- netdata_rwlock_locker *locker = find_rwlock_locker(file, function, line, rwlock);
- if(locker && locker->lock == 'W')
- not_supported_by_posix_rwlocks(file, function, line, rwlock, 'R', "DEADLOCK - WANTS A READ LOCK BUT IT HAS A WRITE LOCK ALREADY");
+ netdata_rwlock_locker *locker = add_rwlock_locker(file, function, line, rwlock, RWLOCK_REQUEST_TRYREAD);
- usec_t start_s = now_monotonic_high_precision_usec();
int ret = __netdata_rwlock_tryrdlock(rwlock);
- usec_t end_s = now_monotonic_high_precision_usec();
-
if(!ret)
- locker = update_or_add_rwlock_locker(file, function, line, rwlock, locker, 'R');
-
- if(end_s - start_s >= NETDATA_TRACE_RWLOCKS_WAIT_TIME_TO_IGNORE_USEC)
- fprintf(stderr,
- "RW_LOCK ON LOCK %p: %d, '%s' (function %s() %lu@%s) WAITED to TRYREAD for %llu usec.\n",
- rwlock,
- gettid(), netdata_thread_tag(),
- function, line, file,
- end_s - start_s);
-
- debug(D_LOCKS, "RW_LOCK: netdata_rwlock_tryrdlock(%p) = %d in %llu usec, from %lu@%s, %s()", rwlock, ret, end_s - start_s, line, file, function);
+ locker->got_it = true;
+ else
+ remove_rwlock_locker(file, function, line, rwlock, locker);
return ret;
}
int netdata_rwlock_trywrlock_debug(const char *file __maybe_unused, const char *function __maybe_unused,
const unsigned long line __maybe_unused, netdata_rwlock_t *rwlock) {
- debug(D_LOCKS, "RW_LOCK: netdata_rwlock_trywrlock(%p) from %lu@%s, %s()", rwlock, line, file, function);
- netdata_rwlock_locker *locker = find_rwlock_locker(file, function, line, rwlock);
- if(locker)
- not_supported_by_posix_rwlocks(file, function, line, rwlock, 'W', "ALREADY HAS THIS LOCK");
+ netdata_rwlock_locker *locker = add_rwlock_locker(file, function, line, rwlock, RWLOCK_REQUEST_TRYWRITE);
- usec_t start_s = now_monotonic_high_precision_usec();
int ret = __netdata_rwlock_trywrlock(rwlock);
- usec_t end_s = now_monotonic_high_precision_usec();
-
if(!ret)
- locker = update_or_add_rwlock_locker(file, function, line, rwlock, locker, 'W');
-
- if(end_s - start_s >= NETDATA_TRACE_RWLOCKS_WAIT_TIME_TO_IGNORE_USEC)
- fprintf(stderr,
- "RW_LOCK ON LOCK %p: %d, '%s' (function %s() %lu@%s) WAITED to TRYWRITE for %llu usec.\n",
- rwlock,
- gettid(), netdata_thread_tag(),
- function, line, file,
- end_s - start_s);
-
- debug(D_LOCKS, "RW_LOCK: netdata_rwlock_trywrlock(%p) = %d in %llu usec, from %lu@%s, %s()", rwlock, ret, end_s - start_s, line, file, function);
+ locker->got_it = true;
+ else
+ remove_rwlock_locker(file, function, line, rwlock, locker);
return ret;
}
diff --git a/libnetdata/locks/locks.h b/libnetdata/locks/locks.h
index 4d2d1655..89b110d5 100644
--- a/libnetdata/locks/locks.h
+++ b/libnetdata/locks/locks.h
@@ -11,24 +11,39 @@ typedef pthread_mutex_t netdata_mutex_t;
typedef struct netdata_spinlock {
bool locked;
+#ifdef NETDATA_INTERNAL_CHECKS
+ pid_t locker_pid;
+ size_t spins;
+#endif
} SPINLOCK;
-#define NETDATA_SPINLOCK_INITIALIZER (SPINLOCK){ .locked = false }
+
+#define NETDATA_SPINLOCK_INITIALIZER \
+ { .locked = false }
+
void netdata_spinlock_init(SPINLOCK *spinlock);
void netdata_spinlock_lock(SPINLOCK *spinlock);
void netdata_spinlock_unlock(SPINLOCK *spinlock);
+bool netdata_spinlock_trylock(SPINLOCK *spinlock);
#ifdef NETDATA_TRACE_RWLOCKS
+
+typedef enum {
+ RWLOCK_REQUEST_READ = (1 << 0),
+ RWLOCK_REQUEST_WRITE = (1 << 1),
+ RWLOCK_REQUEST_TRYREAD = (1 << 2),
+ RWLOCK_REQUEST_TRYWRITE = (1 << 3),
+} LOCKER_REQUEST;
+
typedef struct netdata_rwlock_locker {
+ LOCKER_REQUEST lock;
+ bool got_it;
pid_t pid;
+ size_t refcount;
const char *tag;
- char lock; // 'R', 'W'
const char *file;
const char *function;
unsigned long line;
- size_t callers;
- usec_t start_s;
- struct netdata_rwlock_t **all_caller_locks;
- struct netdata_rwlock_locker *next;
+ struct netdata_rwlock_locker *next, *prev;
} netdata_rwlock_locker;
typedef struct netdata_rwlock_t {
@@ -37,6 +52,7 @@ typedef struct netdata_rwlock_t {
size_t writers; // the number of writers on the lock
netdata_mutex_t lockers_mutex; // a mutex to protect the linked list of the lock holding threads
netdata_rwlock_locker *lockers; // the linked list of the lock holding threads
+ Pvoid_t lockers_pid_JudyL;
} netdata_rwlock_t;
#define NETDATA_RWLOCK_INITIALIZER { \
@@ -44,7 +60,8 @@ typedef struct netdata_rwlock_t {
.readers = 0, \
.writers = 0, \
.lockers_mutex = NETDATA_MUTEX_INITIALIZER, \
- .lockers = NULL \
+ .lockers = NULL, \
+ .lockers_pid_JudyL = NULL, \
}
#else // NETDATA_TRACE_RWLOCKS
diff --git a/libnetdata/log/README.md b/libnetdata/log/README.md
index a767dd44..5f9e5bc7 100644
--- a/libnetdata/log/README.md
+++ b/libnetdata/log/README.md
@@ -1,5 +1,15 @@
<!--
+title "Log"
custom_edit_url: https://github.com/netdata/netdata/edit/master/libnetdata/log/README.md
+sidebar_label: "Log"
+learn_status: "Published"
+learn_topic_type: "Tasks"
+learn_rel_path: "Developers/libnetdata libraries"
-->
+# Log
+
+The netdata log library supports debug, info, error and fatal error logging.
+By default we have an access log, an error log and a collectors log.
+
diff --git a/libnetdata/log/log.c b/libnetdata/log/log.c
index fb3b2d03..1dcdba9c 100644
--- a/libnetdata/log/log.c
+++ b/libnetdata/log/log.c
@@ -14,6 +14,7 @@ uint64_t debug_flags = 0;
int access_log_syslog = 1;
int error_log_syslog = 1;
+int collector_log_syslog = 1;
int output_log_syslog = 1; // debug log
int health_log_syslog = 1;
@@ -23,11 +24,15 @@ FILE *stdaccess = NULL;
int stdhealth_fd = -1;
FILE *stdhealth = NULL;
+int stdcollector_fd = -1;
+FILE *stderror = NULL;
+
const char *stdaccess_filename = NULL;
const char *stderr_filename = NULL;
const char *stdout_filename = NULL;
const char *facility_log = NULL;
const char *stdhealth_filename = NULL;
+const char *stdcollector_filename = NULL;
#ifdef ENABLE_ACLK
const char *aclklog_filename = NULL;
@@ -573,8 +578,14 @@ void reopen_all_log_files() {
if(stdout_filename)
open_log_file(STDOUT_FILENO, stdout, stdout_filename, &output_log_syslog, 0, NULL);
- if(stderr_filename)
- open_log_file(STDERR_FILENO, stderr, stderr_filename, &error_log_syslog, 0, NULL);
+ if(stdcollector_filename)
+ open_log_file(STDERR_FILENO, stderr, stdcollector_filename, &collector_log_syslog, 0, NULL);
+
+ if(stderr_filename) {
+ log_lock();
+ stderror = open_log_file(stdcollector_fd, stderror, stderr_filename, &error_log_syslog, 1, &stdcollector_fd);
+ log_unlock();
+ }
#ifdef ENABLE_ACLK
if (aclklog_enabled)
@@ -593,7 +604,11 @@ void open_all_log_files() {
open_log_file(STDIN_FILENO, stdin, "/dev/null", NULL, 0, NULL);
open_log_file(STDOUT_FILENO, stdout, stdout_filename, &output_log_syslog, 0, NULL);
- open_log_file(STDERR_FILENO, stderr, stderr_filename, &error_log_syslog, 0, NULL);
+ open_log_file(STDERR_FILENO, stderr, stdcollector_filename, &collector_log_syslog, 0, NULL);
+
+ log_lock();
+ stderror = open_log_file(stdcollector_fd, NULL, stderr_filename, &error_log_syslog, 1, &stdcollector_fd);
+ log_unlock();
#ifdef ENABLE_ACLK
if(aclklog_enabled)
@@ -616,7 +631,9 @@ int error_log_limit(int reset) {
static time_t start = 0;
static unsigned long counter = 0, prevented = 0;
- // fprintf(stderr, "FLOOD: counter=%lu, allowed=%lu, backup=%lu, period=%llu\n", counter, error_log_errors_per_period, error_log_errors_per_period_backup, (unsigned long long)error_log_throttle_period);
+ FILE *fp = (!stderror) ? stderr : stderror;
+
+ // fprintf(fp, "FLOOD: counter=%lu, allowed=%lu, backup=%lu, period=%llu\n", counter, error_log_errors_per_period, error_log_errors_per_period_backup, (unsigned long long)error_log_throttle_period);
// do not throttle if the period is 0
if(error_log_throttle_period == 0)
@@ -638,7 +655,7 @@ int error_log_limit(int reset) {
char date[LOG_DATE_LENGTH];
log_date(date, LOG_DATE_LENGTH, now_realtime_sec());
fprintf(
- stderr,
+ fp,
"%s: %s LOG FLOOD PROTECTION reset for process '%s' "
"(prevented %lu logs in the last %"PRId64" seconds).\n",
date,
@@ -661,7 +678,7 @@ int error_log_limit(int reset) {
char date[LOG_DATE_LENGTH];
log_date(date, LOG_DATE_LENGTH, now_realtime_sec());
fprintf(
- stderr,
+ fp,
"%s: %s LOG FLOOD PROTECTION resuming logging from process '%s' "
"(prevented %lu logs in the last %"PRId64" seconds).\n",
date,
@@ -685,7 +702,7 @@ int error_log_limit(int reset) {
char date[LOG_DATE_LENGTH];
log_date(date, LOG_DATE_LENGTH, now_realtime_sec());
fprintf(
- stderr,
+ fp,
"%s: %s LOG FLOOD PROTECTION too many logs (%lu logs in %"PRId64" seconds, threshold is set to %lu logs "
"in %"PRId64" seconds). Preventing more logs from process '%s' for %"PRId64" seconds.\n",
date,
@@ -758,9 +775,10 @@ void debug_int( const char *file, const char *function, const unsigned long line
// ----------------------------------------------------------------------------
// info log
-void info_int( const char *file __maybe_unused, const char *function __maybe_unused, const unsigned long line __maybe_unused, const char *fmt, ... )
+void info_int( int is_collector, const char *file __maybe_unused, const char *function __maybe_unused, const unsigned long line __maybe_unused, const char *fmt, ... )
{
va_list args;
+ FILE *fp = (is_collector || !stderror) ? stderr : stderror;
log_lock();
@@ -770,7 +788,7 @@ void info_int( const char *file __maybe_unused, const char *function __maybe_unu
return;
}
- if(error_log_syslog) {
+ if(collector_log_syslog) {
va_start( args, fmt );
vsyslog(LOG_INFO, fmt, args );
va_end( args );
@@ -781,14 +799,15 @@ void info_int( const char *file __maybe_unused, const char *function __maybe_unu
va_start( args, fmt );
#ifdef NETDATA_INTERNAL_CHECKS
- fprintf(stderr, "%s: %s INFO : %s : (%04lu@%-20.20s:%-15.15s): ", date, program_name, netdata_thread_tag(), line, file, function);
+ fprintf(fp, "%s: %s INFO : %s : (%04lu@%-20.20s:%-15.15s): ",
+ date, program_name, netdata_thread_tag(), line, file, function);
#else
- fprintf(stderr, "%s: %s INFO : %s : ", date, program_name, netdata_thread_tag());
+ fprintf(fp, "%s: %s INFO : %s : ", date, program_name, netdata_thread_tag());
#endif
- vfprintf( stderr, fmt, args );
+ vfprintf(fp, fmt, args );
va_end( args );
- fputc('\n', stderr);
+ fputc('\n', fp);
log_unlock();
}
@@ -819,6 +838,8 @@ static const char *strerror_result_string(const char *a, const char *b) { (void)
#endif
void error_limit_int(ERROR_LIMIT *erl, const char *prefix, const char *file __maybe_unused, const char *function __maybe_unused, const unsigned long line __maybe_unused, const char *fmt, ... ) {
+ FILE *fp = (!stderror) ? stderr : stderror;
+
if(erl->sleep_ut)
sleep_usec(erl->sleep_ut);
@@ -842,7 +863,7 @@ void error_limit_int(ERROR_LIMIT *erl, const char *prefix, const char *file __ma
return;
}
- if(error_log_syslog) {
+ if(collector_log_syslog) {
va_start( args, fmt );
vsyslog(LOG_ERR, fmt, args );
va_end( args );
@@ -853,26 +874,29 @@ void error_limit_int(ERROR_LIMIT *erl, const char *prefix, const char *file __ma
va_start( args, fmt );
#ifdef NETDATA_INTERNAL_CHECKS
- fprintf(stderr, "%s: %s %-5.5s : %s : (%04lu@%-20.20s:%-15.15s): ", date, program_name, prefix, netdata_thread_tag(), line, file, function);
+ fprintf(fp, "%s: %s %-5.5s : %s : (%04lu@%-20.20s:%-15.15s): ",
+ date, program_name, prefix, netdata_thread_tag(), line, file, function);
#else
- fprintf(stderr, "%s: %s %-5.5s : %s : ", date, program_name, prefix, netdata_thread_tag());
+ fprintf(fp, "%s: %s %-5.5s : %s : ", date, program_name, prefix, netdata_thread_tag());
#endif
- vfprintf( stderr, fmt, args );
+ vfprintf(fp, fmt, args );
va_end( args );
if(erl->count > 1)
- fprintf(stderr, " (similar messages repeated %zu times in the last %llu secs)", erl->count, (unsigned long long)(erl->last_logged ? now - erl->last_logged : 0));
+ fprintf(fp, " (similar messages repeated %zu times in the last %llu secs)",
+ erl->count, (unsigned long long)(erl->last_logged ? now - erl->last_logged : 0));
if(erl->sleep_ut)
- fprintf(stderr, " (sleeping for %llu microseconds every time this happens)", erl->sleep_ut);
+ fprintf(fp, " (sleeping for %llu microseconds every time this happens)", erl->sleep_ut);
if(__errno) {
char buf[1024];
- fprintf(stderr, " (errno %d, %s)\n", __errno, strerror_result(strerror_r(__errno, buf, 1023), buf));
+ fprintf(fp,
+ " (errno %d, %s)\n", __errno, strerror_result(strerror_r(__errno, buf, 1023), buf));
errno = 0;
}
else
- fputc('\n', stderr);
+ fputc('\n', fp);
erl->last_logged = now;
erl->count = 0;
@@ -880,9 +904,10 @@ void error_limit_int(ERROR_LIMIT *erl, const char *prefix, const char *file __ma
log_unlock();
}
-void error_int(const char *prefix, const char *file __maybe_unused, const char *function __maybe_unused, const unsigned long line __maybe_unused, const char *fmt, ... ) {
+void error_int(int is_collector, const char *prefix, const char *file __maybe_unused, const char *function __maybe_unused, const unsigned long line __maybe_unused, const char *fmt, ... ) {
// save a copy of errno - just in case this function generates a new error
int __errno = errno;
+ FILE *fp = (is_collector || !stderror) ? stderr : stderror;
va_list args;
@@ -894,7 +919,7 @@ void error_int(const char *prefix, const char *file __maybe_unused, const char *
return;
}
- if(error_log_syslog) {
+ if(collector_log_syslog) {
va_start( args, fmt );
vsyslog(LOG_ERR, fmt, args );
va_end( args );
@@ -905,20 +930,22 @@ void error_int(const char *prefix, const char *file __maybe_unused, const char *
va_start( args, fmt );
#ifdef NETDATA_INTERNAL_CHECKS
- fprintf(stderr, "%s: %s %-5.5s : %s : (%04lu@%-20.20s:%-15.15s): ", date, program_name, prefix, netdata_thread_tag(), line, file, function);
+ fprintf(fp, "%s: %s %-5.5s : %s : (%04lu@%-20.20s:%-15.15s): ",
+ date, program_name, prefix, netdata_thread_tag(), line, file, function);
#else
- fprintf(stderr, "%s: %s %-5.5s : %s : ", date, program_name, prefix, netdata_thread_tag());
+ fprintf(fp, "%s: %s %-5.5s : %s : ", date, program_name, prefix, netdata_thread_tag());
#endif
- vfprintf( stderr, fmt, args );
+ vfprintf(fp, fmt, args );
va_end( args );
if(__errno) {
char buf[1024];
- fprintf(stderr, " (errno %d, %s)\n", __errno, strerror_result(strerror_r(__errno, buf, 1023), buf));
+ fprintf(fp,
+ " (errno %d, %s)\n", __errno, strerror_result(strerror_r(__errno, buf, 1023), buf));
errno = 0;
}
else
- fputc('\n', stderr);
+ fputc('\n', fp);
log_unlock();
}
@@ -933,23 +960,27 @@ static void crash_netdata(void) {
#ifdef HAVE_BACKTRACE
#define BT_BUF_SIZE 100
static void print_call_stack(void) {
+ FILE *fp = (!stderror) ? stderr : stderror;
+
int nptrs;
void *buffer[BT_BUF_SIZE];
nptrs = backtrace(buffer, BT_BUF_SIZE);
if(nptrs)
- backtrace_symbols_fd(buffer, nptrs, fileno(stderr));
+ backtrace_symbols_fd(buffer, nptrs, fileno(fp));
}
#endif
void fatal_int( const char *file, const char *function, const unsigned long line, const char *fmt, ... ) {
+ FILE *fp = (!stderror) ? stderr : stderror;
+
// save a copy of errno - just in case this function generates a new error
int __errno = errno;
va_list args;
const char *thread_tag;
char os_threadname[NETDATA_THREAD_NAME_MAX + 1];
- if(error_log_syslog) {
+ if(collector_log_syslog) {
va_start( args, fmt );
vsyslog(LOG_CRIT, fmt, args );
va_end( args );
@@ -970,15 +1001,16 @@ void fatal_int( const char *file, const char *function, const unsigned long line
va_start( args, fmt );
#ifdef NETDATA_INTERNAL_CHECKS
- fprintf(stderr, "%s: %s FATAL : %s : (%04lu@%-20.20s:%-15.15s): ", date, program_name, thread_tag, line, file, function);
+ fprintf(fp,
+ "%s: %s FATAL : %s : (%04lu@%-20.20s:%-15.15s): ", date, program_name, thread_tag, line, file, function);
#else
- fprintf(stderr, "%s: %s FATAL : %s : ", date, program_name, thread_tag);
+ fprintf(fp, "%s: %s FATAL : %s : ", date, program_name, thread_tag);
#endif
- vfprintf( stderr, fmt, args );
+ vfprintf(fp, fmt, args );
va_end( args );
perror(" # ");
- fputc('\n', stderr);
+ fputc('\n', fp);
log_unlock();
@@ -986,7 +1018,15 @@ void fatal_int( const char *file, const char *function, const unsigned long line
snprintfz(action_data, 70, "%04lu@%-10.10s:%-15.15s/%d", line, file, function, __errno);
char action_result[60+1];
- snprintfz(action_result, 60, "%s:%s", program_name, strncmp(thread_tag, "STREAM_RECEIVER", strlen("STREAM_RECEIVER")) ? thread_tag : "[x]");
+ const char *tag_to_send = thread_tag;
+
+ // anonymize thread names
+ if(strncmp(thread_tag, THREAD_TAG_STREAM_RECEIVER, strlen(THREAD_TAG_STREAM_RECEIVER)) == 0)
+ tag_to_send = THREAD_TAG_STREAM_RECEIVER;
+ if(strncmp(thread_tag, THREAD_TAG_STREAM_SENDER, strlen(THREAD_TAG_STREAM_SENDER)) == 0)
+ tag_to_send = THREAD_TAG_STREAM_SENDER;
+
+ snprintfz(action_result, 60, "%s:%s", program_name, tag_to_send);
send_statistics("FATAL", action_result, action_data);
#ifdef HAVE_BACKTRACE
diff --git a/libnetdata/log/log.h b/libnetdata/log/log.h
index 11dab4c1..3d9f0927 100644
--- a/libnetdata/log/log.h
+++ b/libnetdata/log/log.h
@@ -61,10 +61,14 @@ extern FILE *stdaccess;
extern int stdhealth_fd;
extern FILE *stdhealth;
+extern int stdcollector_fd;
+extern FILE *stderror;
+
extern const char *stdaccess_filename;
extern const char *stderr_filename;
extern const char *stdout_filename;
extern const char *stdhealth_filename;
+extern const char *stdcollector_filename;
extern const char *facility_log;
#ifdef ENABLE_ACLK
@@ -106,7 +110,7 @@ typedef struct error_with_limit {
#ifdef NETDATA_INTERNAL_CHECKS
#define debug(type, args...) do { if(unlikely(debug_flags & type)) debug_int(__FILE__, __FUNCTION__, __LINE__, ##args); } while(0)
-#define internal_error(condition, args...) do { if(unlikely(condition)) error_int("IERR", __FILE__, __FUNCTION__, __LINE__, ##args); } while(0)
+#define internal_error(condition, args...) do { if(unlikely(condition)) error_int(0, "IERR", __FILE__, __FUNCTION__, __LINE__, ##args); } while(0)
#define internal_fatal(condition, args...) do { if(unlikely(condition)) fatal_int(__FILE__, __FUNCTION__, __LINE__, ##args); } while(0)
#else
#define debug(type, args...) debug_dummy()
@@ -114,17 +118,20 @@ typedef struct error_with_limit {
#define internal_fatal(args...) debug_dummy()
#endif
-#define info(args...) info_int(__FILE__, __FUNCTION__, __LINE__, ##args)
-#define infoerr(args...) error_int("INFO", __FILE__, __FUNCTION__, __LINE__, ##args)
-#define error(args...) error_int("ERROR", __FILE__, __FUNCTION__, __LINE__, ##args)
+#define info(args...) info_int(0, __FILE__, __FUNCTION__, __LINE__, ##args)
+#define collector_info(args...) info_int(1, __FILE__, __FUNCTION__, __LINE__, ##args)
+#define infoerr(args...) error_int(0, "INFO", __FILE__, __FUNCTION__, __LINE__, ##args)
+#define error(args...) error_int(0, "ERROR", __FILE__, __FUNCTION__, __LINE__, ##args)
+#define collector_infoerr(args...) error_int(1, "INFO", __FILE__, __FUNCTION__, __LINE__, ##args)
+#define collector_error(args...) error_int(1, "ERROR", __FILE__, __FUNCTION__, __LINE__, ##args)
#define error_limit(erl, args...) error_limit_int(erl, "ERROR", __FILE__, __FUNCTION__, __LINE__, ##args)
#define fatal(args...) fatal_int(__FILE__, __FUNCTION__, __LINE__, ##args)
#define fatal_assert(expr) ((expr) ? (void)(0) : fatal_int(__FILE__, __FUNCTION__, __LINE__, "Assertion `%s' failed", #expr))
void send_statistics(const char *action, const char *action_result, const char *action_data);
void debug_int( const char *file, const char *function, const unsigned long line, const char *fmt, ... ) PRINTFLIKE(4, 5);
-void info_int( const char *file, const char *function, const unsigned long line, const char *fmt, ... ) PRINTFLIKE(4, 5);
-void error_int( const char *prefix, const char *file, const char *function, const unsigned long line, const char *fmt, ... ) PRINTFLIKE(5, 6);
+void info_int( int is_collector, const char *file, const char *function, const unsigned long line, const char *fmt, ... ) PRINTFLIKE(5, 6);
+void error_int( int is_collector, const char *prefix, const char *file, const char *function, const unsigned long line, const char *fmt, ... ) PRINTFLIKE(6, 7);
void error_limit_int(ERROR_LIMIT *erl, const char *prefix, const char *file __maybe_unused, const char *function __maybe_unused, unsigned long line __maybe_unused, const char *fmt, ... ) PRINTFLIKE(6, 7);;
void fatal_int( const char *file, const char *function, const unsigned long line, const char *fmt, ... ) NORETURN PRINTFLIKE(4, 5);
void log_access( const char *fmt, ... ) PRINTFLIKE(1, 2);
diff --git a/libnetdata/onewayalloc/README.md b/libnetdata/onewayalloc/README.md
index 1f459c26..3fa0d9fd 100644
--- a/libnetdata/onewayalloc/README.md
+++ b/libnetdata/onewayalloc/README.md
@@ -1,6 +1,10 @@
<!--
title: "One Way Allocator"
custom_edit_url: https://github.com/netdata/netdata/edit/master/libnetdata/onewayallocator/README.md
+sidebar_label: "One way allocator"
+learn_status: "Published"
+learn_topic_type: "Tasks"
+learn_rel_path: "Developers/libnetdata libraries"
-->
# One Way Allocator
diff --git a/libnetdata/onewayalloc/onewayalloc.c b/libnetdata/onewayalloc/onewayalloc.c
index 59c3b685..2f007b18 100644
--- a/libnetdata/onewayalloc/onewayalloc.c
+++ b/libnetdata/onewayalloc/onewayalloc.c
@@ -14,6 +14,12 @@ typedef struct owa_page {
struct owa_page *last; // the last page on the list - we currently allocate on this
} OWA_PAGE;
+static size_t onewayalloc_total_memory = 0;
+
+size_t onewayalloc_allocated_memory(void) {
+ return __atomic_load_n(&onewayalloc_total_memory, __ATOMIC_RELAXED);
+}
+
// allocations need to be aligned to CPU register width
// https://en.wikipedia.org/wiki/Data_structure_alignment
static inline size_t natural_alignment(size_t size) {
@@ -60,6 +66,7 @@ static OWA_PAGE *onewayalloc_create_internal(OWA_PAGE *head, size_t size_hint) {
// OWA_PAGE *page = (OWA_PAGE *)netdata_mmap(NULL, size, MAP_ANONYMOUS|MAP_PRIVATE, 0);
// if(unlikely(!page)) fatal("Cannot allocate onewayalloc buffer of size %zu", size);
OWA_PAGE *page = (OWA_PAGE *)mallocz(size);
+ __atomic_add_fetch(&onewayalloc_total_memory, size, __ATOMIC_RELAXED);
page->size = size;
page->offset = natural_alignment(sizeof(OWA_PAGE));
@@ -183,11 +190,17 @@ void onewayalloc_destroy(ONEWAYALLOC *owa) {
// head->stats_mallocs_made, head->stats_mallocs_size,
// head->stats_pages, head->stats_pages_size);
+ size_t total_size = 0;
OWA_PAGE *page = head;
while(page) {
+ total_size += page->size;
+
OWA_PAGE *p = page;
page = page->next;
+
// munmap(p, p->size);
freez(p);
}
+
+ __atomic_sub_fetch(&onewayalloc_total_memory, total_size, __ATOMIC_RELAXED);
}
diff --git a/libnetdata/onewayalloc/onewayalloc.h b/libnetdata/onewayalloc/onewayalloc.h
index e536e054..a415b063 100644
--- a/libnetdata/onewayalloc/onewayalloc.h
+++ b/libnetdata/onewayalloc/onewayalloc.h
@@ -16,4 +16,6 @@ void onewayalloc_freez(ONEWAYALLOC *owa, const void *ptr);
void *onewayalloc_doublesize(ONEWAYALLOC *owa, const void *src, size_t oldsize);
+size_t onewayalloc_allocated_memory(void);
+
#endif // ONEWAYALLOC_H
diff --git a/libnetdata/os.c b/libnetdata/os.c
index 196288a6..133c0224 100644
--- a/libnetdata/os.c
+++ b/libnetdata/os.c
@@ -6,61 +6,77 @@
// system functions
// to retrieve settings of the system
-int processors = 1;
-long get_system_cpus(void) {
- processors = 1;
+#define CPUS_FOR_COLLECTORS 0
+#define CPUS_FOR_NETDATA 1
-#ifdef __APPLE__
- int32_t tmp_processors;
+long get_system_cpus_with_cache(bool cache, bool for_netdata) {
+ static long processors[2] = { 0, 0 };
- if (unlikely(GETSYSCTL_BY_NAME("hw.logicalcpu", tmp_processors))) {
- error("Assuming system has %d processors.", processors);
- } else {
- processors = tmp_processors;
- }
+ int index = for_netdata ? CPUS_FOR_NETDATA : CPUS_FOR_COLLECTORS;
+
+ if(likely(cache && processors[index] > 0))
+ return processors[index];
+
+#if defined(__APPLE__) || defined(__FreeBSD__)
+#if defined(__APPLE__)
+#define HW_CPU_NAME "hw.logicalcpu"
+#else
+#define HW_CPU_NAME "hw.ncpu"
+#endif
- return processors;
-#elif __FreeBSD__
int32_t tmp_processors;
+ bool error = false;
- if (unlikely(GETSYSCTL_BY_NAME("hw.ncpu", tmp_processors))) {
- error("Assuming system has %d processors.", processors);
- } else {
- processors = tmp_processors;
- }
+ if (unlikely(GETSYSCTL_BY_NAME(HW_CPU_NAME, tmp_processors)))
+ error = true;
+ else
+ processors[index] = tmp_processors;
+
+ if(processors[index] < 1) {
+ processors[index] = 1;
- return processors;
+ if(error)
+ error("Assuming system has %d processors.", processors[index]);
+ }
+
+ return processors[index];
#else
char filename[FILENAME_MAX + 1];
- snprintfz(filename, FILENAME_MAX, "%s/proc/stat", netdata_configured_host_prefix);
+ snprintfz(filename, FILENAME_MAX, "%s/proc/stat",
+ (!for_netdata && netdata_configured_host_prefix) ? netdata_configured_host_prefix : "");
procfile *ff = procfile_open(filename, NULL, PROCFILE_FLAG_DEFAULT);
if(!ff) {
- error("Cannot open file '%s'. Assuming system has %d processors.", filename, processors);
- return processors;
+ processors[index] = 1;
+ error("Cannot open file '%s'. Assuming system has %ld processors.", filename, processors[index]);
+ return processors[index];
}
ff = procfile_readall(ff);
if(!ff) {
- error("Cannot open file '%s'. Assuming system has %d processors.", filename, processors);
- return processors;
+ processors[index] = 1;
+ error("Cannot open file '%s'. Assuming system has %ld processors.", filename, processors[index]);
+ return processors[index];
}
- processors = 0;
+ long tmp_processors = 0;
unsigned int i;
for(i = 0; i < procfile_lines(ff); i++) {
if(!procfile_linewords(ff, i)) continue;
- if(strncmp(procfile_lineword(ff, i, 0), "cpu", 3) == 0) processors++;
+ if(strncmp(procfile_lineword(ff, i, 0), "cpu", 3) == 0)
+ tmp_processors++;
}
- processors--;
- if(processors < 1) processors = 1;
-
procfile_close(ff);
- debug(D_SYSTEM, "System has %d processors.", processors);
- return processors;
+ processors[index] = --tmp_processors;
+
+ if(processors[index] < 1)
+ processors[index] = 1;
+
+ debug(D_SYSTEM, "System has %ld processors.", processors[index]);
+ return processors[index];
#endif /* __APPLE__, __FreeBSD__ */
}
@@ -90,7 +106,7 @@ pid_t get_system_pid_max(void) {
read = 1;
char filename[FILENAME_MAX + 1];
- snprintfz(filename, FILENAME_MAX, "%s/proc/sys/kernel/pid_max", netdata_configured_host_prefix);
+ snprintfz(filename, FILENAME_MAX, "%s/proc/sys/kernel/pid_max", netdata_configured_host_prefix?netdata_configured_host_prefix:"");
unsigned long long max = 0;
if(read_single_number_file(filename, &max) != 0) {
@@ -120,6 +136,56 @@ void get_system_HZ(void) {
system_hz = (unsigned int) ticks;
}
+static inline unsigned long cpuset_str2ul(char **s) {
+ unsigned long n = 0;
+ char c;
+ for(c = **s; c >= '0' && c <= '9' ; c = *(++*s)) {
+ n *= 10;
+ n += c - '0';
+ }
+ return n;
+}
+
+unsigned long read_cpuset_cpus(const char *filename, long system_cpus) {
+ static char *buf = NULL;
+ static size_t buf_size = 0;
+
+ if(!buf) {
+ buf_size = 100U + 6 * system_cpus; // taken from kernel/cgroup/cpuset.c
+ buf = mallocz(buf_size + 1);
+ }
+
+ int ret = read_file(filename, buf, buf_size);
+
+ if(!ret) {
+ char *s = buf;
+ unsigned long ncpus = 0;
+
+ // parse the cpuset string and calculate the number of cpus the cgroup is allowed to use
+ while(*s) {
+ unsigned long n = cpuset_str2ul(&s);
+ ncpus++;
+ if(*s == ',') {
+ s++;
+ continue;
+ }
+ if(*s == '-') {
+ s++;
+ unsigned long m = cpuset_str2ul(&s);
+ ncpus += m - n; // calculate the number of cpus in the region
+ }
+ s++;
+ }
+
+ if(!ncpus)
+ return 0;
+
+ return ncpus;
+ }
+
+ return 0;
+}
+
// =====================================================================================================================
// FreeBSD
diff --git a/libnetdata/os.h b/libnetdata/os.h
index 67abf0be..3cda79ed 100644
--- a/libnetdata/os.h
+++ b/libnetdata/os.h
@@ -48,8 +48,10 @@ int getsysctl_by_name(const char *name, void *ptr, size_t len);
extern const char *os_type;
-extern int processors;
-long get_system_cpus(void);
+#define get_system_cpus() get_system_cpus_with_cache(true, false)
+#define get_system_cpus_uncached() get_system_cpus_with_cache(false, false)
+long get_system_cpus_with_cache(bool cache, bool for_netdata);
+unsigned long read_cpuset_cpus(const char *filename, long system_cpus);
extern pid_t pid_max;
pid_t get_system_pid_max(void);
diff --git a/libnetdata/popen/README.md b/libnetdata/popen/README.md
index db4aefae..804690d1 100644
--- a/libnetdata/popen/README.md
+++ b/libnetdata/popen/README.md
@@ -1,5 +1,15 @@
<!--
+title: "popen"
custom_edit_url: https://github.com/netdata/netdata/edit/master/libnetdata/popen/README.md
+sidebar_label: "popen"
+learn_status: "Published"
+learn_topic_type: "Tasks"
+learn_rel_path: "Developers/libnetdata libraries"
-->
+# popen
+
+Process management library
+
+
diff --git a/libnetdata/popen/popen.c b/libnetdata/popen/popen.c
index 57f957f6..5ed74ae9 100644
--- a/libnetdata/popen/popen.c
+++ b/libnetdata/popen/popen.c
@@ -43,7 +43,7 @@ static void netdata_popen_tracking_add_pid_unsafe(pid_t pid) {
mp = mallocz(sizeof(struct netdata_popen));
mp->pid = pid;
- DOUBLE_LINKED_LIST_PREPEND_UNSAFE(netdata_popen_root, mp, prev, next);
+ DOUBLE_LINKED_LIST_PREPEND_ITEM_UNSAFE(netdata_popen_root, mp, prev, next);
}
// myp_del deletes pid if we're tracking.
@@ -61,7 +61,7 @@ static void netdata_popen_tracking_del_pid(pid_t pid) {
}
if(mp) {
- DOUBLE_LINKED_LIST_REMOVE_UNSAFE(netdata_popen_root, mp, prev, next);
+ DOUBLE_LINKED_LIST_REMOVE_ITEM_UNSAFE(netdata_popen_root, mp, prev, next);
freez(mp);
}
else
@@ -96,7 +96,7 @@ void netdata_popen_tracking_cleanup(void) {
while(netdata_popen_root) {
struct netdata_popen *mp = netdata_popen_root;
- DOUBLE_LINKED_LIST_REMOVE_UNSAFE(netdata_popen_root, mp, prev, next);
+ DOUBLE_LINKED_LIST_REMOVE_ITEM_UNSAFE(netdata_popen_root, mp, prev, next);
freez(mp);
}
@@ -163,8 +163,7 @@ static int popene_internal(volatile pid_t *pidptr, char **env, uint8_t flags, FI
posix_spawnattr_t attr;
posix_spawn_file_actions_t fa;
- int stdin_fd_to_exclude_from_closing = -1;
- int stdout_fd_to_exclude_from_closing = -1;
+ unsigned int fds_to_exclude_from_closing = OPEN_FD_EXCLUDE_STDERR;
if(posix_spawn_file_actions_init(&fa)) {
error("POPEN: posix_spawn_file_actions_init() failed.");
@@ -195,7 +194,7 @@ static int popene_internal(volatile pid_t *pidptr, char **env, uint8_t flags, FI
if (posix_spawn_file_actions_addopen(&fa, STDIN_FILENO, "/dev/null", O_RDONLY, 0)) {
error("POPEN: posix_spawn_file_actions_addopen() on stdin to /dev/null failed.");
// this is not a fatal error
- stdin_fd_to_exclude_from_closing = STDIN_FILENO;
+ fds_to_exclude_from_closing |= OPEN_FD_EXCLUDE_STDIN;
}
}
@@ -222,16 +221,13 @@ static int popene_internal(volatile pid_t *pidptr, char **env, uint8_t flags, FI
if (posix_spawn_file_actions_addopen(&fa, STDOUT_FILENO, "/dev/null", O_WRONLY, 0)) {
error("POPEN: posix_spawn_file_actions_addopen() on stdout to /dev/null failed.");
// this is not a fatal error
- stdout_fd_to_exclude_from_closing = STDOUT_FILENO;
+ fds_to_exclude_from_closing |= OPEN_FD_EXCLUDE_STDOUT;
}
}
if(flags & POPEN_FLAG_CLOSE_FD) {
// Mark all files to be closed by the exec() stage of posix_spawn()
- for(int i = (int)(sysconf(_SC_OPEN_MAX) - 1); i >= 0; i--) {
- if(likely(i != STDERR_FILENO && i != stdin_fd_to_exclude_from_closing && i != stdout_fd_to_exclude_from_closing))
- (void)fcntl(i, F_SETFD, FD_CLOEXEC);
- }
+ for_each_open_fd(OPEN_FD_ACTION_FD_CLOEXEC, fds_to_exclude_from_closing);
}
attr_rc = posix_spawnattr_init(&attr);
diff --git a/libnetdata/procfile/README.md b/libnetdata/procfile/README.md
index 65638030..8610e77e 100644
--- a/libnetdata/procfile/README.md
+++ b/libnetdata/procfile/README.md
@@ -1,6 +1,10 @@
<!--
title: "PROCFILE"
custom_edit_url: https://github.com/netdata/netdata/edit/master/libnetdata/procfile/README.md
+sidebar_label: "Procfile"
+learn_status: "Published"
+learn_topic_type: "Tasks"
+learn_rel_path: "Developers/libnetdata libraries"
-->
# PROCFILE
@@ -28,7 +32,7 @@ For each iteration, the caller:
- calls `procfile_readall()` to read updated contents.
This call also rewinds (`lseek()` to 0) before reading it.
- For every file, a [BUFFER](/libnetdata/buffer/README.md) is used that is automatically adjusted to fit the entire
+ For every file, a [BUFFER](https://github.com/netdata/netdata/blob/master/libnetdata/buffer/README.md) is used that is automatically adjusted to fit the entire
file contents of the file. So the file is read with a single `read()` call (providing atomicity / consistency when
the data are read from the kernel).
diff --git a/libnetdata/procfile/procfile.c b/libnetdata/procfile/procfile.c
index eb04316c..cdf0f972 100644
--- a/libnetdata/procfile/procfile.c
+++ b/libnetdata/procfile/procfile.c
@@ -296,7 +296,8 @@ procfile *procfile_readall(procfile *ff) {
debug(D_PROCFILE, "Reading file '%s', from position %zd with length %zd", procfile_filename(ff), s, (ssize_t)(ff->size - s));
r = read(ff->fd, &ff->data[s], ff->size - s);
if(unlikely(r == -1)) {
- if(unlikely(!(ff->flags & PROCFILE_FLAG_NO_ERROR_ON_FILE_IO))) error(PF_PREFIX ": Cannot read from file '%s' on fd %d", procfile_filename(ff), ff->fd);
+ if(unlikely(!(ff->flags & PROCFILE_FLAG_NO_ERROR_ON_FILE_IO))) collector_error(PF_PREFIX ": Cannot read from file '%s' on fd %d", procfile_filename(ff), ff->fd);
+ else if(unlikely(ff->flags & PROCFILE_FLAG_ERROR_ON_ERROR_LOG)) error(PF_PREFIX ": Cannot read from file '%s' on fd %d", procfile_filename(ff), ff->fd);
procfile_close(ff);
return NULL;
}
@@ -306,7 +307,8 @@ procfile *procfile_readall(procfile *ff) {
// debug(D_PROCFILE, "Rewinding file '%s'", ff->filename);
if(unlikely(lseek(ff->fd, 0, SEEK_SET) == -1)) {
- if(unlikely(!(ff->flags & PROCFILE_FLAG_NO_ERROR_ON_FILE_IO))) error(PF_PREFIX ": Cannot rewind on file '%s'.", procfile_filename(ff));
+ if(unlikely(!(ff->flags & PROCFILE_FLAG_NO_ERROR_ON_FILE_IO))) collector_error(PF_PREFIX ": Cannot rewind on file '%s'.", procfile_filename(ff));
+ else if(unlikely(ff->flags & PROCFILE_FLAG_ERROR_ON_ERROR_LOG)) error(PF_PREFIX ": Cannot rewind on file '%s'.", procfile_filename(ff));
procfile_close(ff);
return NULL;
}
@@ -403,7 +405,8 @@ procfile *procfile_open(const char *filename, const char *separators, uint32_t f
int fd = open(filename, procfile_open_flags, 0666);
if(unlikely(fd == -1)) {
- if(unlikely(!(flags & PROCFILE_FLAG_NO_ERROR_ON_FILE_IO))) error(PF_PREFIX ": Cannot open file '%s'", filename);
+ if(unlikely(!(flags & PROCFILE_FLAG_NO_ERROR_ON_FILE_IO))) collector_error(PF_PREFIX ": Cannot open file '%s'", filename);
+ else if(unlikely(flags & PROCFILE_FLAG_ERROR_ON_ERROR_LOG)) error(PF_PREFIX ": Cannot open file '%s'", filename);
return NULL;
}
diff --git a/libnetdata/procfile/procfile.h b/libnetdata/procfile/procfile.h
index cae4ad48..8db5b45f 100644
--- a/libnetdata/procfile/procfile.h
+++ b/libnetdata/procfile/procfile.h
@@ -34,8 +34,9 @@ typedef struct {
// ----------------------------------------------------------------------------
// The procfile
-#define PROCFILE_FLAG_DEFAULT 0x00000000
-#define PROCFILE_FLAG_NO_ERROR_ON_FILE_IO 0x00000001
+#define PROCFILE_FLAG_DEFAULT 0x00000000 // To store inside `collector.log`
+#define PROCFILE_FLAG_NO_ERROR_ON_FILE_IO 0x00000001 // Do not store nothing
+#define PROCFILE_FLAG_ERROR_ON_ERROR_LOG 0x00000002 // Store inside `error.log`
typedef enum __attribute__ ((__packed__)) procfile_separator {
PF_CHAR_IS_SEPARATOR,
diff --git a/libnetdata/required_dummies.h b/libnetdata/required_dummies.h
index ad1e8fb8..5a0d4e05 100644
--- a/libnetdata/required_dummies.h
+++ b/libnetdata/required_dummies.h
@@ -36,6 +36,7 @@ int health_variable_lookup(STRING *variable, struct rrdcalc *rc, NETDATA_DOUBLE
void rrdset_thread_rda_free(void){};
void sender_thread_buffer_free(void){};
void query_target_free(void){};
+void service_exits(void){};
// required by get_system_cpus()
char *netdata_configured_host_prefix = "";
diff --git a/libnetdata/simple_pattern/README.md b/libnetdata/simple_pattern/README.md
index cb377f84..a0a7cf68 100644
--- a/libnetdata/simple_pattern/README.md
+++ b/libnetdata/simple_pattern/README.md
@@ -1,10 +1,14 @@
<!--
-title: "Netdata simple patterns"
+title: "Simple patterns"
description: "Netdata supports simple patterns, which are less cryptic versions of regular expressions. Use familiar notation for powerful results."
custom_edit_url: https://github.com/netdata/netdata/edit/master/libnetdata/simple_pattern/README.md
+sidebar_label: "Simple patterns"
+learn_status: "Published"
+learn_topic_type: "Tasks"
+learn_rel_path: "Developers/libnetdata libraries"
-->
-# Netdata simple patterns
+# Simple patterns
Unix prefers regular expressions. But they are just too hard, too cryptic
to use, write and understand.
diff --git a/libnetdata/socket/security.c b/libnetdata/socket/security.c
index 88b3f6d9..7c509215 100644
--- a/libnetdata/socket/security.c
+++ b/libnetdata/socket/security.c
@@ -310,7 +310,7 @@ int security_process_accept(SSL *ssl,int msg) {
int counter = 0;
while ((err = ERR_get_error()) != 0) {
ERR_error_string_n(err, buf, sizeof(buf));
- info("%d SSL Handshake error (%s) on socket %d ", counter++, ERR_error_string((long)SSL_get_error(ssl, test), NULL), sock);
+ error("%d SSL Handshake error (%s) on socket %d", counter++, ERR_error_string((long)SSL_get_error(ssl, test), NULL), sock);
}
return NETDATA_SSL_NO_HANDSHAKE;
}
diff --git a/libnetdata/socket/socket.c b/libnetdata/socket/socket.c
index 40271b62..69124b94 100644
--- a/libnetdata/socket/socket.c
+++ b/libnetdata/socket/socket.c
@@ -926,13 +926,17 @@ ssize_t netdata_ssl_read(SSL *ssl, void *buf, size_t num) {
int bytes, err, retries = 0;
//do {
- bytes = SSL_read(ssl, buf, (int)num);
- err = SSL_get_error(ssl, bytes);
- retries++;
- //} while (bytes <= 0 && (err == SSL_ERROR_WANT_READ));
+ bytes = SSL_read(ssl, buf, (int)num);
+ err = SSL_get_error(ssl, bytes);
+ retries++;
+ //} while (bytes <= 0 && err == SSL_ERROR_WANT_READ);
- if(unlikely(bytes <= 0))
- error("SSL_read() returned %d bytes, SSL error %d", bytes, err);
+ if(unlikely(bytes <= 0)) {
+ if (err == SSL_ERROR_WANT_WRITE || err == SSL_ERROR_WANT_READ) {
+ bytes = 0;
+ } else
+ error("SSL_write() returned %d bytes, SSL error %d", bytes, err);
+ }
if(retries > 1)
error_limit(&erl, "SSL_read() retried %d times", retries);
@@ -947,17 +951,21 @@ ssize_t netdata_ssl_write(SSL *ssl, const void *buf, size_t num) {
size_t total = 0;
//do {
- bytes = SSL_write(ssl, (uint8_t *)buf + total, (int)(num - total));
- err = SSL_get_error(ssl, bytes);
- retries++;
+ bytes = SSL_write(ssl, (uint8_t *)buf + total, (int)(num - total));
+ err = SSL_get_error(ssl, bytes);
+ retries++;
- if(bytes > 0)
- total += bytes;
+ if(bytes > 0)
+ total += bytes;
//} while ((bytes <= 0 && (err == SSL_ERROR_WANT_WRITE)) || (bytes > 0 && total < num));
- if(unlikely(bytes <= 0))
- error("SSL_write() returned %d bytes, SSL error %d", bytes, err);
+ if(unlikely(bytes <= 0)) {
+ if (err == SSL_ERROR_WANT_WRITE || err == SSL_ERROR_WANT_READ) {
+ bytes = 0;
+ } else
+ error("SSL_write() returned %d bytes, SSL error %d", bytes, err);
+ }
if(retries > 1)
error_limit(&erl, "SSL_write() retried %d times", retries);
@@ -1633,6 +1641,7 @@ void poll_events(LISTEN_SOCKETS *sockets
, int (*rcv_callback)(POLLINFO * /*pi*/, short int * /*events*/)
, int (*snd_callback)(POLLINFO * /*pi*/, short int * /*events*/)
, void (*tmr_callback)(void * /*timer_data*/)
+ , bool (*check_to_stop_callback)(void)
, SIMPLE_PATTERN *access_list
, int allow_dns
, void *data
@@ -1715,7 +1724,7 @@ void poll_events(LISTEN_SOCKETS *sockets
netdata_thread_cleanup_push(poll_events_cleanup, &p);
- while(!netdata_exit) {
+ while(!check_to_stop_callback()) {
if(unlikely(timer_usec)) {
now_usec = now_boottime_usec();
diff --git a/libnetdata/socket/socket.h b/libnetdata/socket/socket.h
index 28232427..9577453d 100644
--- a/libnetdata/socket/socket.h
+++ b/libnetdata/socket/socket.h
@@ -10,18 +10,18 @@
#endif
typedef enum web_client_acl {
- WEB_CLIENT_ACL_NONE = 0,
- WEB_CLIENT_ACL_NOCHECK = 0,
- WEB_CLIENT_ACL_DASHBOARD = 1 << 0,
- WEB_CLIENT_ACL_REGISTRY = 1 << 1,
- WEB_CLIENT_ACL_BADGE = 1 << 2,
- WEB_CLIENT_ACL_MGMT = 1 << 3,
- WEB_CLIENT_ACL_STREAMING = 1 << 4,
- WEB_CLIENT_ACL_NETDATACONF = 1 << 5,
- WEB_CLIENT_ACL_SSL_OPTIONAL = 1 << 6,
- WEB_CLIENT_ACL_SSL_FORCE = 1 << 7,
- WEB_CLIENT_ACL_SSL_DEFAULT = 1 << 8,
- WEB_CLIENT_ACL_ACLK = 1 << 9,
+ WEB_CLIENT_ACL_NONE = (0),
+ WEB_CLIENT_ACL_NOCHECK = (0),
+ WEB_CLIENT_ACL_DASHBOARD = (1 << 0),
+ WEB_CLIENT_ACL_REGISTRY = (1 << 1),
+ WEB_CLIENT_ACL_BADGE = (1 << 2),
+ WEB_CLIENT_ACL_MGMT = (1 << 3),
+ WEB_CLIENT_ACL_STREAMING = (1 << 4),
+ WEB_CLIENT_ACL_NETDATACONF = (1 << 5),
+ WEB_CLIENT_ACL_SSL_OPTIONAL = (1 << 6),
+ WEB_CLIENT_ACL_SSL_FORCE = (1 << 7),
+ WEB_CLIENT_ACL_SSL_DEFAULT = (1 << 8),
+ WEB_CLIENT_ACL_ACLK = (1 << 9),
} WEB_CLIENT_ACL;
#define WEB_CLIENT_ACL_ALL 0xFFFF
@@ -202,6 +202,7 @@ void poll_events(LISTEN_SOCKETS *sockets
, int (*rcv_callback)(POLLINFO *pi, short int *events)
, int (*snd_callback)(POLLINFO *pi, short int *events)
, void (*tmr_callback)(void *timer_data)
+ , bool (*check_to_stop_callback)(void)
, SIMPLE_PATTERN *access_list
, int allow_dns
, void *data
diff --git a/libnetdata/statistical/README.md b/libnetdata/statistical/README.md
index f254081d..8fa101f0 100644
--- a/libnetdata/statistical/README.md
+++ b/libnetdata/statistical/README.md
@@ -1,5 +1,12 @@
<!--
+title: "Statistical functions"
custom_edit_url: https://github.com/netdata/netdata/edit/master/libnetdata/statistical/README.md
+sidebar_label: "Statistical functions"
+learn_status: "Published"
+learn_topic_type: "Tasks"
+learn_rel_path: "Developers/libnetdata libraries"
-->
+# Statistical functions
+A library for easy and fast calculations of statistical measurements like average, median etc.
diff --git a/libnetdata/storage_number/README.md b/libnetdata/storage_number/README.md
index 4cd19a98..da2c3ccf 100644
--- a/libnetdata/storage_number/README.md
+++ b/libnetdata/storage_number/README.md
@@ -1,6 +1,10 @@
<!--
title: "Netdata storage number"
custom_edit_url: https://github.com/netdata/netdata/edit/master/libnetdata/storage_number/README.md
+sidebar_label: "Storage number"
+learn_status: "Published"
+learn_topic_type: "Tasks"
+learn_rel_path: "Developers/libnetdata libraries"
-->
# Netdata storage number
diff --git a/libnetdata/string/README.md b/libnetdata/string/README.md
index e73ab269..b1c6e61c 100644
--- a/libnetdata/string/README.md
+++ b/libnetdata/string/README.md
@@ -1,5 +1,10 @@
<!--
+title: "String"
custom_edit_url: https://github.com/netdata/netdata/edit/master/libnetdata/string/README.md
+sidebar_label: "String"
+learn_status: "Published"
+learn_topic_type: "Tasks"
+learn_rel_path: "Developers/libnetdata libraries"
-->
# STRING
@@ -17,4 +22,4 @@ index lookup to find it.
Once there is a `STRING *`, the actual `const char *` can be accessed with `string2str()`.
-All STRING should be constant. Changing the contents of a `const char *` that has been acquired by `string2str()` should never happen. \ No newline at end of file
+All STRING should be constant. Changing the contents of a `const char *` that has been acquired by `string2str()` should never happen.
diff --git a/libnetdata/string/string.c b/libnetdata/string/string.c
index d2db8aab..4e232523 100644
--- a/libnetdata/string/string.c
+++ b/libnetdata/string/string.c
@@ -56,14 +56,29 @@ static struct string_hashtable {
#define string_stats_atomic_decrement(var) __atomic_sub_fetch(&string_base.var, 1, __ATOMIC_RELAXED)
void string_statistics(size_t *inserts, size_t *deletes, size_t *searches, size_t *entries, size_t *references, size_t *memory, size_t *duplications, size_t *releases) {
- *inserts = string_base.inserts;
- *deletes = string_base.deletes;
- *searches = string_base.searches;
- *entries = (size_t)string_base.entries;
- *references = (size_t)string_base.active_references;
- *memory = (size_t)string_base.memory;
- *duplications = string_base.duplications;
- *releases = string_base.releases;
+ if(inserts)
+ *inserts = string_base.inserts;
+
+ if(deletes)
+ *deletes = string_base.deletes;
+
+ if(searches)
+ *searches = string_base.searches;
+
+ if(entries)
+ *entries = (size_t)string_base.entries;
+
+ if(references)
+ *references = (size_t)string_base.active_references;
+
+ if(memory)
+ *memory = (size_t)string_base.memory;
+
+ if(duplications)
+ *duplications = string_base.duplications;
+
+ if(releases)
+ *releases = string_base.releases;
}
#define string_entry_acquire(se) __atomic_add_fetch(&((se)->refcount), 1, __ATOMIC_SEQ_CST);
@@ -186,7 +201,7 @@ static inline STRING *string_index_insert(const char *str, size_t length) {
*ptr = string;
string_base.inserts++;
string_base.entries++;
- string_base.memory += (long)mem_size;
+ string_base.memory += (long)(mem_size + JUDYHS_INDEX_SIZE_ESTIMATE(length));
}
else {
// the item is already in the index
@@ -240,7 +255,7 @@ static inline void string_index_delete(STRING *string) {
size_t mem_size = sizeof(STRING) + string->length;
string_base.deletes++;
string_base.entries--;
- string_base.memory -= (long)mem_size;
+ string_base.memory -= (long)(mem_size + JUDYHS_INDEX_SIZE_ESTIMATE(string->length));
freez(string);
}
diff --git a/libnetdata/threads/README.md b/libnetdata/threads/README.md
index 75ab11b1..71979fea 100644
--- a/libnetdata/threads/README.md
+++ b/libnetdata/threads/README.md
@@ -1,5 +1,12 @@
<!--
+title: Threads
custom_edit_url: https://github.com/netdata/netdata/edit/master/libnetdata/threads/README.md
+sidebar_label: "Threads"
+learn_status: "Published"
+learn_topic_type: "Tasks"
+learn_rel_path: "Developers/libnetdata libraries"
-->
+# Threads
+Netdata uses a custom threads library
diff --git a/libnetdata/threads/threads.c b/libnetdata/threads/threads.c
index 5c3d2675..16de45fd 100644
--- a/libnetdata/threads/threads.c
+++ b/libnetdata/threads/threads.c
@@ -2,8 +2,7 @@
#include "../libnetdata.h"
-static size_t default_stacksize = 0, wanted_stacksize = 0;
-static pthread_attr_t *attr = NULL;
+static pthread_attr_t *netdata_threads_attr = NULL;
// ----------------------------------------------------------------------------
// per thread data
@@ -69,46 +68,48 @@ size_t netdata_threads_init(void) {
// --------------------------------------------------------------------
// get the required stack size of the threads of netdata
- attr = callocz(1, sizeof(pthread_attr_t));
- i = pthread_attr_init(attr);
+ netdata_threads_attr = callocz(1, sizeof(pthread_attr_t));
+ i = pthread_attr_init(netdata_threads_attr);
if(i != 0)
fatal("pthread_attr_init() failed with code %d.", i);
- i = pthread_attr_getstacksize(attr, &default_stacksize);
+ size_t stacksize = 0;
+ i = pthread_attr_getstacksize(netdata_threads_attr, &stacksize);
if(i != 0)
fatal("pthread_attr_getstacksize() failed with code %d.", i);
else
- debug(D_OPTIONS, "initial pthread stack size is %zu bytes", default_stacksize);
+ debug(D_OPTIONS, "initial pthread stack size is %zu bytes", stacksize);
- return default_stacksize;
+ return stacksize;
}
// ----------------------------------------------------------------------------
// late initialization
void netdata_threads_init_after_fork(size_t stacksize) {
- wanted_stacksize = stacksize;
int i;
// ------------------------------------------------------------------------
- // set default pthread stack size
+ // set pthread stack size
- if(attr && default_stacksize < wanted_stacksize && wanted_stacksize > 0) {
- i = pthread_attr_setstacksize(attr, wanted_stacksize);
+ if(netdata_threads_attr && stacksize > (size_t)PTHREAD_STACK_MIN) {
+ i = pthread_attr_setstacksize(netdata_threads_attr, stacksize);
if(i != 0)
- fatal("pthread_attr_setstacksize() to %zu bytes, failed with code %d.", wanted_stacksize, i);
+ error("pthread_attr_setstacksize() to %zu bytes, failed with code %d.", stacksize, i);
else
- debug(D_SYSTEM, "Successfully set pthread stacksize to %zu bytes", wanted_stacksize);
+ info("Set threads stack size to %zu bytes", stacksize);
}
+ else
+ error("Invalid pthread stacksize %zu", stacksize);
}
-
// ----------------------------------------------------------------------------
// netdata_thread_create
-extern void rrdset_thread_rda_free(void);
-extern void sender_thread_buffer_free(void);
-extern void query_target_free(void);
+void rrdset_thread_rda_free(void);
+void sender_thread_buffer_free(void);
+void query_target_free(void);
+void service_exits(void);
static void thread_cleanup(void *ptr) {
if(netdata_thread != ptr) {
@@ -123,6 +124,8 @@ static void thread_cleanup(void *ptr) {
rrdset_thread_rda_free();
query_target_free();
thread_cache_destroy();
+ service_exits();
+ worker_unregister();
freez((void *)netdata_thread->tag);
netdata_thread->tag = NULL;
@@ -214,7 +217,7 @@ int netdata_thread_create(netdata_thread_t *thread, const char *tag, NETDATA_THR
info->start_routine = start_routine;
info->options = options;
- int ret = pthread_create(thread, attr, thread_start, info);
+ int ret = pthread_create(thread, netdata_threads_attr, thread_start, info);
if(ret != 0)
error("failed to create new thread for %s. pthread_create() failed with code %d", tag, ret);
diff --git a/libnetdata/url/README.md b/libnetdata/url/README.md
index bd289d95..cca6f873 100644
--- a/libnetdata/url/README.md
+++ b/libnetdata/url/README.md
@@ -1,5 +1,14 @@
<!--
+title: "URL"
custom_edit_url: https://github.com/netdata/netdata/edit/master/libnetdata/url/README.md
+sidebar_label: "URL"
+learn_status: "Published"
+learn_topic_type: "Tasks"
+learn_rel_path: "Developers/libnetdata libraries"
-->
+# URL
+
+The URL library contains common functions useful for URLs, like conversion from/to hex,
+URL encode/decode and query string parsing.
diff --git a/libnetdata/worker_utilization/worker_utilization.c b/libnetdata/worker_utilization/worker_utilization.c
index afaff209..8028e3a2 100644
--- a/libnetdata/worker_utilization/worker_utilization.c
+++ b/libnetdata/worker_utilization/worker_utilization.c
@@ -52,6 +52,7 @@ struct workers_workname { // this is what we add to Ju
static struct workers_globals {
SPINLOCK spinlock;
Pvoid_t worknames_JudyHS;
+ size_t memory;
} workers_globals = { // workers globals, the base of all worknames
.spinlock = NETDATA_SPINLOCK_INITIALIZER, // a lock for the worknames index
@@ -60,6 +61,14 @@ static struct workers_globals {
static __thread struct worker *worker = NULL; // the current thread worker
+size_t workers_allocated_memory(void) {
+ netdata_spinlock_lock(&workers_globals.spinlock);
+ size_t memory = workers_globals.memory;
+ netdata_spinlock_unlock(&workers_globals.spinlock);
+
+ return memory;
+}
+
void worker_register(const char *name) {
if(unlikely(worker)) return;
@@ -76,20 +85,22 @@ void worker_register(const char *name) {
size_t name_size = strlen(name) + 1;
netdata_spinlock_lock(&workers_globals.spinlock);
- Pvoid_t *PValue = JudyHSGet(workers_globals.worknames_JudyHS, (void *)name, name_size);
- if(!PValue)
- PValue = JudyHSIns(&workers_globals.worknames_JudyHS, (void *)name, name_size, PJE0);
+ workers_globals.memory += sizeof(struct worker) + strlen(worker->tag) + 1 + strlen(worker->workname) + 1;
+
+ Pvoid_t *PValue = JudyHSIns(&workers_globals.worknames_JudyHS, (void *)name, name_size, PJE0);
struct workers_workname *workname = *PValue;
if(!workname) {
workname = mallocz(sizeof(struct workers_workname));
- workname->spinlock = NETDATA_SPINLOCK_INITIALIZER;
+ netdata_spinlock_init(&workname->spinlock);
workname->base = NULL;
*PValue = workname;
+
+ workers_globals.memory += sizeof(struct workers_workname) + JUDYHS_INDEX_SIZE_ESTIMATE(name_size);
}
netdata_spinlock_lock(&workname->spinlock);
- DOUBLE_LINKED_LIST_APPEND_UNSAFE(workname->base, worker, prev, next);
+ DOUBLE_LINKED_LIST_APPEND_ITEM_UNSAFE(workname->base, worker, prev, next);
netdata_spinlock_unlock(&workname->spinlock);
netdata_spinlock_unlock(&workers_globals.spinlock);
@@ -130,14 +141,16 @@ void worker_unregister(void) {
if(PValue) {
struct workers_workname *workname = *PValue;
netdata_spinlock_lock(&workname->spinlock);
- DOUBLE_LINKED_LIST_REMOVE_UNSAFE(workname->base, worker, prev, next);
+ DOUBLE_LINKED_LIST_REMOVE_ITEM_UNSAFE(workname->base, worker, prev, next);
netdata_spinlock_unlock(&workname->spinlock);
if(!workname->base) {
JudyHSDel(&workers_globals.worknames_JudyHS, (void *) worker->workname, workname_size, PJE0);
freez(workname);
+ workers_globals.memory -= sizeof(struct workers_workname) + JUDYHS_INDEX_SIZE_ESTIMATE(workname_size);
}
}
+ workers_globals.memory -= sizeof(struct worker) + strlen(worker->tag) + 1 + strlen(worker->workname) + 1;
netdata_spinlock_unlock(&workers_globals.spinlock);
for(int i = 0; i < WORKER_UTILIZATION_MAX_JOB_TYPES ;i++) {
diff --git a/libnetdata/worker_utilization/worker_utilization.h b/libnetdata/worker_utilization/worker_utilization.h
index f1412e6b..6745a010 100644
--- a/libnetdata/worker_utilization/worker_utilization.h
+++ b/libnetdata/worker_utilization/worker_utilization.h
@@ -15,6 +15,7 @@ typedef enum {
WORKER_METRIC_INCREMENTAL_TOTAL = 4,
} WORKER_METRIC_TYPE;
+size_t workers_allocated_memory(void);
void worker_register(const char *name);
void worker_register_job_name(size_t job_id, const char *name);
void worker_register_job_custom_metric(size_t job_id, const char *name, const char *units, WORKER_METRIC_TYPE type);
diff --git a/ml/ADCharts.cc b/ml/ADCharts.cc
index 00c593c0..cbb13f5d 100644
--- a/ml/ADCharts.cc
+++ b/ml/ADCharts.cc
@@ -3,55 +3,185 @@
#include "ADCharts.h"
#include "Config.h"
-void ml::updateDimensionsChart(RRDHOST *RH,
- collected_number NumTrainedDimensions,
- collected_number NumNormalDimensions,
- collected_number NumAnomalousDimensions) {
- static thread_local RRDSET *RS = nullptr;
- static thread_local RRDDIM *NumTotalDimensionsRD = nullptr;
- static thread_local RRDDIM *NumTrainedDimensionsRD = nullptr;
- static thread_local RRDDIM *NumNormalDimensionsRD = nullptr;
- static thread_local RRDDIM *NumAnomalousDimensionsRD = nullptr;
-
- if (!RS) {
- std::stringstream IdSS, NameSS;
+void ml::updateDimensionsChart(RRDHOST *RH, const MachineLearningStats &MLS) {
+ /*
+ * Machine learning status
+ */
+ {
+ static thread_local RRDSET *MachineLearningStatusRS = nullptr;
+
+ static thread_local RRDDIM *Enabled = nullptr;
+ static thread_local RRDDIM *DisabledUE = nullptr;
+ static thread_local RRDDIM *DisabledSP = nullptr;
+
+ if (!MachineLearningStatusRS) {
+ std::stringstream IdSS, NameSS;
+
+ IdSS << "machine_learning_status_on_" << localhost->machine_guid;
+ NameSS << "machine_learning_status_on_" << rrdhost_hostname(localhost);
+
+ MachineLearningStatusRS = rrdset_create(
+ RH,
+ "netdata", // type
+ IdSS.str().c_str(), // id
+ NameSS.str().c_str(), // name
+ NETDATA_ML_CHART_FAMILY, // family
+ "netdata.machine_learning_status", // ctx
+ "Machine learning status", // title
+ "dimensions", // units
+ NETDATA_ML_PLUGIN, // plugin
+ NETDATA_ML_MODULE_TRAINING, // module
+ NETDATA_ML_CHART_PRIO_MACHINE_LEARNING_STATUS, // priority
+ RH->rrd_update_every, // update_every
+ RRDSET_TYPE_LINE // chart_type
+ );
+ rrdset_flag_set(MachineLearningStatusRS , RRDSET_FLAG_ANOMALY_DETECTION);
+
+ Enabled = rrddim_add(MachineLearningStatusRS, "enabled", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
+ DisabledUE = rrddim_add(MachineLearningStatusRS, "disabled-ue", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
+ DisabledSP = rrddim_add(MachineLearningStatusRS, "disabled-sp", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
+ }
+
+ rrddim_set_by_pointer(MachineLearningStatusRS, Enabled, MLS.NumMachineLearningStatusEnabled);
+ rrddim_set_by_pointer(MachineLearningStatusRS, DisabledUE, MLS.NumMachineLearningStatusDisabledUE);
+ rrddim_set_by_pointer(MachineLearningStatusRS, DisabledSP, MLS.NumMachineLearningStatusDisabledSP);
+
+ rrdset_done(MachineLearningStatusRS);
+ }
- IdSS << "dimensions_on_" << localhost->machine_guid;
- NameSS << "dimensions_on_" << localhost->hostname;
+ /*
+ * Metric type
+ */
+ {
+ static thread_local RRDSET *MetricTypesRS = nullptr;
+
+ static thread_local RRDDIM *Constant = nullptr;
+ static thread_local RRDDIM *Variable = nullptr;
+
+ if (!MetricTypesRS) {
+ std::stringstream IdSS, NameSS;
+
+ IdSS << "metric_types_on_" << localhost->machine_guid;
+ NameSS << "metric_types_on_" << rrdhost_hostname(localhost);
+
+ MetricTypesRS = rrdset_create(
+ RH,
+ "netdata", // type
+ IdSS.str().c_str(), // id
+ NameSS.str().c_str(), // name
+ NETDATA_ML_CHART_FAMILY, // family
+ "netdata.metric_types", // ctx
+ "Dimensions by metric type", // title
+ "dimensions", // units
+ NETDATA_ML_PLUGIN, // plugin
+ NETDATA_ML_MODULE_TRAINING, // module
+ NETDATA_ML_CHART_PRIO_METRIC_TYPES, // priority
+ RH->rrd_update_every, // update_every
+ RRDSET_TYPE_LINE // chart_type
+ );
+ rrdset_flag_set(MetricTypesRS, RRDSET_FLAG_ANOMALY_DETECTION);
+
+ Constant = rrddim_add(MetricTypesRS, "constant", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
+ Variable = rrddim_add(MetricTypesRS, "variable", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
+ }
+
+ rrddim_set_by_pointer(MetricTypesRS, Constant, MLS.NumMetricTypeConstant);
+ rrddim_set_by_pointer(MetricTypesRS, Variable, MLS.NumMetricTypeVariable);
+
+ rrdset_done(MetricTypesRS);
+ }
- RS = rrdset_create(
- RH,
- "anomaly_detection", // type
- IdSS.str().c_str(), // id
- NameSS.str().c_str(), // name
- "dimensions", // family
- "anomaly_detection.dimensions", // ctx
- "Anomaly detection dimensions", // title
- "dimensions", // units
- "netdata", // plugin
- "ml", // module
- 39183, // priority
- RH->rrd_update_every, // update_every
- RRDSET_TYPE_LINE // chart_type
- );
- rrdset_flag_set(RS, RRDSET_FLAG_ANOMALY_DETECTION);
-
- NumTotalDimensionsRD = rrddim_add(RS, "total", NULL,
- 1, 1, RRD_ALGORITHM_ABSOLUTE);
- NumTrainedDimensionsRD = rrddim_add(RS, "trained", NULL,
- 1, 1, RRD_ALGORITHM_ABSOLUTE);
- NumNormalDimensionsRD = rrddim_add(RS, "normal", NULL,
- 1, 1, RRD_ALGORITHM_ABSOLUTE);
- NumAnomalousDimensionsRD = rrddim_add(RS, "anomalous", NULL,
- 1, 1, RRD_ALGORITHM_ABSOLUTE);
+ /*
+ * Training status
+ */
+ {
+ static thread_local RRDSET *TrainingStatusRS = nullptr;
+
+ static thread_local RRDDIM *Untrained = nullptr;
+ static thread_local RRDDIM *PendingWithoutModel = nullptr;
+ static thread_local RRDDIM *Trained = nullptr;
+ static thread_local RRDDIM *PendingWithModel = nullptr;
+
+ if (!TrainingStatusRS) {
+ std::stringstream IdSS, NameSS;
+
+ IdSS << "training_status_on_" << localhost->machine_guid;
+ NameSS << "training_status_on_" << rrdhost_hostname(localhost);
+
+ TrainingStatusRS = rrdset_create(
+ RH,
+ "netdata", // type
+ IdSS.str().c_str(), // id
+ NameSS.str().c_str(), // name
+ NETDATA_ML_CHART_FAMILY, // family
+ "netdata.training_status", // ctx
+ "Training status of dimensions", // title
+ "dimensions", // units
+ NETDATA_ML_PLUGIN, // plugin
+ NETDATA_ML_MODULE_TRAINING, // module
+ NETDATA_ML_CHART_PRIO_TRAINING_STATUS, // priority
+ RH->rrd_update_every, // update_every
+ RRDSET_TYPE_LINE // chart_type
+ );
+
+ rrdset_flag_set(TrainingStatusRS, RRDSET_FLAG_ANOMALY_DETECTION);
+
+ Untrained = rrddim_add(TrainingStatusRS, "untrained", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
+ PendingWithoutModel = rrddim_add(TrainingStatusRS, "pending-without-model", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
+ Trained = rrddim_add(TrainingStatusRS, "trained", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
+ PendingWithModel = rrddim_add(TrainingStatusRS, "pending-with-model", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
+ }
+
+ rrddim_set_by_pointer(TrainingStatusRS, Untrained, MLS.NumTrainingStatusUntrained);
+ rrddim_set_by_pointer(TrainingStatusRS, PendingWithoutModel, MLS.NumTrainingStatusPendingWithoutModel);
+ rrddim_set_by_pointer(TrainingStatusRS, Trained, MLS.NumTrainingStatusTrained);
+ rrddim_set_by_pointer(TrainingStatusRS, PendingWithModel, MLS.NumTrainingStatusPendingWithModel);
+
+ rrdset_done(TrainingStatusRS);
}
- rrddim_set_by_pointer(RS, NumTotalDimensionsRD, NumNormalDimensions + NumAnomalousDimensions);
- rrddim_set_by_pointer(RS, NumTrainedDimensionsRD, NumTrainedDimensions);
- rrddim_set_by_pointer(RS, NumNormalDimensionsRD, NumNormalDimensions);
- rrddim_set_by_pointer(RS, NumAnomalousDimensionsRD, NumAnomalousDimensions);
+ /*
+ * Prediction status
+ */
+ {
+ static thread_local RRDSET *PredictionRS = nullptr;
+
+ static thread_local RRDDIM *Anomalous = nullptr;
+ static thread_local RRDDIM *Normal = nullptr;
+
+ if (!PredictionRS) {
+ std::stringstream IdSS, NameSS;
+
+ IdSS << "dimensions_on_" << localhost->machine_guid;
+ NameSS << "dimensions_on_" << rrdhost_hostname(localhost);
+
+ PredictionRS = rrdset_create(
+ RH,
+ "anomaly_detection", // type
+ IdSS.str().c_str(), // id
+ NameSS.str().c_str(), // name
+ "dimensions", // family
+ "anomaly_detection.dimensions", // ctx
+ "Anomaly detection dimensions", // title
+ "dimensions", // units
+ NETDATA_ML_PLUGIN, // plugin
+ NETDATA_ML_MODULE_TRAINING, // module
+ ML_CHART_PRIO_DIMENSIONS, // priority
+ RH->rrd_update_every, // update_every
+ RRDSET_TYPE_LINE // chart_type
+ );
+ rrdset_flag_set(PredictionRS, RRDSET_FLAG_ANOMALY_DETECTION);
+
+ Anomalous = rrddim_add(PredictionRS, "anomalous", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
+ Normal = rrddim_add(PredictionRS, "normal", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
+ }
+
+ rrddim_set_by_pointer(PredictionRS, Anomalous, MLS.NumAnomalousDimensions);
+ rrddim_set_by_pointer(PredictionRS, Normal, MLS.NumNormalDimensions);
+
+ rrdset_done(PredictionRS);
+ }
- rrdset_done(RS);
}
void ml::updateHostAndDetectionRateCharts(RRDHOST *RH, collected_number AnomalyRate) {
@@ -62,20 +192,20 @@ void ml::updateHostAndDetectionRateCharts(RRDHOST *RH, collected_number AnomalyR
std::stringstream IdSS, NameSS;
IdSS << "anomaly_rate_on_" << localhost->machine_guid;
- NameSS << "anomaly_rate_on_" << localhost->hostname;
+ NameSS << "anomaly_rate_on_" << rrdhost_hostname(localhost);
HostRateRS = rrdset_create(
- RH,
- "anomaly_detection", // type
+ RH,
+ "anomaly_detection", // type
IdSS.str().c_str(), // id
NameSS.str().c_str(), // name
"anomaly_rate", // family
"anomaly_detection.anomaly_rate", // ctx
"Percentage of anomalous dimensions", // title
"percentage", // units
- "netdata", // plugin
- "ml", // module
- 39184, // priority
+ NETDATA_ML_PLUGIN, // plugin
+ NETDATA_ML_MODULE_DETECTION, // module
+ ML_CHART_PRIO_ANOMALY_RATE, // priority
RH->rrd_update_every, // update_every
RRDSET_TYPE_LINE // chart_type
);
@@ -96,20 +226,20 @@ void ml::updateHostAndDetectionRateCharts(RRDHOST *RH, collected_number AnomalyR
std::stringstream IdSS, NameSS;
IdSS << "anomaly_detection_on_" << localhost->machine_guid;
- NameSS << "anomaly_detection_on_" << localhost->hostname;
+ NameSS << "anomaly_detection_on_" << rrdhost_hostname(localhost);
AnomalyDetectionRS = rrdset_create(
- RH,
- "anomaly_detection", // type
+ RH,
+ "anomaly_detection", // type
IdSS.str().c_str(), // id
NameSS.str().c_str(), // name
"anomaly_detection", // family
"anomaly_detection.detector_events", // ctx
"Anomaly detection events", // title
"percentage", // units
- "netdata", // plugin
- "ml", // module
- 39185, // priority
+ NETDATA_ML_PLUGIN, // plugin
+ NETDATA_ML_MODULE_DETECTION, // module
+ ML_CHART_PRIO_DETECTOR_EVENTS, // priority
RH->rrd_update_every, // update_every
RRDSET_TYPE_LINE // chart_type
);
@@ -141,93 +271,248 @@ void ml::updateHostAndDetectionRateCharts(RRDHOST *RH, collected_number AnomalyR
NULL /* group options */,
0, /* timeout */
0, /* tier */
- QUERY_SOURCE_ML
+ QUERY_SOURCE_ML,
+ STORAGE_PRIORITY_BEST_EFFORT
);
- if(R) {
- assert(R->d == 1 && R->n == 1 && R->rows == 1);
- static thread_local bool PrevAboveThreshold = false;
- bool AboveThreshold = R->v[0] >= Cfg.HostAnomalyRateThreshold;
- bool NewAnomalyEvent = AboveThreshold && !PrevAboveThreshold;
- PrevAboveThreshold = AboveThreshold;
+ if(R) {
+ if(R->d == 1 && R->n == 1 && R->rows == 1) {
+ static thread_local bool PrevAboveThreshold = false;
+ bool AboveThreshold = R->v[0] >= Cfg.HostAnomalyRateThreshold;
+ bool NewAnomalyEvent = AboveThreshold && !PrevAboveThreshold;
+ PrevAboveThreshold = AboveThreshold;
- rrddim_set_by_pointer(AnomalyDetectionRS, AboveThresholdRD, AboveThreshold);
- rrddim_set_by_pointer(AnomalyDetectionRS, NewAnomalyEventRD, NewAnomalyEvent);
- rrdset_done(AnomalyDetectionRS);
+ rrddim_set_by_pointer(AnomalyDetectionRS, AboveThresholdRD, AboveThreshold);
+ rrddim_set_by_pointer(AnomalyDetectionRS, NewAnomalyEventRD, NewAnomalyEvent);
+ rrdset_done(AnomalyDetectionRS);
+ }
rrdr_free(OWA, R);
}
+
onewayalloc_destroy(OWA);
}
-void ml::updateDetectionChart(RRDHOST *RH) {
- static thread_local RRDSET *RS = nullptr;
- static thread_local RRDDIM *UserRD, *SystemRD = nullptr;
-
- if (!RS) {
- std::stringstream IdSS, NameSS;
-
- IdSS << "prediction_stats_" << RH->machine_guid;
- NameSS << "prediction_stats_for_" << RH->hostname;
-
- RS = rrdset_create_localhost(
- "netdata", // type
- IdSS.str().c_str(), // id
- NameSS.str().c_str(), // name
- "ml", // family
- "netdata.prediction_stats", // ctx
- "Prediction thread CPU usage", // title
- "milliseconds/s", // units
- "netdata", // plugin
- "ml", // module
- 136000, // priority
- RH->rrd_update_every, // update_every
- RRDSET_TYPE_STACKED // chart_type
- );
-
- UserRD = rrddim_add(RS, "user", NULL, 1, 1000, RRD_ALGORITHM_INCREMENTAL);
- SystemRD = rrddim_add(RS, "system", NULL, 1, 1000, RRD_ALGORITHM_INCREMENTAL);
+void ml::updateResourceUsageCharts(RRDHOST *RH, const struct rusage &PredictionRU, const struct rusage &TrainingRU) {
+ /*
+ * prediction rusage
+ */
+ {
+ static thread_local RRDSET *RS = nullptr;
+
+ static thread_local RRDDIM *User = nullptr;
+ static thread_local RRDDIM *System = nullptr;
+
+ if (!RS) {
+ std::stringstream IdSS, NameSS;
+
+ IdSS << "prediction_usage_for_" << RH->machine_guid;
+ NameSS << "prediction_usage_for_" << rrdhost_hostname(RH);
+
+ RS = rrdset_create_localhost(
+ "netdata", // type
+ IdSS.str().c_str(), // id
+ NameSS.str().c_str(), // name
+ NETDATA_ML_CHART_FAMILY, // family
+ "netdata.prediction_usage", // ctx
+ "Prediction resource usage", // title
+ "milliseconds/s", // units
+ NETDATA_ML_PLUGIN, // plugin
+ NETDATA_ML_MODULE_PREDICTION, // module
+ NETDATA_ML_CHART_PRIO_PREDICTION_USAGE, // priority
+ RH->rrd_update_every, // update_every
+ RRDSET_TYPE_STACKED // chart_type
+ );
+ rrdset_flag_set(RS, RRDSET_FLAG_ANOMALY_DETECTION);
+
+ User = rrddim_add(RS, "user", NULL, 1, 1000, RRD_ALGORITHM_INCREMENTAL);
+ System = rrddim_add(RS, "system", NULL, 1, 1000, RRD_ALGORITHM_INCREMENTAL);
+ }
+
+ rrddim_set_by_pointer(RS, User, PredictionRU.ru_utime.tv_sec * 1000000ULL + PredictionRU.ru_utime.tv_usec);
+ rrddim_set_by_pointer(RS, System, PredictionRU.ru_stime.tv_sec * 1000000ULL + PredictionRU.ru_stime.tv_usec);
+
+ rrdset_done(RS);
}
- struct rusage TRU;
- getrusage(RUSAGE_THREAD, &TRU);
-
- rrddim_set_by_pointer(RS, UserRD, TRU.ru_utime.tv_sec * 1000000ULL + TRU.ru_utime.tv_usec);
- rrddim_set_by_pointer(RS, SystemRD, TRU.ru_stime.tv_sec * 1000000ULL + TRU.ru_stime.tv_usec);
- rrdset_done(RS);
+ /*
+ * training rusage
+ */
+ {
+ static thread_local RRDSET *RS = nullptr;
+
+ static thread_local RRDDIM *User = nullptr;
+ static thread_local RRDDIM *System = nullptr;
+
+ if (!RS) {
+ std::stringstream IdSS, NameSS;
+
+ IdSS << "training_usage_for_" << RH->machine_guid;
+ NameSS << "training_usage_for_" << rrdhost_hostname(RH);
+
+ RS = rrdset_create_localhost(
+ "netdata", // type
+ IdSS.str().c_str(), // id
+ NameSS.str().c_str(), // name
+ NETDATA_ML_CHART_FAMILY, // family
+ "netdata.training_usage", // ctx
+ "Training resource usage", // title
+ "milliseconds/s", // units
+ NETDATA_ML_PLUGIN, // plugin
+ NETDATA_ML_MODULE_TRAINING, // module
+ NETDATA_ML_CHART_PRIO_TRAINING_USAGE, // priority
+ RH->rrd_update_every, // update_every
+ RRDSET_TYPE_STACKED // chart_type
+ );
+ rrdset_flag_set(RS, RRDSET_FLAG_ANOMALY_DETECTION);
+
+ User = rrddim_add(RS, "user", NULL, 1, 1000, RRD_ALGORITHM_INCREMENTAL);
+ System = rrddim_add(RS, "system", NULL, 1, 1000, RRD_ALGORITHM_INCREMENTAL);
+ }
+
+ rrddim_set_by_pointer(RS, User, TrainingRU.ru_utime.tv_sec * 1000000ULL + TrainingRU.ru_utime.tv_usec);
+ rrddim_set_by_pointer(RS, System, TrainingRU.ru_stime.tv_sec * 1000000ULL + TrainingRU.ru_stime.tv_usec);
+
+ rrdset_done(RS);
+ }
}
-void ml::updateTrainingChart(RRDHOST *RH, struct rusage *TRU) {
- static thread_local RRDSET *RS = nullptr;
- static thread_local RRDDIM *UserRD = nullptr;
- static thread_local RRDDIM *SystemRD = nullptr;
-
- if (!RS) {
- std::stringstream IdSS, NameSS;
-
- IdSS << "training_stats_" << RH->machine_guid;
- NameSS << "training_stats_for_" << RH->hostname;
-
- RS = rrdset_create_localhost(
- "netdata", // type
- IdSS.str().c_str(), // id
- NameSS.str().c_str(), // name
- "ml", // family
- "netdata.training_stats", // ctx
- "Training thread CPU usage", // title
- "milliseconds/s", // units
- "netdata", // plugin
- "ml", // module
- 136001, // priority
- RH->rrd_update_every, // update_every
- RRDSET_TYPE_STACKED // chart_type
- );
+void ml::updateTrainingStatisticsChart(RRDHOST *RH, const TrainingStats &TS) {
+ /*
+ * queue stats
+ */
+ {
+ static thread_local RRDSET *RS = nullptr;
+
+ static thread_local RRDDIM *QueueSize = nullptr;
+ static thread_local RRDDIM *PoppedItems = nullptr;
+
+ if (!RS) {
+ std::stringstream IdSS, NameSS;
+
+ IdSS << "queue_stats_on_" << localhost->machine_guid;
+ NameSS << "queue_stats_on_" << rrdhost_hostname(localhost);
+
+ RS = rrdset_create(
+ RH,
+ "netdata", // type
+ IdSS.str().c_str(), // id
+ NameSS.str().c_str(), // name
+ NETDATA_ML_CHART_FAMILY, // family
+ "netdata.queue_stats", // ctx
+ "Training queue stats", // title
+ "items", // units
+ NETDATA_ML_PLUGIN, // plugin
+ NETDATA_ML_MODULE_TRAINING, // module
+ NETDATA_ML_CHART_PRIO_QUEUE_STATS, // priority
+ RH->rrd_update_every, // update_every
+ RRDSET_TYPE_LINE// chart_type
+ );
+ rrdset_flag_set(RS, RRDSET_FLAG_ANOMALY_DETECTION);
+
+ QueueSize = rrddim_add(RS, "queue_size", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
+ PoppedItems = rrddim_add(RS, "popped_items", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
+ }
+
+ rrddim_set_by_pointer(RS, QueueSize, TS.QueueSize);
+ rrddim_set_by_pointer(RS, PoppedItems, TS.NumPoppedItems);
+
+ rrdset_done(RS);
+ }
- UserRD = rrddim_add(RS, "user", NULL, 1, 1000, RRD_ALGORITHM_INCREMENTAL);
- SystemRD = rrddim_add(RS, "system", NULL, 1, 1000, RRD_ALGORITHM_INCREMENTAL);
+ /*
+ * training stats
+ */
+ {
+ static thread_local RRDSET *RS = nullptr;
+
+ static thread_local RRDDIM *Allotted = nullptr;
+ static thread_local RRDDIM *Consumed = nullptr;
+ static thread_local RRDDIM *Remaining = nullptr;
+
+ if (!RS) {
+ std::stringstream IdSS, NameSS;
+
+ IdSS << "training_time_stats_on_" << localhost->machine_guid;
+ NameSS << "training_time_stats_on_" << rrdhost_hostname(localhost);
+
+ RS = rrdset_create(
+ RH,
+ "netdata", // type
+ IdSS.str().c_str(), // id
+ NameSS.str().c_str(), // name
+ NETDATA_ML_CHART_FAMILY, // family
+ "netdata.training_time_stats", // ctx
+ "Training time stats", // title
+ "milliseconds", // units
+ NETDATA_ML_PLUGIN, // plugin
+ NETDATA_ML_MODULE_TRAINING, // module
+ NETDATA_ML_CHART_PRIO_TRAINING_TIME_STATS, // priority
+ RH->rrd_update_every, // update_every
+ RRDSET_TYPE_LINE// chart_type
+ );
+ rrdset_flag_set(RS, RRDSET_FLAG_ANOMALY_DETECTION);
+
+ Allotted = rrddim_add(RS, "allotted", NULL, 1, 1000, RRD_ALGORITHM_ABSOLUTE);
+ Consumed = rrddim_add(RS, "consumed", NULL, 1, 1000, RRD_ALGORITHM_ABSOLUTE);
+ Remaining = rrddim_add(RS, "remaining", NULL, 1, 1000, RRD_ALGORITHM_ABSOLUTE);
+ }
+
+ rrddim_set_by_pointer(RS, Allotted, TS.AllottedUT);
+ rrddim_set_by_pointer(RS, Consumed, TS.ConsumedUT);
+ rrddim_set_by_pointer(RS, Remaining, TS.RemainingUT);
+
+ rrdset_done(RS);
}
- rrddim_set_by_pointer(RS, UserRD, TRU->ru_utime.tv_sec * 1000000ULL + TRU->ru_utime.tv_usec);
- rrddim_set_by_pointer(RS, SystemRD, TRU->ru_stime.tv_sec * 1000000ULL + TRU->ru_stime.tv_usec);
- rrdset_done(RS);
+ /*
+ * training result stats
+ */
+ {
+ static thread_local RRDSET *RS = nullptr;
+
+ static thread_local RRDDIM *Ok = nullptr;
+ static thread_local RRDDIM *InvalidQueryTimeRange = nullptr;
+ static thread_local RRDDIM *NotEnoughCollectedValues = nullptr;
+ static thread_local RRDDIM *NullAcquiredDimension = nullptr;
+ static thread_local RRDDIM *ChartUnderReplication = nullptr;
+
+ if (!RS) {
+ std::stringstream IdSS, NameSS;
+
+ IdSS << "training_results_on_" << localhost->machine_guid;
+ NameSS << "training_results_on_" << rrdhost_hostname(localhost);
+
+ RS = rrdset_create(
+ RH,
+ "netdata", // type
+ IdSS.str().c_str(), // id
+ NameSS.str().c_str(), // name
+ NETDATA_ML_CHART_FAMILY, // family
+ "netdata.training_results", // ctx
+ "Training results", // title
+ "events", // units
+ NETDATA_ML_PLUGIN, // plugin
+ NETDATA_ML_MODULE_TRAINING, // module
+ NETDATA_ML_CHART_PRIO_TRAINING_RESULTS, // priority
+ RH->rrd_update_every, // update_every
+ RRDSET_TYPE_LINE// chart_type
+ );
+ rrdset_flag_set(RS, RRDSET_FLAG_ANOMALY_DETECTION);
+
+ Ok = rrddim_add(RS, "ok", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
+ InvalidQueryTimeRange = rrddim_add(RS, "invalid-queries", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
+ NotEnoughCollectedValues = rrddim_add(RS, "not-enough-values", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
+ NullAcquiredDimension = rrddim_add(RS, "null-acquired-dimensions", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
+ ChartUnderReplication = rrddim_add(RS, "chart-under-replication", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
+ }
+
+ rrddim_set_by_pointer(RS, Ok, TS.TrainingResultOk);
+ rrddim_set_by_pointer(RS, InvalidQueryTimeRange, TS.TrainingResultInvalidQueryTimeRange);
+ rrddim_set_by_pointer(RS, NotEnoughCollectedValues, TS.TrainingResultNotEnoughCollectedValues);
+ rrddim_set_by_pointer(RS, NullAcquiredDimension, TS.TrainingResultNullAcquiredDimension);
+ rrddim_set_by_pointer(RS, ChartUnderReplication, TS.TrainingResultChartUnderReplication);
+
+ rrdset_done(RS);
+ }
}
diff --git a/ml/ADCharts.h b/ml/ADCharts.h
index 0be324f7..ee09669e 100644
--- a/ml/ADCharts.h
+++ b/ml/ADCharts.h
@@ -3,20 +3,18 @@
#ifndef ML_ADCHARTS_H
#define ML_ADCHARTS_H
+#include "Stats.h"
#include "ml-private.h"
namespace ml {
-void updateDimensionsChart(RRDHOST *RH,
- collected_number NumTrainedDimensions,
- collected_number NumNormalDimensions,
- collected_number NumAnomalousDimensions);
+void updateDimensionsChart(RRDHOST *RH, const MachineLearningStats &MLS);
void updateHostAndDetectionRateCharts(RRDHOST *RH, collected_number AnomalyRate);
-void updateDetectionChart(RRDHOST *RH);
+void updateResourceUsageCharts(RRDHOST *RH, const struct rusage &PredictionRU, const struct rusage &TrainingRU);
-void updateTrainingChart(RRDHOST *RH, struct rusage *TRU);
+void updateTrainingStatisticsChart(RRDHOST *RH, const TrainingStats &TS);
} // namespace ml
diff --git a/ml/Chart.cc b/ml/Chart.cc
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/ml/Chart.cc
diff --git a/ml/Chart.h b/ml/Chart.h
new file mode 100644
index 00000000..dbd6a910
--- /dev/null
+++ b/ml/Chart.h
@@ -0,0 +1,128 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#ifndef ML_CHART_H
+#define ML_CHART_H
+
+#include "Config.h"
+#include "Dimension.h"
+
+#include "ml-private.h"
+#include "json/single_include/nlohmann/json.hpp"
+
+namespace ml
+{
+
+class Chart {
+public:
+ Chart(RRDSET *RS) :
+ RS(RS),
+ MLS()
+ { }
+
+ RRDSET *getRS() const {
+ return RS;
+ }
+
+ bool isAvailableForML() {
+ return rrdset_is_available_for_exporting_and_alarms(RS);
+ }
+
+ void addDimension(Dimension *D) {
+ std::lock_guard<Mutex> L(M);
+ Dimensions[D->getRD()] = D;
+ }
+
+ void removeDimension(Dimension *D) {
+ std::lock_guard<Mutex> L(M);
+ Dimensions.erase(D->getRD());
+ }
+
+ void getModelsAsJson(nlohmann::json &Json) {
+ std::lock_guard<Mutex> L(M);
+
+ for (auto &DP : Dimensions) {
+ Dimension *D = DP.second;
+ nlohmann::json JsonArray = nlohmann::json::array();
+ for (const KMeans &KM : D->getModels()) {
+ nlohmann::json J;
+ KM.toJson(J);
+ JsonArray.push_back(J);
+ }
+
+ Json[getMLDimensionID(D->getRD())] = JsonArray;
+ }
+ }
+
+ void updateBegin() {
+ M.lock();
+ MLS = {};
+ }
+
+ void updateDimension(Dimension *D, bool IsAnomalous) {
+ switch (D->getMLS()) {
+ case MachineLearningStatus::DisabledDueToUniqueUpdateEvery:
+ MLS.NumMachineLearningStatusDisabledUE++;
+ return;
+ case MachineLearningStatus::DisabledDueToExcludedChart:
+ MLS.NumMachineLearningStatusDisabledSP++;
+ return;
+ case MachineLearningStatus::Enabled: {
+ MLS.NumMachineLearningStatusEnabled++;
+
+ switch (D->getMT()) {
+ case MetricType::Constant:
+ MLS.NumMetricTypeConstant++;
+ MLS.NumTrainingStatusTrained++;
+ MLS.NumNormalDimensions++;
+ return;
+ case MetricType::Variable:
+ MLS.NumMetricTypeVariable++;
+ break;
+ }
+
+ switch (D->getTS()) {
+ case TrainingStatus::Untrained:
+ MLS.NumTrainingStatusUntrained++;
+ return;
+ case TrainingStatus::PendingWithoutModel:
+ MLS.NumTrainingStatusPendingWithoutModel++;
+ return;
+ case TrainingStatus::Trained:
+ MLS.NumTrainingStatusTrained++;
+
+ MLS.NumAnomalousDimensions += IsAnomalous;
+ MLS.NumNormalDimensions += !IsAnomalous;
+ return;
+ case TrainingStatus::PendingWithModel:
+ MLS.NumTrainingStatusPendingWithModel++;
+
+ MLS.NumAnomalousDimensions += IsAnomalous;
+ MLS.NumNormalDimensions += !IsAnomalous;
+ return;
+ }
+
+ return;
+ }
+ }
+ }
+
+ void updateEnd() {
+ M.unlock();
+ }
+
+ MachineLearningStats getMLS() {
+ std::lock_guard<Mutex> L(M);
+ return MLS;
+ }
+
+private:
+ RRDSET *RS;
+ MachineLearningStats MLS;
+
+ Mutex M;
+ std::unordered_map<RRDDIM *, Dimension *> Dimensions;
+};
+
+} // namespace ml
+
+#endif /* ML_CHART_H */
diff --git a/ml/Config.cc b/ml/Config.cc
index eedd8c29..ba3a6144 100644
--- a/ml/Config.cc
+++ b/ml/Config.cc
@@ -31,7 +31,7 @@ void Config::readMLConfig(void) {
unsigned MaxTrainSamples = config_get_number(ConfigSectionML, "maximum num samples to train", 4 * 3600);
unsigned MinTrainSamples = config_get_number(ConfigSectionML, "minimum num samples to train", 1 * 900);
unsigned TrainEvery = config_get_number(ConfigSectionML, "train every", 1 * 3600);
- unsigned NumModelsToUse = config_get_number(ConfigSectionML, "number of models per dimension", 1 * 24);
+ unsigned NumModelsToUse = config_get_number(ConfigSectionML, "number of models per dimension", 1);
unsigned DiffN = config_get_number(ConfigSectionML, "num samples to diff", 1);
unsigned SmoothN = config_get_number(ConfigSectionML, "num samples to smooth", 3);
@@ -53,7 +53,7 @@ void Config::readMLConfig(void) {
MaxTrainSamples = clamp<unsigned>(MaxTrainSamples, 1 * 3600, 24 * 3600);
MinTrainSamples = clamp<unsigned>(MinTrainSamples, 1 * 900, 6 * 3600);
TrainEvery = clamp<unsigned>(TrainEvery, 1 * 3600, 6 * 3600);
- NumModelsToUse = clamp<unsigned>(TrainEvery, 1, 7 * 24);
+ NumModelsToUse = clamp<unsigned>(NumModelsToUse, 1, 7 * 24);
DiffN = clamp(DiffN, 0u, 1u);
SmoothN = clamp(SmoothN, 0u, 5u);
@@ -108,7 +108,7 @@ void Config::readMLConfig(void) {
// Always exclude anomaly_detection charts from training.
Cfg.ChartsToSkip = "anomaly_detection.* ";
Cfg.ChartsToSkip += config_get(ConfigSectionML, "charts to skip from training", "netdata.*");
- Cfg.SP_ChartsToSkip = simple_pattern_create(ChartsToSkip.c_str(), NULL, SIMPLE_PATTERN_EXACT);
+ Cfg.SP_ChartsToSkip = simple_pattern_create(Cfg.ChartsToSkip.c_str(), NULL, SIMPLE_PATTERN_EXACT);
Cfg.StreamADCharts = config_get_boolean(ConfigSectionML, "stream anomaly detection charts", true);
}
diff --git a/ml/Config.h b/ml/Config.h
index d876d4aa..f10e1149 100644
--- a/ml/Config.h
+++ b/ml/Config.h
@@ -14,6 +14,7 @@ public:
unsigned MaxTrainSamples;
unsigned MinTrainSamples;
unsigned TrainEvery;
+
unsigned NumModelsToUse;
unsigned DBEngineAnomalyRateEvery;
diff --git a/ml/Dimension.cc b/ml/Dimension.cc
index bf34abb7..db925689 100644
--- a/ml/Dimension.cc
+++ b/ml/Dimension.cc
@@ -3,171 +3,344 @@
#include "Config.h"
#include "Dimension.h"
#include "Query.h"
+#include "Host.h"
using namespace ml;
-bool Dimension::isActive() const {
- bool SetObsolete = rrdset_flag_check(RD->rrdset, RRDSET_FLAG_OBSOLETE);
- bool DimObsolete = rrddim_flag_check(RD, RRDDIM_FLAG_OBSOLETE);
- return !SetObsolete && !DimObsolete;
+static const char *mls2str(MachineLearningStatus MLS) {
+ switch (MLS) {
+ case ml::MachineLearningStatus::Enabled:
+ return "enabled";
+ case ml::MachineLearningStatus::DisabledDueToUniqueUpdateEvery:
+ return "disabled-ue";
+ case ml::MachineLearningStatus::DisabledDueToExcludedChart:
+ return "disabled-sp";
+ default:
+ return "unknown";
+ }
+}
+
+static const char *mt2str(MetricType MT) {
+ switch (MT) {
+ case ml::MetricType::Constant:
+ return "constant";
+ case ml::MetricType::Variable:
+ return "variable";
+ default:
+ return "unknown";
+ }
}
-std::pair<CalculatedNumber *, size_t> Dimension::getCalculatedNumbers() {
+static const char *ts2str(TrainingStatus TS) {
+ switch (TS) {
+ case ml::TrainingStatus::PendingWithModel:
+ return "pending-with-model";
+ case ml::TrainingStatus::PendingWithoutModel:
+ return "pending-without-model";
+ case ml::TrainingStatus::Trained:
+ return "trained";
+ case ml::TrainingStatus::Untrained:
+ return "untrained";
+ default:
+ return "unknown";
+ }
+}
+
+static const char *tr2str(TrainingResult TR) {
+ switch (TR) {
+ case ml::TrainingResult::Ok:
+ return "ok";
+ case ml::TrainingResult::InvalidQueryTimeRange:
+ return "invalid-query";
+ case ml::TrainingResult::NotEnoughCollectedValues:
+ return "missing-values";
+ case ml::TrainingResult::NullAcquiredDimension:
+ return "null-acquired-dim";
+ case ml::TrainingResult::ChartUnderReplication:
+ return "chart-under-replication";
+ default:
+ return "unknown";
+ }
+}
+
+std::pair<CalculatedNumber *, TrainingResponse> Dimension::getCalculatedNumbers(const TrainingRequest &TrainingReq) {
+ TrainingResponse TrainingResp = {};
+
+ TrainingResp.RequestTime = TrainingReq.RequestTime;
+ TrainingResp.FirstEntryOnRequest = TrainingReq.FirstEntryOnRequest;
+ TrainingResp.LastEntryOnRequest = TrainingReq.LastEntryOnRequest;
+
+ TrainingResp.FirstEntryOnResponse = rrddim_first_entry_s_of_tier(RD, 0);
+ TrainingResp.LastEntryOnResponse = rrddim_last_entry_s_of_tier(RD, 0);
+
size_t MinN = Cfg.MinTrainSamples;
size_t MaxN = Cfg.MaxTrainSamples;
// Figure out what our time window should be.
- time_t BeforeT = now_realtime_sec() - 1;
- time_t AfterT = BeforeT - (MaxN * updateEvery());
-
- BeforeT -= (BeforeT % updateEvery());
- AfterT -= (AfterT % updateEvery());
-
- BeforeT = std::min(BeforeT, latestTime());
- AfterT = std::max(AfterT, oldestTime());
+ TrainingResp.QueryBeforeT = TrainingResp.LastEntryOnResponse;
+ TrainingResp.QueryAfterT = std::max(
+ TrainingResp.QueryBeforeT - static_cast<time_t>((MaxN - 1) * updateEvery()),
+ TrainingResp.FirstEntryOnResponse
+ );
+
+ if (TrainingResp.QueryAfterT >= TrainingResp.QueryBeforeT) {
+ TrainingResp.Result = TrainingResult::InvalidQueryTimeRange;
+ return { nullptr, TrainingResp };
+ }
- if (AfterT >= BeforeT)
- return { nullptr, 0 };
+ if (rrdset_is_replicating(RD->rrdset)) {
+ TrainingResp.Result = TrainingResult::ChartUnderReplication;
+ return { nullptr, TrainingResp };
+ }
CalculatedNumber *CNs = new CalculatedNumber[MaxN * (Cfg.LagN + 1)]();
// Start the query.
- unsigned Idx = 0;
- unsigned CollectedValues = 0;
- unsigned TotalValues = 0;
+ size_t Idx = 0;
CalculatedNumber LastValue = std::numeric_limits<CalculatedNumber>::quiet_NaN();
Query Q = Query(getRD());
- Q.init(AfterT, BeforeT);
+ Q.init(TrainingResp.QueryAfterT, TrainingResp.QueryBeforeT);
while (!Q.isFinished()) {
if (Idx == MaxN)
break;
auto P = Q.nextMetric();
+
CalculatedNumber Value = P.second;
if (netdata_double_isnumber(Value)) {
+ if (!TrainingResp.DbAfterT)
+ TrainingResp.DbAfterT = P.first;
+ TrainingResp.DbBeforeT = P.first;
+
CNs[Idx] = Value;
LastValue = CNs[Idx];
- CollectedValues++;
+ TrainingResp.CollectedValues++;
} else
CNs[Idx] = LastValue;
Idx++;
}
- TotalValues = Idx;
+ TrainingResp.TotalValues = Idx;
+
+ if (TrainingResp.CollectedValues < MinN) {
+ TrainingResp.Result = TrainingResult::NotEnoughCollectedValues;
- if (CollectedValues < MinN) {
delete[] CNs;
- return { nullptr, 0 };
+ return { nullptr, TrainingResp };
}
// Find first non-NaN value.
- for (Idx = 0; std::isnan(CNs[Idx]); Idx++, TotalValues--) { }
+ for (Idx = 0; std::isnan(CNs[Idx]); Idx++, TrainingResp.TotalValues--) { }
// Overwrite NaN values.
if (Idx != 0)
- memmove(CNs, &CNs[Idx], sizeof(CalculatedNumber) * TotalValues);
+ memmove(CNs, &CNs[Idx], sizeof(CalculatedNumber) * TrainingResp.TotalValues);
- return { CNs, TotalValues };
+ TrainingResp.Result = TrainingResult::Ok;
+ return { CNs, TrainingResp };
}
-MLResult Dimension::trainModel() {
- auto P = getCalculatedNumbers();
+TrainingResult Dimension::trainModel(const TrainingRequest &TrainingReq) {
+ auto P = getCalculatedNumbers(TrainingReq);
CalculatedNumber *CNs = P.first;
- unsigned N = P.second;
+ TrainingResponse TrainingResp = P.second;
+
+ if (TrainingResp.Result != TrainingResult::Ok) {
+ std::lock_guard<Mutex> L(M);
+
+ MT = MetricType::Constant;
+
+ switch (TS) {
+ case TrainingStatus::PendingWithModel:
+ TS = TrainingStatus::Trained;
+ break;
+ case TrainingStatus::PendingWithoutModel:
+ TS = TrainingStatus::Untrained;
+ break;
+ default:
+ break;
+ }
+
+ TR = TrainingResp;
- if (!CNs)
- return MLResult::MissingData;
+ LastTrainingTime = TrainingResp.LastEntryOnResponse;
+ return TrainingResp.Result;
+ }
+ unsigned N = TrainingResp.TotalValues;
unsigned TargetNumSamples = Cfg.MaxTrainSamples * Cfg.RandomSamplingRatio;
double SamplingRatio = std::min(static_cast<double>(TargetNumSamples) / N, 1.0);
SamplesBuffer SB = SamplesBuffer(CNs, N, 1, Cfg.DiffN, Cfg.SmoothN, Cfg.LagN,
SamplingRatio, Cfg.RandomNums);
- std::vector<DSample> Samples = SB.preprocess();
+ std::vector<DSample> Samples;
+ SB.preprocess(Samples);
KMeans KM;
KM.train(Samples, Cfg.MaxKMeansIters);
{
- std::lock_guard<std::mutex> Lock(Mutex);
- Models[0] = KM;
- }
+ std::lock_guard<Mutex> L(M);
- Trained = true;
- ConstantModel = true;
+ if (Models.size() < Cfg.NumModelsToUse) {
+ Models.push_back(std::move(KM));
+ } else {
+ std::rotate(std::begin(Models), std::begin(Models) + 1, std::end(Models));
+ Models[Models.size() - 1] = std::move(KM);
+ }
+
+ MT = MetricType::Constant;
+ TS = TrainingStatus::Trained;
+ TR = TrainingResp;
+ LastTrainingTime = rrddim_last_entry_s(RD);
+ }
delete[] CNs;
- return MLResult::Success;
+ return TrainingResp.Result;
}
-bool Dimension::shouldTrain(const TimePoint &TP) const {
- if (ConstantModel)
- return false;
+void Dimension::scheduleForTraining(time_t CurrT) {
+ switch (MT) {
+ case MetricType::Constant: {
+ return;
+ } default:
+ break;
+ }
- return (LastTrainedAt + Seconds(Cfg.TrainEvery * updateEvery())) < TP;
+ switch (TS) {
+ case TrainingStatus::PendingWithModel:
+ case TrainingStatus::PendingWithoutModel:
+ break;
+ case TrainingStatus::Untrained: {
+ Host *H = reinterpret_cast<Host *>(RD->rrdset->rrdhost->ml_host);
+ TS = TrainingStatus::PendingWithoutModel;
+ H->scheduleForTraining(getTrainingRequest(CurrT));
+ break;
+ }
+ case TrainingStatus::Trained: {
+ bool NeedsTraining = (time_t)(LastTrainingTime + (Cfg.TrainEvery * updateEvery())) < CurrT;
+
+ if (NeedsTraining) {
+ Host *H = reinterpret_cast<Host *>(RD->rrdset->rrdhost->ml_host);
+ TS = TrainingStatus::PendingWithModel;
+ H->scheduleForTraining(getTrainingRequest(CurrT));
+ }
+ break;
+ }
+ }
}
-bool Dimension::predict(CalculatedNumber Value, bool Exists) {
+bool Dimension::predict(time_t CurrT, CalculatedNumber Value, bool Exists) {
+ // Nothing to do if ML is disabled for this dimension
+ if (MLS != MachineLearningStatus::Enabled)
+ return false;
+
+ // Don't treat values that don't exist as anomalous
if (!Exists) {
CNs.clear();
- AnomalyBit = false;
return false;
}
+ // Save the value and return if we don't have enough values for a sample
unsigned N = Cfg.DiffN + Cfg.SmoothN + Cfg.LagN;
if (CNs.size() < N) {
CNs.push_back(Value);
- AnomalyBit = false;
return false;
}
+ // Push the value and check if it's different from the last one
+ bool SameValue = true;
std::rotate(std::begin(CNs), std::begin(CNs) + 1, std::end(CNs));
-
if (CNs[N - 1] != Value)
- ConstantModel = false;
-
+ SameValue = false;
CNs[N - 1] = Value;
- if (!isTrained() || ConstantModel) {
- AnomalyBit = false;
- return false;
- }
-
- CalculatedNumber *TmpCNs = new CalculatedNumber[N * (Cfg.LagN + 1)]();
+ // Create the sample
+ CalculatedNumber TmpCNs[N * (Cfg.LagN + 1)];
+ memset(TmpCNs, 0, N * (Cfg.LagN + 1) * sizeof(CalculatedNumber));
std::memcpy(TmpCNs, CNs.data(), N * sizeof(CalculatedNumber));
SamplesBuffer SB = SamplesBuffer(TmpCNs, N, 1,
Cfg.DiffN, Cfg.SmoothN, Cfg.LagN,
1.0, Cfg.RandomNums);
- const DSample Sample = SB.preprocess().back();
- delete[] TmpCNs;
+ SB.preprocess(Feature);
- std::unique_lock<std::mutex> Lock(Mutex, std::defer_lock);
- if (!Lock.try_lock()) {
- AnomalyBit = false;
+ /*
+ * Lock to predict and possibly schedule the dimension for training
+ */
+
+ std::unique_lock<Mutex> L(M, std::defer_lock);
+ if (!L.try_lock()) {
return false;
}
+ // Mark the metric time as variable if we received different values
+ if (!SameValue)
+ MT = MetricType::Variable;
+
+ // Decide if the dimension needs to be scheduled for training
+ scheduleForTraining(CurrT);
+
+ // Nothing to do if we don't have a model
+ switch (TS) {
+ case TrainingStatus::Untrained:
+ case TrainingStatus::PendingWithoutModel:
+ return false;
+ default:
+ break;
+ }
+
+ /*
+ * Use the KMeans models to check if the value is anomalous
+ */
+
+ size_t ModelsConsulted = 0;
+ size_t Sum = 0;
+
for (const auto &KM : Models) {
- double AnomalyScore = KM.anomalyScore(Sample);
- if (AnomalyScore == std::numeric_limits<CalculatedNumber>::quiet_NaN()) {
- AnomalyBit = false;
+ ModelsConsulted++;
+
+ double AnomalyScore = KM.anomalyScore(Feature);
+ if (AnomalyScore == std::numeric_limits<CalculatedNumber>::quiet_NaN())
continue;
- }
if (AnomalyScore < (100 * Cfg.DimensionAnomalyScoreThreshold)) {
- AnomalyBit = false;
+ global_statistics_ml_models_consulted(ModelsConsulted);
return false;
}
+
+ Sum += 1;
}
- AnomalyBit = true;
- return true;
+ global_statistics_ml_models_consulted(ModelsConsulted);
+ return Sum;
}
-std::array<KMeans, 1> Dimension::getModels() {
- std::unique_lock<std::mutex> Lock(Mutex);
+std::vector<KMeans> Dimension::getModels() {
+ std::unique_lock<Mutex> L(M);
return Models;
}
+
+void Dimension::dump() const {
+ const char *ChartId = rrdset_id(RD->rrdset);
+ const char *DimensionId = rrddim_id(RD);
+
+ const char *MLS_Str = mls2str(MLS);
+ const char *MT_Str = mt2str(MT);
+ const char *TS_Str = ts2str(TS);
+ const char *TR_Str = tr2str(TR.Result);
+
+ const char *fmt =
+ "[ML] %s.%s: MLS=%s, MT=%s, TS=%s, Result=%s, "
+ "ReqTime=%ld, FEOReq=%ld, LEOReq=%ld, "
+ "FEOResp=%ld, LEOResp=%ld, QTR=<%ld, %ld>, DBTR=<%ld, %ld>, Collected=%zu, Total=%zu";
+
+ error(fmt,
+ ChartId, DimensionId, MLS_Str, MT_Str, TS_Str, TR_Str,
+ TR.RequestTime, TR.FirstEntryOnRequest, TR.LastEntryOnRequest,
+ TR.FirstEntryOnResponse, TR.LastEntryOnResponse,
+ TR.QueryAfterT, TR.QueryBeforeT, TR.DbAfterT, TR.DbBeforeT, TR.CollectedValues, TR.TotalValues
+ );
+}
diff --git a/ml/Dimension.h b/ml/Dimension.h
index 3ec56e09..2b1adfff 100644
--- a/ml/Dimension.h
+++ b/ml/Dimension.h
@@ -3,6 +3,8 @@
#ifndef ML_DIMENSION_H
#define ML_DIMENSION_H
+#include "Mutex.h"
+#include "Stats.h"
#include "Query.h"
#include "Config.h"
@@ -10,12 +12,6 @@
namespace ml {
-enum class MLResult {
- Success = 0,
- MissingData,
- NaN,
-};
-
static inline std::string getMLDimensionID(RRDDIM *RD) {
RRDSET *RS = RD->rrdset;
@@ -24,16 +20,118 @@ static inline std::string getMLDimensionID(RRDDIM *RD) {
return SS.str();
}
+enum class MachineLearningStatus {
+ // Enable training/prediction
+ Enabled,
+
+ // Disable due to update every being different from the host's
+ DisabledDueToUniqueUpdateEvery,
+
+ // Disable because configuration pattern matches the chart's id
+ DisabledDueToExcludedChart,
+};
+
+enum class TrainingStatus {
+ // We don't have a model for this dimension
+ Untrained,
+
+ // Request for training sent, but we don't have any models yet
+ PendingWithoutModel,
+
+ // Request to update existing models sent
+ PendingWithModel,
+
+ // Have a valid, up-to-date model
+ Trained,
+};
+
+enum class MetricType {
+ // The dimension has constant values, no need to train
+ Constant,
+
+ // The dimension's values fluctuate, we need to generate a model
+ Variable,
+};
+
+struct TrainingRequest {
+ // Chart/dimension we want to train
+ STRING *ChartId;
+ STRING *DimensionId;
+
+ // Creation time of request
+ time_t RequestTime;
+
+ // First/last entry of this dimension in DB
+ // at the point the request was made
+ time_t FirstEntryOnRequest;
+ time_t LastEntryOnRequest;
+};
+
+void dumpTrainingRequest(const TrainingRequest &TrainingReq, const char *Prefix);
+
+enum TrainingResult {
+ // We managed to create a KMeans model
+ Ok,
+ // Could not query DB with a correct time range
+ InvalidQueryTimeRange,
+ // Did not gather enough data from DB to run KMeans
+ NotEnoughCollectedValues,
+ // Acquired a null dimension
+ NullAcquiredDimension,
+ // Chart is under replication
+ ChartUnderReplication,
+};
+
+struct TrainingResponse {
+ // Time when the request for this response was made
+ time_t RequestTime;
+
+ // First/last entry of the dimension in DB when generating the request
+ time_t FirstEntryOnRequest;
+ time_t LastEntryOnRequest;
+
+ // First/last entry of the dimension in DB when generating the response
+ time_t FirstEntryOnResponse;
+ time_t LastEntryOnResponse;
+
+ // After/Before timestamps of our DB query
+ time_t QueryAfterT;
+ time_t QueryBeforeT;
+
+ // Actual after/before returned by the DB query ops
+ time_t DbAfterT;
+ time_t DbBeforeT;
+
+ // Number of doubles returned by the DB query
+ size_t CollectedValues;
+
+ // Number of values we return to the caller
+ size_t TotalValues;
+
+ // Result of training response
+ TrainingResult Result;
+};
+
+void dumpTrainingResponse(const TrainingResponse &TrainingResp, const char *Prefix);
+
class Dimension {
public:
Dimension(RRDDIM *RD) :
RD(RD),
- LastTrainedAt(Seconds(0)),
- Trained(false),
- ConstantModel(false),
- AnomalyScore(0.0),
- AnomalyBit(0)
- { }
+ MT(MetricType::Constant),
+ TS(TrainingStatus::Untrained),
+ TR(),
+ LastTrainingTime(0)
+ {
+ if (simple_pattern_matches(Cfg.SP_ChartsToSkip, rrdset_name(RD->rrdset)))
+ MLS = MachineLearningStatus::DisabledDueToExcludedChart;
+ else if (RD->update_every != RD->rrdset->rrdhost->rrd_update_every)
+ MLS = MachineLearningStatus::DisabledDueToUniqueUpdateEvery;
+ else
+ MLS = MachineLearningStatus::Enabled;
+
+ Models.reserve(Cfg.NumModelsToUse);
+ }
RRDDIM *getRD() const {
return RD;
@@ -43,50 +141,56 @@ public:
return RD->update_every;
}
- time_t latestTime() const {
- return Query(RD).latestTime();
- }
-
- time_t oldestTime() const {
- return Query(RD).oldestTime();
+ MetricType getMT() const {
+ return MT;
}
- bool isTrained() const {
- return Trained;
+ TrainingStatus getTS() const {
+ return TS;
}
- bool isAnomalous() const {
- return AnomalyBit;
+ MachineLearningStatus getMLS() const {
+ return MLS;
}
- bool shouldTrain(const TimePoint &TP) const;
+ TrainingResult trainModel(const TrainingRequest &TR);
- bool isActive() const;
+ void scheduleForTraining(time_t CurrT);
- MLResult trainModel();
+ bool predict(time_t CurrT, CalculatedNumber Value, bool Exists);
- bool predict(CalculatedNumber Value, bool Exists);
+ std::vector<KMeans> getModels();
+
+ void dump() const;
- std::pair<bool, double> detect(size_t WindowLength, bool Reset);
-
- std::array<KMeans, 1> getModels();
+private:
+ TrainingRequest getTrainingRequest(time_t CurrT) const {
+ return TrainingRequest {
+ string_dup(RD->rrdset->id),
+ string_dup(RD->id),
+ CurrT,
+ rrddim_first_entry_s(RD),
+ rrddim_last_entry_s(RD)
+ };
+ }
private:
- std::pair<CalculatedNumber *, size_t> getCalculatedNumbers();
+ std::pair<CalculatedNumber *, TrainingResponse> getCalculatedNumbers(const TrainingRequest &TrainingReq);
public:
RRDDIM *RD;
+ MetricType MT;
+ TrainingStatus TS;
+ TrainingResponse TR;
- TimePoint LastTrainedAt;
- std::atomic<bool> Trained;
- std::atomic<bool> ConstantModel;
+ time_t LastTrainingTime;
- CalculatedNumber AnomalyScore;
- std::atomic<bool> AnomalyBit;
+ MachineLearningStatus MLS;
std::vector<CalculatedNumber> CNs;
- std::array<KMeans, 1> Models;
- std::mutex Mutex;
+ DSample Feature;
+ std::vector<KMeans> Models;
+ Mutex M;
};
} // namespace ml
diff --git a/ml/Host.cc b/ml/Host.cc
index 4a57178c..a5f276a8 100644
--- a/ml/Host.cc
+++ b/ml/Host.cc
@@ -2,42 +2,24 @@
#include "Config.h"
#include "Host.h"
+#include "Queue.h"
#include "ADCharts.h"
#include "json/single_include/nlohmann/json.hpp"
using namespace ml;
-void RrdHost::addDimension(Dimension *D) {
- std::lock_guard<std::mutex> Lock(Mutex);
-
- DimensionsMap[D->getRD()] = D;
-
- // Default construct mutex for dimension
- LocksMap[D];
+void Host::addChart(Chart *C) {
+ std::lock_guard<Mutex> L(M);
+ Charts[C->getRS()] = C;
}
-void RrdHost::removeDimension(Dimension *D) {
- // Remove the dimension from the hosts map.
- {
- std::lock_guard<std::mutex> Lock(Mutex);
- DimensionsMap.erase(D->getRD());
- }
-
- // Delete the dimension by locking the mutex that protects it.
- {
- std::lock_guard<std::mutex> Lock(LocksMap[D]);
- delete D;
- }
-
- // Remove the lock entry for the deleted dimension.
- {
- std::lock_guard<std::mutex> Lock(Mutex);
- LocksMap.erase(D);
- }
+void Host::removeChart(Chart *C) {
+ std::lock_guard<Mutex> L(M);
+ Charts.erase(C->getRS());
}
-void RrdHost::getConfigAsJson(nlohmann::json &Json) const {
+void Host::getConfigAsJson(nlohmann::json &Json) const {
Json["version"] = 1;
Json["enabled"] = Cfg.EnableAnomalyDetection;
@@ -63,193 +45,343 @@ void RrdHost::getConfigAsJson(nlohmann::json &Json) const {
Json["charts-to-skip"] = Cfg.ChartsToSkip;
}
-void TrainableHost::getModelsAsJson(nlohmann::json &Json) {
- std::lock_guard<std::mutex> Lock(Mutex);
+void Host::getModelsAsJson(nlohmann::json &Json) {
+ std::lock_guard<Mutex> L(M);
- for (auto &DP : DimensionsMap) {
- Dimension *D = DP.second;
-
- nlohmann::json JsonArray = nlohmann::json::array();
- for (const KMeans &KM : D->getModels()) {
- nlohmann::json J;
- KM.toJson(J);
- JsonArray.push_back(J);
- }
- Json[getMLDimensionID(D->getRD())] = JsonArray;
+ for (auto &CP : Charts) {
+ Chart *C = CP.second;
+ C->getModelsAsJson(Json);
}
-
- return;
}
-std::pair<Dimension *, Duration<double>>
-TrainableHost::findDimensionToTrain(const TimePoint &NowTP) {
- std::lock_guard<std::mutex> Lock(Mutex);
+#define WORKER_JOB_DETECTION_PREP 0
+#define WORKER_JOB_DETECTION_DIM_CHART 1
+#define WORKER_JOB_DETECTION_HOST_CHART 2
+#define WORKER_JOB_DETECTION_STATS 3
+#define WORKER_JOB_DETECTION_RESOURCES 4
- Duration<double> AllottedDuration = Duration<double>{Cfg.TrainEvery * updateEvery()} / (DimensionsMap.size() + 1);
+void Host::detectOnce() {
+ worker_is_busy(WORKER_JOB_DETECTION_PREP);
- for (auto &DP : DimensionsMap) {
- Dimension *D = DP.second;
+ MLS = {};
+ MachineLearningStats MLSCopy = {};
+ TrainingStats TSCopy = {};
- if (D->shouldTrain(NowTP)) {
- LocksMap[D].lock();
- return { D, AllottedDuration };
- }
- }
+ {
+ std::lock_guard<Mutex> L(M);
- return { nullptr, AllottedDuration };
-}
+ /*
+ * prediction/detection stats
+ */
+ for (auto &CP : Charts) {
+ Chart *C = CP.second;
-void TrainableHost::trainDimension(Dimension *D, const TimePoint &NowTP) {
- if (D == nullptr)
- return;
+ if (!C->isAvailableForML())
+ continue;
- D->LastTrainedAt = NowTP + Seconds{D->updateEvery()};
- D->trainModel();
+ MachineLearningStats ChartMLS = C->getMLS();
- {
- std::lock_guard<std::mutex> Lock(Mutex);
- LocksMap[D].unlock();
- }
-}
+ MLS.NumMachineLearningStatusEnabled += ChartMLS.NumMachineLearningStatusEnabled;
+ MLS.NumMachineLearningStatusDisabledUE += ChartMLS.NumMachineLearningStatusDisabledUE;
+ MLS.NumMachineLearningStatusDisabledSP += ChartMLS.NumMachineLearningStatusDisabledSP;
-void TrainableHost::train() {
- Duration<double> MaxSleepFor = Seconds{10 * updateEvery()};
+ MLS.NumMetricTypeConstant += ChartMLS.NumMetricTypeConstant;
+ MLS.NumMetricTypeVariable += ChartMLS.NumMetricTypeVariable;
- worker_register("MLTRAIN");
- worker_register_job_name(0, "dimensions");
+ MLS.NumTrainingStatusUntrained += ChartMLS.NumTrainingStatusUntrained;
+ MLS.NumTrainingStatusPendingWithoutModel += ChartMLS.NumTrainingStatusPendingWithoutModel;
+ MLS.NumTrainingStatusTrained += ChartMLS.NumTrainingStatusTrained;
+ MLS.NumTrainingStatusPendingWithModel += ChartMLS.NumTrainingStatusPendingWithModel;
- worker_is_busy(0);
- while (!netdata_exit) {
- netdata_thread_testcancel();
- netdata_thread_disable_cancelability();
+ MLS.NumAnomalousDimensions += ChartMLS.NumAnomalousDimensions;
+ MLS.NumNormalDimensions += ChartMLS.NumNormalDimensions;
+ }
- updateResourceUsage();
+ HostAnomalyRate = 0.0;
+ size_t NumActiveDimensions = MLS.NumAnomalousDimensions + MLS.NumNormalDimensions;
+ if (NumActiveDimensions)
+ HostAnomalyRate = static_cast<double>(MLS.NumAnomalousDimensions) / NumActiveDimensions;
- TimePoint NowTP = SteadyClock::now();
+ MLSCopy = MLS;
- auto P = findDimensionToTrain(NowTP);
- trainDimension(P.first, NowTP);
+ /*
+ * training stats
+ */
+ TSCopy = TS;
- netdata_thread_enable_cancelability();
+ TS.QueueSize = 0;
+ TS.NumPoppedItems = 0;
- Duration<double> AllottedDuration = P.second;
- Duration<double> RealDuration = SteadyClock::now() - NowTP;
+ TS.AllottedUT = 0;
+ TS.ConsumedUT = 0;
+ TS.RemainingUT = 0;
- Duration<double> SleepFor;
- if (RealDuration >= AllottedDuration)
- continue;
+ TS.TrainingResultOk = 0;
+ TS.TrainingResultInvalidQueryTimeRange = 0;
+ TS.TrainingResultNotEnoughCollectedValues = 0;
+ TS.TrainingResultNullAcquiredDimension = 0;
+ TS.TrainingResultChartUnderReplication = 0;
+ }
- worker_is_idle();
- SleepFor = std::min(AllottedDuration - RealDuration, MaxSleepFor);
- TimePoint Now = SteadyClock::now();
- auto Until = Now + SleepFor;
- while (Now < Until && !netdata_exit) {
- std::this_thread::sleep_for(std::chrono::milliseconds(1000));
- Now = SteadyClock::now();
- }
- worker_is_busy(0);
+ // Calc the avg values
+ if (TSCopy.NumPoppedItems) {
+ TSCopy.QueueSize /= TSCopy.NumPoppedItems;
+ TSCopy.AllottedUT /= TSCopy.NumPoppedItems;
+ TSCopy.ConsumedUT /= TSCopy.NumPoppedItems;
+ TSCopy.RemainingUT /= TSCopy.NumPoppedItems;
+
+ TSCopy.TrainingResultOk /= TSCopy.NumPoppedItems;
+ TSCopy.TrainingResultInvalidQueryTimeRange /= TSCopy.NumPoppedItems;
+ TSCopy.TrainingResultNotEnoughCollectedValues /= TSCopy.NumPoppedItems;
+ TSCopy.TrainingResultNullAcquiredDimension /= TSCopy.NumPoppedItems;
+ TSCopy.TrainingResultChartUnderReplication /= TSCopy.NumPoppedItems;
+ } else {
+ TSCopy.QueueSize = 0;
+ TSCopy.AllottedUT = 0;
+ TSCopy.ConsumedUT = 0;
+ TSCopy.RemainingUT = 0;
}
-}
-#define WORKER_JOB_DETECT_DIMENSION 0
-#define WORKER_JOB_UPDATE_DETECTION_CHART 1
-#define WORKER_JOB_UPDATE_ANOMALY_RATES 2
-#define WORKER_JOB_UPDATE_CHARTS 3
+ if(!RH)
+ return;
-#if WORKER_UTILIZATION_MAX_JOB_TYPES < 5
-#error WORKER_UTILIZATION_MAX_JOB_TYPES has to be at least 5
+ worker_is_busy(WORKER_JOB_DETECTION_DIM_CHART);
+ updateDimensionsChart(RH, MLSCopy);
+
+ worker_is_busy(WORKER_JOB_DETECTION_HOST_CHART);
+ updateHostAndDetectionRateCharts(RH, HostAnomalyRate * 10000.0);
+
+#ifdef NETDATA_ML_RESOURCE_CHARTS
+ worker_is_busy(WORKER_JOB_DETECTION_RESOURCES);
+ struct rusage PredictionRU;
+ getrusage(RUSAGE_THREAD, &PredictionRU);
+ updateResourceUsageCharts(RH, PredictionRU, TSCopy.TrainingRU);
#endif
-void DetectableHost::detectOnce() {
- size_t NumAnomalousDimensions = 0;
- size_t NumNormalDimensions = 0;
- size_t NumTrainedDimensions = 0;
- size_t NumActiveDimensions = 0;
+ worker_is_busy(WORKER_JOB_DETECTION_STATS);
+ updateTrainingStatisticsChart(RH, TSCopy);
+}
- {
- std::lock_guard<std::mutex> Lock(Mutex);
+class AcquiredDimension {
+public:
+ static AcquiredDimension find(RRDHOST *RH, STRING *ChartId, STRING *DimensionId) {
+ RRDDIM_ACQUIRED *AcqRD = nullptr;
+ Dimension *D = nullptr;
+
+ RRDSET *RS = rrdset_find(RH, string2str(ChartId));
+ if (RS) {
+ AcqRD = rrddim_find_and_acquire(RS, string2str(DimensionId));
+ if (AcqRD) {
+ RRDDIM *RD = rrddim_acquired_to_rrddim(AcqRD);
+ if (RD)
+ D = reinterpret_cast<Dimension *>(RD->ml_dimension);
+ }
+ }
- for (auto &DP : DimensionsMap) {
- worker_is_busy(WORKER_JOB_DETECT_DIMENSION);
+ return AcquiredDimension(AcqRD, D);
+ }
- Dimension *D = DP.second;
+private:
+ AcquiredDimension(RRDDIM_ACQUIRED *AcqRD, Dimension *D) : AcqRD(AcqRD), D(D) {}
- if (!D->isActive())
- continue;
+public:
+ TrainingResult train(const TrainingRequest &TR) {
+ if (!D)
+ return TrainingResult::NullAcquiredDimension;
- NumActiveDimensions++;
- NumTrainedDimensions += D->isTrained();
+ return D->trainModel(TR);
+ }
- bool IsAnomalous = D->isAnomalous();
- if (IsAnomalous)
- NumAnomalousDimensions += 1;
+ ~AcquiredDimension() {
+ if (AcqRD)
+ rrddim_acquired_release(AcqRD);
+ }
+
+private:
+ RRDDIM_ACQUIRED *AcqRD;
+ Dimension *D;
+};
+
+void Host::scheduleForTraining(TrainingRequest TR) {
+ TrainingQueue.push(TR);
+}
+
+#define WORKER_JOB_TRAINING_FIND 0
+#define WORKER_JOB_TRAINING_TRAIN 1
+#define WORKER_JOB_TRAINING_STATS 2
+
+void Host::train() {
+ worker_register("MLTRAIN");
+ worker_register_job_name(WORKER_JOB_TRAINING_FIND, "find");
+ worker_register_job_name(WORKER_JOB_TRAINING_TRAIN, "train");
+ worker_register_job_name(WORKER_JOB_TRAINING_STATS, "stats");
+
+ service_register(SERVICE_THREAD_TYPE_NETDATA, NULL, (force_quit_t )ml_cancel_anomaly_detection_threads, RH, true);
+
+ while (service_running(SERVICE_ML_TRAINING)) {
+ auto P = TrainingQueue.pop();
+ TrainingRequest TrainingReq = P.first;
+ size_t Size = P.second;
+
+ if (ThreadsCancelled) {
+ info("Stopping training thread because it was cancelled.");
+ break;
}
- if (NumAnomalousDimensions)
- HostAnomalyRate = static_cast<double>(NumAnomalousDimensions) / NumActiveDimensions;
- else
- HostAnomalyRate = 0.0;
+ usec_t AllottedUT = (Cfg.TrainEvery * RH->rrd_update_every * USEC_PER_SEC) / Size;
+ if (AllottedUT > USEC_PER_SEC)
+ AllottedUT = USEC_PER_SEC;
- NumNormalDimensions = NumActiveDimensions - NumAnomalousDimensions;
- }
+ usec_t StartUT = now_monotonic_usec();
+ TrainingResult TrainingRes;
+ {
+ worker_is_busy(WORKER_JOB_TRAINING_FIND);
+ AcquiredDimension AcqDim = AcquiredDimension::find(RH, TrainingReq.ChartId, TrainingReq.DimensionId);
- this->NumAnomalousDimensions = NumAnomalousDimensions;
- this->NumNormalDimensions = NumNormalDimensions;
- this->NumTrainedDimensions = NumTrainedDimensions;
- this->NumActiveDimensions = NumActiveDimensions;
+ worker_is_busy(WORKER_JOB_TRAINING_TRAIN);
+ TrainingRes = AcqDim.train(TrainingReq);
- worker_is_busy(WORKER_JOB_UPDATE_CHARTS);
- updateDimensionsChart(getRH(), NumTrainedDimensions, NumNormalDimensions, NumAnomalousDimensions);
- updateHostAndDetectionRateCharts(getRH(), HostAnomalyRate * 10000.0);
+ string_freez(TrainingReq.ChartId);
+ string_freez(TrainingReq.DimensionId);
+ }
+ usec_t ConsumedUT = now_monotonic_usec() - StartUT;
+
+ worker_is_busy(WORKER_JOB_TRAINING_STATS);
+
+ usec_t RemainingUT = 0;
+ if (ConsumedUT < AllottedUT)
+ RemainingUT = AllottedUT - ConsumedUT;
+
+ {
+ std::lock_guard<Mutex> L(M);
+
+ if (TS.AllottedUT == 0) {
+ struct rusage TRU;
+ getrusage(RUSAGE_THREAD, &TRU);
+ TS.TrainingRU = TRU;
+ }
+
+ TS.QueueSize += Size;
+ TS.NumPoppedItems += 1;
+
+ TS.AllottedUT += AllottedUT;
+ TS.ConsumedUT += ConsumedUT;
+ TS.RemainingUT += RemainingUT;
+
+ switch (TrainingRes) {
+ case TrainingResult::Ok:
+ TS.TrainingResultOk += 1;
+ break;
+ case TrainingResult::InvalidQueryTimeRange:
+ TS.TrainingResultInvalidQueryTimeRange += 1;
+ break;
+ case TrainingResult::NotEnoughCollectedValues:
+ TS.TrainingResultNotEnoughCollectedValues += 1;
+ break;
+ case TrainingResult::NullAcquiredDimension:
+ TS.TrainingResultNullAcquiredDimension += 1;
+ break;
+ case TrainingResult::ChartUnderReplication:
+ TS.TrainingResultChartUnderReplication += 1;
+ break;
+ }
+ }
- struct rusage TRU;
- getResourceUsage(&TRU);
- updateTrainingChart(getRH(), &TRU);
+ worker_is_idle();
+ std::this_thread::sleep_for(std::chrono::microseconds{RemainingUT});
+ worker_is_busy(0);
+ }
}
-void DetectableHost::detect() {
+void Host::detect() {
worker_register("MLDETECT");
- worker_register_job_name(WORKER_JOB_DETECT_DIMENSION, "dimensions");
- worker_register_job_name(WORKER_JOB_UPDATE_DETECTION_CHART, "detection chart");
- worker_register_job_name(WORKER_JOB_UPDATE_ANOMALY_RATES, "anomaly rates");
- worker_register_job_name(WORKER_JOB_UPDATE_CHARTS, "charts");
+ worker_register_job_name(WORKER_JOB_DETECTION_PREP, "prep");
+ worker_register_job_name(WORKER_JOB_DETECTION_DIM_CHART, "dim chart");
+ worker_register_job_name(WORKER_JOB_DETECTION_HOST_CHART, "host chart");
+ worker_register_job_name(WORKER_JOB_DETECTION_STATS, "stats");
+ worker_register_job_name(WORKER_JOB_DETECTION_RESOURCES, "resources");
- std::this_thread::sleep_for(Seconds{10});
+ service_register(SERVICE_THREAD_TYPE_NETDATA, NULL, (force_quit_t )ml_cancel_anomaly_detection_threads, RH, true);
heartbeat_t HB;
heartbeat_init(&HB);
- while (!netdata_exit) {
- netdata_thread_testcancel();
+ while (service_running((SERVICE_TYPE)(SERVICE_ML_PREDICTION | SERVICE_COLLECTORS))) {
worker_is_idle();
- heartbeat_next(&HB, updateEvery() * USEC_PER_SEC);
-
- netdata_thread_disable_cancelability();
+ heartbeat_next(&HB, (RH ? RH->rrd_update_every : default_rrd_update_every) * USEC_PER_SEC);
detectOnce();
-
- worker_is_busy(WORKER_JOB_UPDATE_DETECTION_CHART);
- updateDetectionChart(getRH());
- netdata_thread_enable_cancelability();
}
}
-void DetectableHost::getDetectionInfoAsJson(nlohmann::json &Json) const {
+void Host::getDetectionInfoAsJson(nlohmann::json &Json) const {
Json["version"] = 1;
- Json["anomalous-dimensions"] = NumAnomalousDimensions;
- Json["normal-dimensions"] = NumNormalDimensions;
- Json["total-dimensions"] = NumAnomalousDimensions + NumNormalDimensions;
- Json["trained-dimensions"] = NumTrainedDimensions;
+ Json["anomalous-dimensions"] = MLS.NumAnomalousDimensions;
+ Json["normal-dimensions"] = MLS.NumNormalDimensions;
+ Json["total-dimensions"] = MLS.NumAnomalousDimensions + MLS.NumNormalDimensions;
+ Json["trained-dimensions"] = MLS.NumTrainingStatusTrained + MLS.NumTrainingStatusPendingWithModel;
+}
+
+void *train_main(void *Arg) {
+ Host *H = reinterpret_cast<Host *>(Arg);
+ H->train();
+ return nullptr;
}
-void DetectableHost::startAnomalyDetectionThreads() {
- TrainingThread = std::thread(&TrainableHost::train, this);
- DetectionThread = std::thread(&DetectableHost::detect, this);
+void *detect_main(void *Arg) {
+ Host *H = reinterpret_cast<Host *>(Arg);
+ H->detect();
+ return nullptr;
}
-void DetectableHost::stopAnomalyDetectionThreads() {
- netdata_thread_cancel(TrainingThread.native_handle());
- netdata_thread_cancel(DetectionThread.native_handle());
+void Host::startAnomalyDetectionThreads() {
+ if (ThreadsRunning) {
+ error("Anomaly detections threads for host %s are already-up and running.", rrdhost_hostname(RH));
+ return;
+ }
+
+ ThreadsRunning = true;
+ ThreadsCancelled = false;
+ ThreadsJoined = false;
+
+ char Tag[NETDATA_THREAD_TAG_MAX + 1];
+
+// #define ML_DISABLE_JOINING
- TrainingThread.join();
- DetectionThread.join();
+ snprintfz(Tag, NETDATA_THREAD_TAG_MAX, "MLTR[%s]", rrdhost_hostname(RH));
+ netdata_thread_create(&TrainingThread, Tag, NETDATA_THREAD_OPTION_JOINABLE, train_main, static_cast<void *>(this));
+
+ snprintfz(Tag, NETDATA_THREAD_TAG_MAX, "MLDT[%s]", rrdhost_hostname(RH));
+ netdata_thread_create(&DetectionThread, Tag, NETDATA_THREAD_OPTION_JOINABLE, detect_main, static_cast<void *>(this));
+}
+
+void Host::stopAnomalyDetectionThreads(bool join) {
+ if (!ThreadsRunning) {
+ error("Anomaly detections threads for host %s have already been stopped.", rrdhost_hostname(RH));
+ return;
+ }
+
+ if(!ThreadsCancelled) {
+ ThreadsCancelled = true;
+
+ // Signal the training queue to stop popping-items
+ TrainingQueue.signal();
+ netdata_thread_cancel(TrainingThread);
+ netdata_thread_cancel(DetectionThread);
+ }
+
+ if (join && !ThreadsJoined) {
+ ThreadsJoined = true;
+ ThreadsRunning = false;
+
+ // these fail on alpine linux and our CI hangs forever
+ // failing to compile static builds
+
+ // commenting them, until we find a solution
+
+ // to enable again:
+ // NETDATA_THREAD_OPTION_DEFAULT needs to become NETDATA_THREAD_OPTION_JOINABLE
+
+ netdata_thread_join(TrainingThread, nullptr);
+ netdata_thread_join(DetectionThread, nullptr);
+ }
}
diff --git a/ml/Host.h b/ml/Host.h
index 52a0cd09..289cb5ab 100644
--- a/ml/Host.h
+++ b/ml/Host.h
@@ -3,97 +3,67 @@
#ifndef ML_HOST_H
#define ML_HOST_H
+#include "Mutex.h"
#include "Config.h"
#include "Dimension.h"
+#include "Chart.h"
+#include "Queue.h"
#include "ml-private.h"
#include "json/single_include/nlohmann/json.hpp"
-namespace ml {
+namespace ml
+{
-class RrdHost {
-public:
- RrdHost(RRDHOST *RH) : RH(RH) {};
-
- RRDHOST *getRH() { return RH; }
-
- unsigned updateEvery() { return RH->rrd_update_every; }
-
- std::string getUUID() {
- char S[UUID_STR_LEN];
- uuid_unparse_lower(RH->host_uuid, S);
- return S;
- }
-
- void addDimension(Dimension *D);
- void removeDimension(Dimension *D);
-
- void getConfigAsJson(nlohmann::json &Json) const;
-
- virtual ~RrdHost() {};
+class Host {
-protected:
- RRDHOST *RH;
-
- // Protect dimension and lock maps
- std::mutex Mutex;
-
- std::unordered_map<RRDDIM *, Dimension *> DimensionsMap;
- std::unordered_map<Dimension *, std::mutex> LocksMap;
-};
+friend void* train_main(void *);
+friend void *detect_main(void *);
-class TrainableHost : public RrdHost {
public:
- TrainableHost(RRDHOST *RH) : RrdHost(RH) {}
-
- void train();
-
- void updateResourceUsage() {
- std::lock_guard<std::mutex> Lock(ResourceUsageMutex);
- getrusage(RUSAGE_THREAD, &ResourceUsage);
- }
-
- void getResourceUsage(struct rusage *RU) {
- std::lock_guard<std::mutex> Lock(ResourceUsageMutex);
- memcpy(RU, &ResourceUsage, sizeof(struct rusage));
- }
+ Host(RRDHOST *RH) :
+ RH(RH),
+ MLS(),
+ TS(),
+ HostAnomalyRate(0.0),
+ ThreadsRunning(false),
+ ThreadsCancelled(false),
+ ThreadsJoined(false)
+ {}
+
+ void addChart(Chart *C);
+ void removeChart(Chart *C);
+ void getConfigAsJson(nlohmann::json &Json) const;
void getModelsAsJson(nlohmann::json &Json);
-
-private:
- std::pair<Dimension *, Duration<double>> findDimensionToTrain(const TimePoint &NowTP);
- void trainDimension(Dimension *D, const TimePoint &NowTP);
-
- struct rusage ResourceUsage{};
- std::mutex ResourceUsageMutex;
-};
-
-class DetectableHost : public TrainableHost {
-public:
- DetectableHost(RRDHOST *RH) : TrainableHost(RH) {}
+ void getDetectionInfoAsJson(nlohmann::json &Json) const;
void startAnomalyDetectionThreads();
- void stopAnomalyDetectionThreads();
+ void stopAnomalyDetectionThreads(bool join);
- void getDetectionInfoAsJson(nlohmann::json &Json) const;
+ void scheduleForTraining(TrainingRequest TR);
+ void train();
-private:
void detect();
void detectOnce();
private:
- std::thread TrainingThread;
- std::thread DetectionThread;
-
+ RRDHOST *RH;
+ MachineLearningStats MLS;
+ TrainingStats TS;
CalculatedNumber HostAnomalyRate{0.0};
+ std::atomic<bool> ThreadsRunning;
+ std::atomic<bool> ThreadsCancelled;
+ std::atomic<bool> ThreadsJoined;
- size_t NumAnomalousDimensions{0};
- size_t NumNormalDimensions{0};
- size_t NumTrainedDimensions{0};
- size_t NumActiveDimensions{0};
-};
+ Queue<TrainingRequest> TrainingQueue;
-using Host = DetectableHost;
+ Mutex M;
+ std::unordered_map<RRDSET *, Chart *> Charts;
+
+ netdata_thread_t TrainingThread;
+ netdata_thread_t DetectionThread;
+};
} // namespace ml
diff --git a/ml/Mutex.h b/ml/Mutex.h
new file mode 100644
index 00000000..fcdb7531
--- /dev/null
+++ b/ml/Mutex.h
@@ -0,0 +1,36 @@
+#ifndef ML_MUTEX_H
+#define ML_MUTEX_H
+
+#include "ml-private.h"
+
+class Mutex {
+public:
+ Mutex() {
+ netdata_mutex_init(&M);
+ }
+
+ void lock() {
+ netdata_mutex_lock(&M);
+ }
+
+ void unlock() {
+ netdata_mutex_unlock(&M);
+ }
+
+ bool try_lock() {
+ return netdata_mutex_trylock(&M) == 0;
+ }
+
+ netdata_mutex_t *inner() {
+ return &M;
+ }
+
+ ~Mutex() {
+ netdata_mutex_destroy(&M);
+ }
+
+private:
+ netdata_mutex_t M;
+};
+
+#endif /* ML_MUTEX_H */
diff --git a/ml/Query.h b/ml/Query.h
index 78d11700..42a96e85 100644
--- a/ml/Query.h
+++ b/ml/Query.h
@@ -8,19 +8,19 @@ namespace ml {
class Query {
public:
Query(RRDDIM *RD) : RD(RD), Initialized(false) {
- Ops = RD->tiers[0]->query_ops;
+ Ops = RD->tiers[0].query_ops;
}
time_t latestTime() {
- return Ops->latest_time(RD->tiers[0]->db_metric_handle);
+ return Ops->latest_time_s(RD->tiers[0].db_metric_handle);
}
time_t oldestTime() {
- return Ops->oldest_time(RD->tiers[0]->db_metric_handle);
+ return Ops->oldest_time_s(RD->tiers[0].db_metric_handle);
}
void init(time_t AfterT, time_t BeforeT) {
- Ops->init(RD->tiers[0]->db_metric_handle, &Handle, AfterT, BeforeT);
+ Ops->init(RD->tiers[0].db_metric_handle, &Handle, AfterT, BeforeT, STORAGE_PRIORITY_BEST_EFFORT);
Initialized = true;
points_read = 0;
}
@@ -40,7 +40,7 @@ public:
std::pair<time_t, CalculatedNumber> nextMetric() {
points_read++;
STORAGE_POINT sp = Ops->next_metric(&Handle);
- return { sp.start_time, sp.sum / sp.count };
+ return {sp.end_time_s, sp.sum / sp.count };
}
private:
diff --git a/ml/Queue.h b/ml/Queue.h
new file mode 100644
index 00000000..37a74bd0
--- /dev/null
+++ b/ml/Queue.h
@@ -0,0 +1,66 @@
+#ifndef QUEUE_H
+#define QUEUE_H
+
+#include "ml-private.h"
+#include "Mutex.h"
+#include <queue>
+#include <mutex>
+#include <condition_variable>
+
+template<typename T>
+class Queue {
+public:
+ Queue(void) : Q(), M() {
+ pthread_cond_init(&CV, nullptr);
+ Exit = false;
+ }
+
+ ~Queue() {
+ pthread_cond_destroy(&CV);
+ }
+
+ void push(T t) {
+ std::lock_guard<Mutex> L(M);
+
+ Q.push(t);
+ pthread_cond_signal(&CV);
+ }
+
+ std::pair<T, size_t> pop(void) {
+ std::lock_guard<Mutex> L(M);
+
+ while (Q.empty()) {
+ pthread_cond_wait(&CV, M.inner());
+
+ if (Exit) {
+ // This should happen only when we are destroying a host.
+ // Callers should use a flag dedicated to checking if we
+ // are about to delete the host or exit the agent. The original
+ // implementation would call pthread_exit which would cause
+ // the queue's mutex to be destroyed twice (and fail on the
+ // 2nd time)
+ return { T(), 0 };
+ }
+ }
+
+ T V = Q.front();
+ size_t Size = Q.size();
+ Q.pop();
+
+ return { V, Size };
+ }
+
+ void signal() {
+ std::lock_guard<Mutex> L(M);
+ Exit = true;
+ pthread_cond_signal(&CV);
+ }
+
+private:
+ std::queue<T> Q;
+ Mutex M;
+ pthread_cond_t CV;
+ std::atomic<bool> Exit;
+};
+
+#endif /* QUEUE_H */
diff --git a/ml/README.md b/ml/README.md
index f6fd923a..7f3ed276 100644
--- a/ml/README.md
+++ b/ml/README.md
@@ -1,14 +1,18 @@
-<!--
-title: Configure machine learning (ML) powered anomaly detection
-custom_edit_url: https://github.com/netdata/netdata/edit/master/ml/README.md
-description: This is an in-depth look at how Netdata uses ML to detect anomalies.
-keywords: [machine learning, anomaly detection, Netdata ML]
+<!--
+title: "Configure machine learning (ML) powered anomaly detection"
+custom_edit_url: "https://github.com/netdata/netdata/edit/master/ml/README.md"
+description: "This is an in-depth look at how Netdata uses ML to detect anomalies."
+sidebar_label: "Configure machine learning (ML) powered anomaly detection"
+learn_status: "Published"
+learn_topic_type: "Tasks"
+learn_rel_path: "Setup"
-->
+
# Machine learning (ML) powered anomaly detection
## Overview
-As of [`v1.32.0`](https://github.com/netdata/netdata/releases/tag/v1.32.0), Netdata comes with some ML powered [anomaly detection](https://en.wikipedia.org/wiki/Anomaly_detection) capabilities built into it and available to use out of the box, with zero configuration required (ML was enabled by default in `v1.35.0-29-nightly` in [this PR](https://github.com/netdata/netdata/pull/13158), previously it required a one line config change).
+As of [`v1.32.0`](https://github.com/netdata/netdata/releases/tag/v1.32.0), Netdata comes with ML powered [anomaly detection](https://en.wikipedia.org/wiki/Anomaly_detection) capabilities built into it and available to use out of the box, with zero configuration required (ML was enabled by default in `v1.35.0-29-nightly` in [this PR](https://github.com/netdata/netdata/pull/13158), previously it required a one line config change).
🚧 **Note**: If you would like to get involved and help us with some feedback, email us at analytics-ml-team@netdata.cloud, comment on the [beta launch post](https://community.netdata.cloud/t/anomaly-advisor-beta-launch/2717) in the Netdata community, or come join us in the [🤖-ml-powered-monitoring](https://discord.gg/4eRSEUpJnc) channel of the Netdata discord.
@@ -99,49 +103,7 @@ An ["anomaly detector"](#anomaly-detector) looks at all anomaly bits of a node.
Essentially if the ["Node Anomaly Rate"](#node-anomaly-rate) (NAR) passes a defined threshold and stays above that threshold for a persistent amount of time, a "Node [Anomaly Event](#anomaly-event)" will be triggered.
-These anomaly events are currently exposed via `/api/v1/anomaly_events`
-
-**Note**: Clicking the link below will likely return an empty list of `[]`. This is the response when no anomaly events exist in the specified range. The example response below is illustrative of what the response would be when one or more anomaly events exist within the range of `after` to `before`.
-
-https://london.my-netdata.io/api/v1/anomaly_events?after=1638365182000&before=1638365602000
-
-If an event exists within the window, the result would be a list of start and end times.
-
-```
-[
- [
- 1638367788,
- 1638367851
- ]
-]
-```
-
-Information about each anomaly event can then be found at the `/api/v1/anomaly_event_info` endpoint (making sure to pass the `after` and `before` params):
-
-**Note**: If you click the below url you will get a `null` since no such anomaly event exists as the response is just an illustrative example taken from a node that did have such an anomaly event.
-
-https://london.my-netdata.io/api/v1/anomaly_event_info?after=1638367788&before=1638367851
-
-```
-[
- [
- 0.66,
- "netdata.response_time|max"
- ],
- [
- 0.63,
- "netdata.response_time|average"
- ],
- [
- 0.54,
- "netdata.requests|requests"
- ],
- ...
-```
-
-The query returns a list of dimension anomaly rates for all dimensions that were considered part of the detected anomaly event.
-
-**Note**: We plan to build additional anomaly detection and exploration features into both Netdata Agent and Netdata Cloud. The current endpoints are still under active development to power the upcoming features.
+These anomaly events are currently exposed via the `new_anomaly_event` dimension on the `anomaly_detection.anomaly_detection` chart.
## Configuration
@@ -152,7 +114,7 @@ To enable or disable anomaly detection:
2. In the `[ml]` section, set `enabled = yes` to enable or `enabled = no` to disable.
3. Restart netdata (typically `sudo systemctl restart netdata`).
-**Note**: If you would like to learn more about configuring Netdata please see [the configuration guide](https://learn.netdata.cloud/guides/step-by-step/step-04).
+**Note**: If you would like to learn more about configuring Netdata please see [the configuration guide](https://github.com/netdata/netdata/blob/master/docs/guides/step-by-step/step-04.md).
Below is a list of all the available configuration params and their default values.
@@ -162,6 +124,7 @@ Below is a list of all the available configuration params and their default valu
# maximum num samples to train = 14400
# minimum num samples to train = 3600
# train every = 3600
+ # number of models per dimension = 1
# dbengine anomaly rate every = 30
# num samples to diff = 1
# num samples to smooth = 3
@@ -169,12 +132,9 @@ Below is a list of all the available configuration params and their default valu
# random sampling ratio = 0.2
# maximum number of k-means iterations = 1000
# dimension anomaly score threshold = 0.99
- # host anomaly rate threshold = 0.01000
- # minimum window size = 30.00000
- # maximum window size = 600.00000
- # idle window size = 30.00000
- # window minimum anomaly rate = 0.25000
- # anomaly event min dimension rate threshold = 0.05000
+ # host anomaly rate threshold = 1.0
+ # anomaly detection grouping method = average
+ # anomaly detection grouping duration = 300
# hosts to skip from training = !*
# charts to skip from training = netdata.*
```
@@ -183,7 +143,7 @@ Below is a list of all the available configuration params and their default valu
If you would like to run ML on a parent instead of at the edge, some configuration options are illustrated below.
-This example assumes 3 child nodes [streaming](https://learn.netdata.cloud/docs/agent/streaming) to 1 parent node and illustrates the main ways you might want to configure running ML for the children on the parent, running ML on the children themselves, or even a mix of approaches.
+This example assumes 3 child nodes [streaming](https://github.com/netdata/netdata/blob/master/streaming/README.md) to 1 parent node and illustrates the main ways you might want to configure running ML for the children on the parent, running ML on the children themselves, or even a mix of approaches.
![parent_child_options](https://user-images.githubusercontent.com/2178292/164439761-8fb7dddd-c4d8-4329-9f44-9a794937a086.png)
@@ -221,6 +181,7 @@ This example assumes 3 child nodes [streaming](https://learn.netdata.cloud/docs/
- `maximum num samples to train`: (`3600`/`86400`) This is the maximum amount of time you would like to train each model on. For example, the default of `14400` trains on the preceding 4 hours of data, assuming an `update every` of 1 second.
- `minimum num samples to train`: (`900`/`21600`) This is the minimum amount of data required to be able to train a model. For example, the default of `900` implies that once at least 15 minutes of data is available for training, a model is trained, otherwise it is skipped and checked again at the next training run.
- `train every`: (`1800`/`21600`) This is how often each model will be retrained. For example, the default of `3600` means that each model is retrained every hour. Note: The training of all models is spread out across the `train every` period for efficiency, so in reality, it means that each model will be trained in a staggered manner within each `train every` period.
+- `number of models per dimension`: (`1`/`168`) This is the number of trained models that will be used for scoring. For example the default `number of models per dimension = 1` means that just the most recently trained model (covering up to the most recent `maximum num samples to train` of training data) for the dimension will be used to determine the corresponding anomaly bit. Alternatively, if you have `train every = 3600` and `number of models per dimension = 24` this means that netdata will store and use the last 24 trained models for each dimension when determining the anomaly bit, this means that for the latest feature vector in this configuration to be considered anomalous it would need to look anomalous across _all_ the models trained for that dimension in the last 24 hours. As such, increasing `number of models per dimension` may reduce some false positives since it will result in more models (covering a wider time frame of training) being used during scoring.
- `dbengine anomaly rate every`: (`30`/`900`) This is how often netdata will aggregate all the anomaly bits into a single chart (`anomaly_detection.anomaly_rates`). The aggregation into a single chart allows enabling anomaly rate ranking over _all_ metrics with one API call as opposed to a call per chart.
- `num samples to diff`: (`0`/`1`) This is a `0` or `1` to determine if you want the model to operate on differences of the raw data or just the raw data. For example, the default of `1` means that we take differences of the raw values. Using differences is more general and works on dimensions that might naturally tend to have some trends or cycles in them that is normal behavior to which we don't want to be too sensitive.
- `num samples to smooth`: (`0`/`5`) This is a small integer that controls the amount of smoothing applied as part of the feature processing used by the model. For example, the default of `3` means that the rolling average of the last 3 values is used. Smoothing like this helps the model be a little more robust to spiky types of dimensions that naturally "jump" up or down as part of their normal behavior.
@@ -228,40 +189,37 @@ This example assumes 3 child nodes [streaming](https://learn.netdata.cloud/docs/
- `random sampling ratio`: (`0.2`/`1.0`) This parameter determines how much of the available training data is randomly sampled when training a model. The default of `0.2` means that Netdata will train on a random 20% of training data. This parameter influences cost efficiency. At `0.2` the model is still reasonably trained while minimizing system overhead costs caused by the training.
- `maximum number of k-means iterations`: This is a parameter that can be passed to the model to limit the number of iterations in training the k-means model. Vast majority of cases can ignore and leave as default.
- `dimension anomaly score threshold`: (`0.01`/`5.00`) This is the threshold at which an individual dimension at a specific timestep is considered anomalous or not. For example, the default of `0.99` means that a dimension with an anomaly score of 99% or higher is flagged as anomalous. This is a normalized probability based on the training data, so the default of 99% means that anything that is as strange (based on distance measure) or more strange as the most strange 1% of data observed during training will be flagged as anomalous. If you wanted to make the anomaly detection on individual dimensions more sensitive you could try a value like `0.90` (90%) or to make it less sensitive you could try `1.5` (150%).
-- `host anomaly rate threshold`: (`0.0`/`1.0`) This is the percentage of dimensions (based on all those enabled for anomaly detection) that need to be considered anomalous at specific timestep for the host itself to be considered anomalous. For example, the default value of `0.01` means that if more than 1% of dimensions are anomalous at the same time then the host itself is considered in an anomalous state.
-- `minimum window size`: The Netdata "Anomaly Detector" logic works over a rolling window of data. This parameter defines the minimum length of window to consider. If over this window the host is in an anomalous state then an anomaly detection event will be triggered. For example, the default of `30` means that the detector will initially work over a rolling window of 30 seconds. Note: The length of this window will be dynamic once an anomaly event has been triggered such that it will expand as needed until either the max length of an anomaly event is hit or the host settles back into a normal state with sufficiently decreased host level anomaly states in the rolling window. Note: If you wanted to adjust the higher level anomaly detector behavior then this is one parameter you might adjust to see the impact of on anomaly detection events.
-- `maximum window size`: This parameter defines the maximum length of window to consider. If an anomaly event reaches this size, it will be closed. This is to provide an upper bound on the length of an anomaly event and cost of the anomaly detector logic for that event.
-- `window minimum anomaly rate`: (`0.0`/`1.0`) This parameter corresponds to a threshold on the percentage of time in the rolling window that the host was considered in an anomalous state. For example, the default of `0.25` means that if the host is in an anomalous state for 25% of more of the rolling window then and anomaly event will be triggered or extended if one is already active. Note: If you want to make the anomaly detector itself less sensitive, you can adjust this value to something like `0.75` which would mean the host needs to be much more consistently in an anomalous state to trigger an anomaly detection event. Likewise, a lower value like `0.1` would make the anomaly detector more sensitive.
-- `anomaly event min dimension rate threshold`: (`0.0`/`1.0`) This is a parameter that helps filter out irrelevant dimensions from anomaly events. For example, the default of `0.05` means that only dimensions that were considered anomalous for at least 5% of the anomaly event itself will be included in that anomaly event. The idea here is to just include dimensions that were consistently anomalous as opposed to those that may have just randomly happened to be anomalous at the same time.
+- `host anomaly rate threshold`: (`0.1`/`10.0`) This is the percentage of dimensions (based on all those enabled for anomaly detection) that need to be considered anomalous at specific timestep for the host itself to be considered anomalous. For example, the default value of `1.0` means that if more than 1% of dimensions are anomalous at the same time then the host itself is considered in an anomalous state.
+- `anomaly detection grouping method`: The grouping method used when calculating node level anomaly rate.
+- `anomaly detection grouping duration`: (`60`/`900`) The duration across which to calculate the node level anomaly rate, the default of `900` means that the node level anomaly rate is calculated across a rolling 5 minute window.
- `hosts to skip from training`: This parameter allows you to turn off anomaly detection for any child hosts on a parent host by defining those you would like to skip from training here. For example, a value like `dev-*` skips all hosts on a parent that begin with the "dev-" prefix. The default value of `!*` means "don't skip any".
-- `charts to skip from training`: This parameter allows you to exclude certain charts from anomaly detection. By default, only netdata related charts are excluded. This is to avoid the scenario where accessing the netdata dashboard could itself tigger some anomalies if you don't access them regularly. If you want to include charts that are excluded by default, add them in small groups and then measure any impact on performance before adding additional ones. Example: If you want to include system, apps, and user charts:`!system.* !apps.* !user.* *`.
+- `charts to skip from training`: This parameter allows you to exclude certain charts from anomaly detection. By default, only netdata related charts are excluded. This is to avoid the scenario where accessing the netdata dashboard could itself trigger some anomalies if you don't access them regularly. If you want to include charts that are excluded by default, add them in small groups and then measure any impact on performance before adding additional ones. Example: If you want to include system, apps, and user charts:`!system.* !apps.* !user.* *`.
## Charts
Once enabled, the "Anomaly Detection" menu and charts will be available on the dashboard.
-![anomaly_detection_menu](https://user-images.githubusercontent.com/2178292/144255721-4568aabf-39c7-4855-bf1c-31b1d60e28e6.png)
+![anomaly_detection_menu](https://user-images.githubusercontent.com/2178292/207584589-2e984786-5e01-404b-a20a-58573884d6df.png)
In terms of anomaly detection, the most interesting charts would be the `anomaly_detection.dimensions` and `anomaly_detection.anomaly_rate` ones, which hold the `anomalous` and `anomaly_rate` dimensions that show the overall number of dimensions considered anomalous at any time and the corresponding anomaly rate.
- `anomaly_detection.dimensions`: Total count of dimensions considered anomalous or normal.
- `anomaly_detection.dimensions`: Percentage of anomalous dimensions.
-- `anomaly_detection.detector_window`: The length of the active window used by the detector.
-- `anomaly_detection.detector_events`: Flags (0 or 1) to show when an anomaly event has been triggered by the detector.
+- `anomaly_detection.anomaly_detection`: Flags (0 or 1) to show when an anomaly event has been triggered by the detector.
Below is an example of how these charts may look in the presence of an anomaly event.
Initially we see a jump in `anomalous` dimensions:
-![anomalous](https://user-images.githubusercontent.com/2178292/144256036-c89fa768-5e5f-4278-9725-c67521c0d95e.png)
+![anomalous](https://user-images.githubusercontent.com/2178292/207589021-c0d2926f-bb55-4c5c-9e32-be1851558fa8.png)
And a corresponding jump in the `anomaly_rate`:
-![anomaly_rate](https://user-images.githubusercontent.com/2178292/144256071-7d157438-31f3-4b23-a795-0fd3b2e2e85c.png)
+![anomaly_rate](https://user-images.githubusercontent.com/2178292/207589172-8853804b-6826-4731-8d06-b9e32d3071af.png)
After a short while the rolling node anomaly rate goes `above_threshold`, and once it stays above threshold for long enough a `new_anomaly_event` is created:
-![anomaly_event](https://user-images.githubusercontent.com/2178292/144256152-910b06ec-26b8-45b4-bcb7-4c2acdf9af15.png)
+![anomaly_event](https://user-images.githubusercontent.com/2178292/207589308-931a3c76-440a-48c1-970e-191743d26607.png)
## Glossary
@@ -307,4 +265,4 @@ The anomaly rate across all dimensions of a node.
- Netdata uses [dlib](https://github.com/davisking/dlib) under the hood for its core ML features.
- You should benchmark Netdata resource usage before and after enabling ML. Typical overhead ranges from 1-2% additional CPU at most.
- The "anomaly bit" has been implemented to be a building block to underpin many more ML based use cases that we plan to deliver soon.
-- At its core Netdata uses an approach and problem formulation very similar to the Netdata python [anomalies collector](https://learn.netdata.cloud/docs/agent/collectors/python.d.plugin/anomalies), just implemented in a much much more efficient and scalable way in the agent in c++. So if you would like to learn more about the approach and are familiar with Python that is a useful resource to explore, as is the corresponding [deep dive tutorial](https://nbviewer.org/github/netdata/community/blob/main/netdata-agent-api/netdata-pandas/anomalies_collector_deepdive.ipynb) where the default model used is PCA instead of K-Means but the overall approach and formulation is similar.
+- At its core Netdata uses an approach and problem formulation very similar to the Netdata python [anomalies collector](https://github.com/netdata/netdata/blob/master/collectors/python.d.plugin/anomalies/README.md), just implemented in a much much more efficient and scalable way in the agent in c++. So if you would like to learn more about the approach and are familiar with Python that is a useful resource to explore, as is the corresponding [deep dive tutorial](https://nbviewer.org/github/netdata/community/blob/main/netdata-agent-api/netdata-pandas/anomalies_collector_deepdive.ipynb) where the default model used is PCA instead of K-Means but the overall approach and formulation is similar.
diff --git a/ml/SamplesBuffer.cc b/ml/SamplesBuffer.cc
index d276c6e0..359b60c2 100644
--- a/ml/SamplesBuffer.cc
+++ b/ml/SamplesBuffer.cc
@@ -54,12 +54,12 @@ void SamplesBuffer::diffSamples() {
void SamplesBuffer::smoothSamples() {
// Holds the mean value of each window
- CalculatedNumber *AccCNs = new CalculatedNumber[NumDimsPerSample]();
- Sample Acc(AccCNs, NumDimsPerSample);
+ CalculatedNumber AccCNs[1] = { 0 };
+ Sample Acc(AccCNs, 1);
// Used to avoid clobbering the accumulator when moving the window
- CalculatedNumber *TmpCNs = new CalculatedNumber[NumDimsPerSample]();
- Sample Tmp(TmpCNs, NumDimsPerSample);
+ CalculatedNumber TmpCNs[1] = { 0 };
+ Sample Tmp(TmpCNs, 1);
CalculatedNumber Factor = (CalculatedNumber) 1 / SmoothN;
@@ -88,9 +88,6 @@ void SamplesBuffer::smoothSamples() {
Acc.copy(Tmp);
Acc.scale(Factor);
}
-
- delete[] AccCNs;
- delete[] TmpCNs;
}
void SamplesBuffer::lagSamples() {
@@ -103,31 +100,30 @@ void SamplesBuffer::lagSamples() {
}
}
-std::vector<DSample> SamplesBuffer::preprocess() {
+void SamplesBuffer::preprocess(std::vector<DSample> &Samples) {
assert(Preprocessed == false);
- std::vector<DSample> DSamples;
size_t OutN = NumSamples;
// Diff
if (DiffN >= OutN)
- return DSamples;
+ return;
OutN -= DiffN;
diffSamples();
// Smooth
if (SmoothN == 0 || SmoothN > OutN)
- return DSamples;
+ return;
OutN -= (SmoothN - 1);
smoothSamples();
// Lag
if (LagN >= OutN)
- return DSamples;
+ return;
OutN -= LagN;
lagSamples();
- DSamples.reserve(OutN);
+ Samples.reserve(OutN);
Preprocessed = true;
uint32_t MaxMT = std::numeric_limits<uint32_t>::max();
@@ -143,8 +139,45 @@ std::vector<DSample> SamplesBuffer::preprocess() {
const Sample PS = getPreprocessedSample(Idx);
PS.initDSample(DS);
- DSamples.push_back(DS);
+ Samples.push_back(std::move(DS));
}
+}
+
+void SamplesBuffer::preprocess(DSample &Feature) {
+ assert(Preprocessed == false);
+
+ size_t OutN = NumSamples;
+
+ // Diff
+ if (DiffN >= OutN)
+ return;
+ OutN -= DiffN;
+ diffSamples();
- return DSamples;
+ // Smooth
+ if (SmoothN == 0 || SmoothN > OutN)
+ return;
+ OutN -= (SmoothN - 1);
+ smoothSamples();
+
+ // Lag
+ if (LagN >= OutN)
+ return;
+ OutN -= LagN;
+ lagSamples();
+
+ Preprocessed = true;
+
+ uint32_t MaxMT = std::numeric_limits<uint32_t>::max();
+ uint32_t CutOff = static_cast<double>(MaxMT) * SamplingRatio;
+
+ for (size_t Idx = NumSamples - OutN; Idx != NumSamples; Idx++) {
+ if (RandNums[Idx] > CutOff)
+ continue;
+
+ Feature.set_size(NumDimsPerSample * (LagN + 1));
+
+ const Sample PS = getPreprocessedSample(Idx);
+ PS.initDSample(Feature);
+ }
}
diff --git a/ml/SamplesBuffer.h b/ml/SamplesBuffer.h
index 1c7215cc..ca60f4b9 100644
--- a/ml/SamplesBuffer.h
+++ b/ml/SamplesBuffer.h
@@ -86,9 +86,12 @@ public:
DiffN(DiffN), SmoothN(SmoothN), LagN(LagN),
SamplingRatio(SamplingRatio), RandNums(RandNums),
BytesPerSample(NumDimsPerSample * sizeof(CalculatedNumber)),
- Preprocessed(false) {};
+ Preprocessed(false) {
+ assert(NumDimsPerSample == 1 && "SamplesBuffer supports only one dimension per sample");
+ };
- std::vector<DSample> preprocess();
+ void preprocess(std::vector<DSample> &Samples);
+ void preprocess(DSample &Feature);
std::vector<Sample> getPreprocessedSamples() const;
size_t capacity() const { return NumSamples; }
diff --git a/ml/SamplesBufferTests.cc b/ml/SamplesBufferTests.cc
deleted file mode 100644
index 5997a2a1..00000000
--- a/ml/SamplesBufferTests.cc
+++ /dev/null
@@ -1,146 +0,0 @@
-// SPDX-License-Identifier: GPL-3.0-or-later
-
-#include "ml/ml-private.h"
-#include <gtest/gtest.h>
-
-/*
- * The SamplesBuffer class implements the functionality of the following python
- * code:
- * >> df = pd.DataFrame(data=samples)
- * >> df = df.diff(diff_n).dropna()
- * >> df = df.rolling(smooth_n).mean().dropna()
- * >> df = pd.concat([df.shift(n) for n in range(lag_n + 1)], axis=1).dropna()
- *
- * Its correctness has been verified by automatically generating random
- * data frames in Python and comparing them with the correspondent preprocessed
- * SampleBuffers.
- *
- * The following tests are meant to catch unintended changes in the SamplesBuffer
- * implementation. For development purposes, one should compare changes against
- * the aforementioned python code.
-*/
-
-TEST(SamplesBufferTest, NS_8_NDPS_1_DN_1_SN_3_LN_1) {
- size_t NumSamples = 8, NumDimsPerSample = 1;
- size_t DiffN = 1, SmoothN = 3, LagN = 3;
-
- size_t N = NumSamples * NumDimsPerSample * (LagN + 1);
- CalculatedNumber *CNs = new CalculatedNumber[N]();
-
- CNs[0] = 0.7568336679490107;
- CNs[1] = 0.4814406581763254;
- CNs[2] = 0.40073555156221874;
- CNs[3] = 0.5973257298194408;
- CNs[4] = 0.5334727814345868;
- CNs[5] = 0.2632477193454843;
- CNs[6] = 0.2684839023122384;
- CNs[7] = 0.851332948637479;
-
- std::vector<uint32_t> RandNums(NumSamples, std::numeric_limits<uint32_t>::max());
- SamplesBuffer SB(CNs, NumSamples, NumDimsPerSample, DiffN, SmoothN, LagN, 1.0, RandNums);
- SB.preprocess();
-
- std::vector<Sample> Samples = SB.getPreprocessedSamples();
- EXPECT_EQ(Samples.size(), 2);
-
- Sample S0 = Samples[0];
- const CalculatedNumber *S0_CNs = S0.getCalculatedNumbers();
- Sample S1 = Samples[1];
- const CalculatedNumber *S1_CNs = S1.getCalculatedNumbers();
-
- EXPECT_NEAR(S0_CNs[0], -0.109614, 0.001);
- EXPECT_NEAR(S0_CNs[1], -0.0458293, 0.001);
- EXPECT_NEAR(S0_CNs[2], 0.017344, 0.001);
- EXPECT_NEAR(S0_CNs[3], -0.0531693, 0.001);
-
- EXPECT_NEAR(S1_CNs[0], 0.105953, 0.001);
- EXPECT_NEAR(S1_CNs[1], -0.109614, 0.001);
- EXPECT_NEAR(S1_CNs[2], -0.0458293, 0.001);
- EXPECT_NEAR(S1_CNs[3], 0.017344, 0.001);
-
- delete[] CNs;
-}
-
-TEST(SamplesBufferTest, NS_8_NDPS_1_DN_2_SN_3_LN_2) {
- size_t NumSamples = 8, NumDimsPerSample = 1;
- size_t DiffN = 2, SmoothN = 3, LagN = 2;
-
- size_t N = NumSamples * NumDimsPerSample * (LagN + 1);
- CalculatedNumber *CNs = new CalculatedNumber[N]();
-
- CNs[0] = 0.20511885291342846;
- CNs[1] = 0.13151717360306558;
- CNs[2] = 0.6017085062423134;
- CNs[3] = 0.46256882933941545;
- CNs[4] = 0.7887758447877941;
- CNs[5] = 0.9237989080034406;
- CNs[6] = 0.15552559051428083;
- CNs[7] = 0.6309750314597955;
-
- std::vector<uint32_t> RandNums(NumSamples, std::numeric_limits<uint32_t>::max());
- SamplesBuffer SB(CNs, NumSamples, NumDimsPerSample, DiffN, SmoothN, LagN, 1.0, RandNums);
- SB.preprocess();
-
- std::vector<Sample> Samples = SB.getPreprocessedSamples();
- EXPECT_EQ(Samples.size(), 2);
-
- Sample S0 = Samples[0];
- const CalculatedNumber *S0_CNs = S0.getCalculatedNumbers();
- Sample S1 = Samples[1];
- const CalculatedNumber *S1_CNs = S1.getCalculatedNumbers();
-
- EXPECT_NEAR(S0_CNs[0], 0.005016, 0.001);
- EXPECT_NEAR(S0_CNs[1], 0.326450, 0.001);
- EXPECT_NEAR(S0_CNs[2], 0.304903, 0.001);
-
- EXPECT_NEAR(S1_CNs[0], -0.154948, 0.001);
- EXPECT_NEAR(S1_CNs[1], 0.005016, 0.001);
- EXPECT_NEAR(S1_CNs[2], 0.326450, 0.001);
-
- delete[] CNs;
-}
-
-TEST(SamplesBufferTest, NS_8_NDPS_3_DN_2_SN_4_LN_1) {
- size_t NumSamples = 8, NumDimsPerSample = 3;
- size_t DiffN = 2, SmoothN = 4, LagN = 1;
-
- size_t N = NumSamples * NumDimsPerSample * (LagN + 1);
- CalculatedNumber *CNs = new CalculatedNumber[N]();
-
- CNs[0] = 0.34310900399667765; CNs[1] = 0.14694315994488194; CNs[2] = 0.8246677800938796;
- CNs[3] = 0.48249504592307835; CNs[4] = 0.23241087965531182; CNs[5] = 0.9595348555892567;
- CNs[6] = 0.44281094035598334; CNs[7] = 0.5143142171362715; CNs[8] = 0.06391303014242555;
- CNs[9] = 0.7460491027783901; CNs[10] = 0.43887217459032923; CNs[11] = 0.2814395025355999;
- CNs[12] = 0.9231114281214198; CNs[13] = 0.326882401786898; CNs[14] = 0.26747939220376216;
- CNs[15] = 0.7787571209969636; CNs[16] =0.5851700001235088; CNs[17] = 0.34410728945321567;
- CNs[18] = 0.9394494507088997; CNs[19] =0.17567223681734334; CNs[20] = 0.42732886195446984;
- CNs[21] = 0.9460522396152958; CNs[22] =0.23462747016780894; CNs[23] = 0.35983249900892145;
-
- std::vector<uint32_t> RandNums(NumSamples, std::numeric_limits<uint32_t>::max());
- SamplesBuffer SB(CNs, NumSamples, NumDimsPerSample, DiffN, SmoothN, LagN, 1.0, RandNums);
- SB.preprocess();
-
- std::vector<Sample> Samples = SB.getPreprocessedSamples();
- EXPECT_EQ(Samples.size(), 2);
-
- Sample S0 = Samples[0];
- const CalculatedNumber *S0_CNs = S0.getCalculatedNumbers();
- Sample S1 = Samples[1];
- const CalculatedNumber *S1_CNs = S1.getCalculatedNumbers();
-
- EXPECT_NEAR(S0_CNs[0], 0.198225, 0.001);
- EXPECT_NEAR(S0_CNs[1], 0.003529, 0.001);
- EXPECT_NEAR(S0_CNs[2], -0.063003, 0.001);
- EXPECT_NEAR(S0_CNs[3], 0.219066, 0.001);
- EXPECT_NEAR(S0_CNs[4], 0.133175, 0.001);
- EXPECT_NEAR(S0_CNs[5], -0.293154, 0.001);
-
- EXPECT_NEAR(S1_CNs[0], 0.174160, 0.001);
- EXPECT_NEAR(S1_CNs[1], -0.135722, 0.001);
- EXPECT_NEAR(S1_CNs[2], 0.110452, 0.001);
- EXPECT_NEAR(S1_CNs[3], 0.198225, 0.001);
- EXPECT_NEAR(S1_CNs[4], 0.003529, 0.001);
- EXPECT_NEAR(S1_CNs[5], -0.063003, 0.001);
-
- delete[] CNs;
-}
diff --git a/ml/Stats.h b/ml/Stats.h
new file mode 100644
index 00000000..b99bc39d
--- /dev/null
+++ b/ml/Stats.h
@@ -0,0 +1,46 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#ifndef ML_STATS_H
+#define ML_STATS_H
+
+#include "ml-private.h"
+
+namespace ml {
+
+struct MachineLearningStats {
+ size_t NumMachineLearningStatusEnabled;
+ size_t NumMachineLearningStatusDisabledUE;
+ size_t NumMachineLearningStatusDisabledSP;
+
+ size_t NumMetricTypeConstant;
+ size_t NumMetricTypeVariable;
+
+ size_t NumTrainingStatusUntrained;
+ size_t NumTrainingStatusPendingWithoutModel;
+ size_t NumTrainingStatusTrained;
+ size_t NumTrainingStatusPendingWithModel;
+
+ size_t NumAnomalousDimensions;
+ size_t NumNormalDimensions;
+};
+
+struct TrainingStats {
+ struct rusage TrainingRU;
+
+ size_t QueueSize;
+ size_t NumPoppedItems;
+
+ usec_t AllottedUT;
+ usec_t ConsumedUT;
+ usec_t RemainingUT;
+
+ size_t TrainingResultOk;
+ size_t TrainingResultInvalidQueryTimeRange;
+ size_t TrainingResultNotEnoughCollectedValues;
+ size_t TrainingResultNullAcquiredDimension;
+ size_t TrainingResultChartUnderReplication;
+};
+
+} // namespace ml
+
+#endif /* ML_STATS_H */
diff --git a/ml/ml-dummy.c b/ml/ml-dummy.c
index 492dfe2f..17801889 100644
--- a/ml/ml-dummy.c
+++ b/ml/ml-dummy.c
@@ -15,9 +15,37 @@ bool ml_enabled(RRDHOST *RH) {
void ml_init(void) {}
-void ml_new_host(RRDHOST *RH) { (void) RH; }
+void ml_host_new(RRDHOST *RH) {
+ UNUSED(RH);
+}
+
+void ml_host_delete(RRDHOST *RH) {
+ UNUSED(RH);
+}
+
+void ml_chart_new(RRDSET *RS) {
+ UNUSED(RS);
+}
+
+void ml_chart_delete(RRDSET *RS) {
+ UNUSED(RS);
+}
+
+void ml_dimension_new(RRDDIM *RD) {
+ UNUSED(RD);
+}
+
+void ml_dimension_delete(RRDDIM *RD) {
+ UNUSED(RD);
+}
-void ml_delete_host(RRDHOST *RH) { (void) RH; }
+void ml_start_anomaly_detection_threads(RRDHOST *RH) {
+ UNUSED(RH);
+}
+
+void ml_stop_anomaly_detection_threads(RRDHOST *RH) {
+ UNUSED(RH);
+}
char *ml_get_host_info(RRDHOST *RH) {
(void) RH;
@@ -29,17 +57,24 @@ char *ml_get_host_runtime_info(RRDHOST *RH) {
return NULL;
}
+void ml_chart_update_begin(RRDSET *RS) {
+ (void) RS;
+}
+
+void ml_chart_update_end(RRDSET *RS) {
+ (void) RS;
+}
+
char *ml_get_host_models(RRDHOST *RH) {
(void) RH;
return NULL;
}
-void ml_new_dimension(RRDDIM *RD) { (void) RD; }
-
-void ml_delete_dimension(RRDDIM *RD) { (void) RD; }
-
-bool ml_is_anomalous(RRDDIM *RD, double Value, bool Exists) {
- (void) RD; (void) Value; (void) Exists;
+bool ml_is_anomalous(RRDDIM *RD, time_t CurrT, double Value, bool Exists) {
+ (void) RD;
+ (void) CurrT;
+ (void) Value;
+ (void) Exists;
return false;
}
diff --git a/ml/ml-private.h b/ml/ml-private.h
index 2bd72ac5..e479f235 100644
--- a/ml/ml-private.h
+++ b/ml/ml-private.h
@@ -6,21 +6,8 @@
#include "KMeans.h"
#include "ml/ml.h"
-#include <chrono>
#include <map>
#include <mutex>
#include <sstream>
-namespace ml {
-
-using SteadyClock = std::chrono::steady_clock;
-using TimePoint = std::chrono::time_point<SteadyClock>;
-
-template<typename T>
-using Duration = std::chrono::duration<T>;
-
-using Seconds = std::chrono::seconds;
-
-} // namespace ml
-
#endif /* ML_PRIVATE_H */
diff --git a/ml/ml.cc b/ml/ml.cc
index 1a7d6ae2..461c83ba 100644
--- a/ml/ml.cc
+++ b/ml/ml.cc
@@ -2,6 +2,7 @@
#include "Config.h"
#include "Dimension.h"
+#include "Chart.h"
#include "Host.h"
#include <random>
@@ -45,56 +46,65 @@ void ml_init(void) {
Cfg.RandomNums.push_back(Gen());
}
-void ml_new_host(RRDHOST *RH) {
+void ml_host_new(RRDHOST *RH) {
if (!ml_enabled(RH))
return;
Host *H = new Host(RH);
- RH->ml_host = static_cast<ml_host_t>(H);
-
- H->startAnomalyDetectionThreads();
+ RH->ml_host = reinterpret_cast<ml_host_t *>(H);
}
-void ml_delete_host(RRDHOST *RH) {
- Host *H = static_cast<Host *>(RH->ml_host);
+void ml_host_delete(RRDHOST *RH) {
+ Host *H = reinterpret_cast<Host *>(RH->ml_host);
if (!H)
return;
- H->stopAnomalyDetectionThreads();
-
delete H;
RH->ml_host = nullptr;
}
-void ml_new_dimension(RRDDIM *RD) {
- RRDSET *RS = RD->rrdset;
-
- Host *H = static_cast<Host *>(RD->rrdset->rrdhost->ml_host);
+void ml_chart_new(RRDSET *RS) {
+ Host *H = reinterpret_cast<Host *>(RS->rrdhost->ml_host);
if (!H)
return;
- if (static_cast<unsigned>(RD->update_every) != H->updateEvery())
+ Chart *C = new Chart(RS);
+ RS->ml_chart = reinterpret_cast<ml_chart_t *>(C);
+
+ H->addChart(C);
+}
+
+void ml_chart_delete(RRDSET *RS) {
+ Host *H = reinterpret_cast<Host *>(RS->rrdhost->ml_host);
+ if (!H)
return;
- if (simple_pattern_matches(Cfg.SP_ChartsToSkip, rrdset_name(RS)))
+ Chart *C = reinterpret_cast<Chart *>(RS->ml_chart);
+ H->removeChart(C);
+
+ delete C;
+ RS->ml_chart = nullptr;
+}
+
+void ml_dimension_new(RRDDIM *RD) {
+ Chart *C = reinterpret_cast<Chart *>(RD->rrdset->ml_chart);
+ if (!C)
return;
Dimension *D = new Dimension(RD);
- RD->ml_dimension = static_cast<ml_dimension_t>(D);
- H->addDimension(D);
+ RD->ml_dimension = reinterpret_cast<ml_dimension_t *>(D);
+ C->addDimension(D);
}
-void ml_delete_dimension(RRDDIM *RD) {
- Dimension *D = static_cast<Dimension *>(RD->ml_dimension);
+void ml_dimension_delete(RRDDIM *RD) {
+ Dimension *D = reinterpret_cast<Dimension *>(RD->ml_dimension);
if (!D)
return;
- Host *H = static_cast<Host *>(RD->rrdset->rrdhost->ml_host);
- if (!H)
- delete D;
- else
- H->removeDimension(D);
+ Chart *C = reinterpret_cast<Chart *>(RD->rrdset->ml_chart);
+ C->removeDimension(D);
+ delete D;
RD->ml_dimension = nullptr;
}
@@ -102,7 +112,7 @@ char *ml_get_host_info(RRDHOST *RH) {
nlohmann::json ConfigJson;
if (RH && RH->ml_host) {
- Host *H = static_cast<Host *>(RH->ml_host);
+ Host *H = reinterpret_cast<Host *>(RH->ml_host);
H->getConfigAsJson(ConfigJson);
} else {
ConfigJson["enabled"] = false;
@@ -115,7 +125,7 @@ char *ml_get_host_runtime_info(RRDHOST *RH) {
nlohmann::json ConfigJson;
if (RH && RH->ml_host) {
- Host *H = static_cast<Host *>(RH->ml_host);
+ Host *H = reinterpret_cast<Host *>(RH->ml_host);
H->getDetectionInfoAsJson(ConfigJson);
} else {
return nullptr;
@@ -128,7 +138,7 @@ char *ml_get_host_models(RRDHOST *RH) {
nlohmann::json ModelsJson;
if (RH && RH->ml_host) {
- Host *H = static_cast<Host *>(RH->ml_host);
+ Host *H = reinterpret_cast<Host *>(RH->ml_host);
H->getModelsAsJson(ModelsJson);
return strdup(ModelsJson.dump(2, '\t').c_str());
}
@@ -136,30 +146,57 @@ char *ml_get_host_models(RRDHOST *RH) {
return nullptr;
}
-bool ml_is_anomalous(RRDDIM *RD, double Value, bool Exists) {
- Dimension *D = static_cast<Dimension *>(RD->ml_dimension);
- if (!D)
- return false;
+void ml_start_anomaly_detection_threads(RRDHOST *RH) {
+ if (RH && RH->ml_host) {
+ Host *H = reinterpret_cast<Host *>(RH->ml_host);
+ H->startAnomalyDetectionThreads();
+ }
+}
+
+void ml_stop_anomaly_detection_threads(RRDHOST *RH) {
+ if (RH && RH->ml_host) {
+ Host *H = reinterpret_cast<Host *>(RH->ml_host);
+ H->stopAnomalyDetectionThreads(true);
+ }
+}
- return D->predict(Value, Exists);
+void ml_cancel_anomaly_detection_threads(RRDHOST *RH) {
+ if (RH && RH->ml_host) {
+ Host *H = reinterpret_cast<Host *>(RH->ml_host);
+ H->stopAnomalyDetectionThreads(false);
+ }
}
-bool ml_streaming_enabled() {
- return Cfg.StreamADCharts;
+void ml_chart_update_begin(RRDSET *RS) {
+ Chart *C = reinterpret_cast<Chart *>(RS->ml_chart);
+ if (!C)
+ return;
+
+ C->updateBegin();
}
-#if defined(ENABLE_ML_TESTS)
+void ml_chart_update_end(RRDSET *RS) {
+ Chart *C = reinterpret_cast<Chart *>(RS->ml_chart);
+ if (!C)
+ return;
+
+ C->updateEnd();
+}
-#include "gtest/gtest.h"
+bool ml_is_anomalous(RRDDIM *RD, time_t CurrT, double Value, bool Exists) {
+ Dimension *D = reinterpret_cast<Dimension *>(RD->ml_dimension);
+ if (!D)
+ return false;
-int test_ml(int argc, char *argv[]) {
- (void) argc;
- (void) argv;
+ Chart *C = reinterpret_cast<Chart *>(RD->rrdset->ml_chart);
- ::testing::InitGoogleTest(&argc, argv);
- return RUN_ALL_TESTS();
+ bool IsAnomalous = D->predict(CurrT, Value, Exists);
+ C->updateDimension(D, IsAnomalous);
+ return IsAnomalous;
}
-#endif // ENABLE_ML_TESTS
+bool ml_streaming_enabled() {
+ return Cfg.StreamADCharts;
+}
#include "ml-private.h"
diff --git a/ml/ml.h b/ml/ml.h
index 8e62c498..8bed627f 100644
--- a/ml/ml.h
+++ b/ml/ml.h
@@ -14,35 +14,36 @@ extern "C" {
// the anomaly rate dimension, whenever its backing dimension is freed.
void rrddim_free(RRDSET *st, RRDDIM *rd);
-typedef void* ml_host_t;
-typedef void* ml_dimension_t;
-
bool ml_capable();
bool ml_enabled(RRDHOST *RH);
void ml_init(void);
-void ml_new_host(RRDHOST *RH);
-void ml_delete_host(RRDHOST *RH);
+void ml_host_new(RRDHOST *RH);
+void ml_host_delete(RRDHOST *RH);
+
+void ml_chart_new(RRDSET *RS);
+void ml_chart_delete(RRDSET *RS);
+
+void ml_dimension_new(RRDDIM *RD);
+void ml_dimension_delete(RRDDIM *RD);
+
+void ml_start_anomaly_detection_threads(RRDHOST *RH);
+void ml_stop_anomaly_detection_threads(RRDHOST *RH);
+void ml_cancel_anomaly_detection_threads(RRDHOST *RH);
char *ml_get_host_info(RRDHOST *RH);
char *ml_get_host_runtime_info(RRDHOST *RH);
char *ml_get_host_models(RRDHOST *RH);
-void ml_new_dimension(RRDDIM *RD);
-void ml_delete_dimension(RRDDIM *RD);
+void ml_chart_update_begin(RRDSET *RS);
+void ml_chart_update_end(RRDSET *RS);
-bool ml_is_anomalous(RRDDIM *RD, double value, bool exists);
+bool ml_is_anomalous(RRDDIM *RD, time_t curr_t, double value, bool exists);
bool ml_streaming_enabled();
-#define ML_ANOMALY_RATES_CHART_ID "anomaly_detection.anomaly_rates"
-
-#if defined(ENABLE_ML_TESTS)
-int test_ml(int argc, char *argv[]);
-#endif
-
#ifdef __cplusplus
};
#endif
diff --git a/ml/notebooks/netdata_anomaly_detection_deepdive.ipynb b/ml/notebooks/netdata_anomaly_detection_deepdive.ipynb
index 8d0c0c7e..14e4366b 100644
--- a/ml/notebooks/netdata_anomaly_detection_deepdive.ipynb
+++ b/ml/notebooks/netdata_anomaly_detection_deepdive.ipynb
@@ -309,7 +309,7 @@
},
{
"data": {
- "image/png": "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iIiIiEizJ35ejw17Sw1d5oDOLfDYnwaG/L2iogJXXHEF8vPz0dDQgEceeQRffvklfvjhBwDAjBkz8Oabb2Ly5Mm45ZZbkJOTAyEEbr75ZnTr1g05OTm49tpr0aRJEyxatAgbNmzAfffdh/LycrRr1w4TJ05Ep06dcPrpp+O4447DH3/8gYqKCnzyySd49tlnsXbtWlx55ZUYP358UNqWLVuGu+++GxUVFcjMzMSsWbPw7bff4vvvv0dJSQn27NmD6667Do899hhyc3Nx4YUXYt26dQCAF198EeXl5UFXLidOnIicnBy8/vrrAIALL7wQDzzwAEaNGhW0fffeey+2b9+OO+64A4WFhWjatCnee+899OvXDzfeeCOysrKwcuVKjBw5Ei+//HJMx4kBpo0crqhF6+wMq5NBREREROQ4U6dORefOnTFlyhQAQElJCR577DEUFhaiffv2+Oijj3DzzTdj1apV2LNnjy+AKy4uRqtWrfD666/jxRdfxLBhw1BXV4e77roLP/74I9q3b49JkybhP//5Dz788EMAQEZGBnJycvDf//4XF110EZYvX442bdqgV69euPfee9G2bVtfumpra3HllVdi0qRJGD58OEpLS9GkSRMAwNKlS7Fu3To0bdoUw4cPx5gxY9CuXbuY9oPa9gHArbfeirfffhu9e/fGkiVL8I9//AOzZ88G4H7FzMKFC5GamhrTugEGmLbx46o9uPurVfjhjpEY0q2V1ckhIiIiIopauCuNZhk0aBDuv/9+/Otf/8KFF16IUaNG4frrr8dnn32Gm266CYsWLcInn3yCsrIy7NixA3fddRfGjBmDc845J2hZmzdvxrp163D22WcDABoaGtCpUyff7//3f//nW+fAgQN9v/Xs2RN5eXl+AebmzZvRqVMnDB8+HADQokUL329nn322b9pLLrkE8+fPx5///OeY9kPPnj2Dtq+8vBwLFy7E5Zdf7puupqbG9/fll19uSHAJMMC0jUXbDwIANu4rZYBJRERERKRTnz59sGLFCvz6668YN24czjrrLPz1r3/Fn/70J2RlZeHyyy9HWloaWrdujdWrV2PatGl4++238fXXX/uuTHpJKTFw4EAsWrRIdV2ZmZkAgJSUFN/f3s967mMMfA2IEAJpaWlwuVy+70K9hzLUdGrb9+qrr6JVq1ZYtWqV6rKys7M1pzkSPuSHiIiIiIgcb+/evWjatCmuu+46PPjgg1ixYgU6d+6Mzp07Y/z48bjpppsAAEVFRXC5XLj00ksxfvx4rFixAgDQvHlzlJWVAQD69u2LwsJCX4BZV1eH9evXR5Wuvn37Yt++fVi2bBkAoKyszBeEzpgxA4cOHUJVVRV++OEHjBw5Eh07dkRBQQEOHjyImpoa/PLLL6rL7d69O1atWgWXy4W8vDwsXbo05Pa1aNECPXr0wDfffAPAHUCvXr06qu2JhFcwiYiIiIjI8dauXYsHH3wQKSkpSE9Px1tvvQUAuPbaa1FYWIj+/fsDAPbs2YObbrrJd/Xv2WefBQDceOONuP32230P+Zk8eTL++c9/oqSkBPX19bjnnnswcKD2ob8XXHAB3n//fXTu3BmTJk3CXXfdhaqqKjRp0gQzZ84EAIwYMQKXXnop8vPzcd1112HYsGEAgEcffRQjRoxAly5d0K9fP9Xljxw5Ej169MCAAQPQv39/HH/88WG37/PPP8ff//53jB8/HnV1dbjqqqswePBgXftYCyGlNHyhw4YNkzk5OYYvN5GN/XYNvlqWh2cvGYSrRxxpdXKIiIiIiHTZuHGjL4izkzvvvBPHHXccbrnlFquT4ifwKbBa58nNzY3ruzDVjqsQYrmUcpja9LyCSURERERECWno0KHIzs7GSy+9ZHVSkgYDTCIiIiIiSkjLly+3Ogkh3Xjjjbjxxht1zTNkyBB0797dlPQYhQGmTZgwUpmIiIiIKK6klEFPRiXjDBkyJK7ri+Z2Sj5FloiIiIiIYpaVlYWDBw9GFZSQ/UgpcfDgQWRlZemaj1cwbYIdPURERETkZF27dkV+fj4KCwutTgoZJCsrC127dtU1DwNMIiIiIiKKWXp6Onr06GF1MshiHCJLREREREREhmCASURERERERIZggElERERERESGYIBpE3zYFhEREREROR0DTJvhw2SJiIiIiMipGGDaDC9kEhERERGRUzHAtAm+B5OIiIiIiJyOASYREREREREZggEmERERERERGYIBJhERERERERmCAaZN8DUlRERERETkdAwwbYbP+iEiIiIiIqdigGkzvJBJREREREROxQDTJviaEiIiIiIicjoGmERERERERGQIBphERERERERkCAaYNsGnyBIRERERkdMxwLQZ3opJREREREROxQCTiIiIiIiIDMEAk4iIiIiIiAzBANNmeCsmERERERE5FQNMm+B7MImIiIiIyOkYYBIREREREZEhGGDaBF9TQkRERERETscA02Y4UpaIiIiIiJyKASYREREREREZggEmERERERERGYIBJhERERERERmCAabN8Fk/RERERETkVAwwbYLvwSQiIiIiIqdjgGkTfE0JERERERE5HQNMm+GFTCIiIiIicioGmERERERERGQIBphERERERERkCAaYREREREREZAjNAaYQIlUIsVII8YuZCSIiIiIiIiJn0nMF824AG81KCLnxYbJERERERORUmgJMIURXAGMAvG9ucoiIiIgomU1fvx+FZTVWJ8P2dhZVYOH2IquTYXvVdQ34dnk+JN8JGDdar2C+CuAhAK5QEwghbhVC5AghcgoLC41IW1JKlteU3DJxGT6cv9PqZBAREZGNVNU24NZPl+P6D5ZYnRTbO+PFubjmPe6nSJ6fuhn3f7Ma87YwPomXiAGmEOJCAAVSyuXhppNSviulHCalHNa+fXvDEkiJadamAjz5ywark0FEREQ20uC5ypR3qNLilFCiOFBWDQAoq663OCXJQ8sVzJEA/k8IkQvgKwBnCiE+MzVVRERERERE5DgRA0wp5cNSyq5Syu4ArgIwW0p5nekpIyIiIqKkxLvliJyL78EkIiIiIltIlmdRECWyND0TSynnAphrSkqIiIiIiIhMwKvi8cMrmERERERElJB4VTz+GGASEREREVFC4pXL+GOASURERERECY1XMuOHASYRERER2YrkZScix2KASURERES2IHiZiUzCPov4YYBJREREREQJiX0W8ccAk4iIiIhsgUNjiZyPASYZZuO+Ujz83Rq4XKwdiIiSxTvztuPXtfusTgYREdkEA0wyzF8/zsGXS/Owt6TK6qQQEVGcPPvbJvzj8xVWJ4MSBO/BJHI+BphERERERJSQOK4u/hhgkuF4/wQRERHFQjIsIHIsBphEREREZAuCz/wkgzFHxR8DTCIiIiIiIjIEA0yb4FAQIiIiIiJyOgaYNsOnpxERERERkVMxwLQZPiCHiIiIiMhYko3suGGAaRO8qZ2IiIjIjbEAkXMxwCTDcHgvEREREdmRYEM1bhhgEhERERERkSEYYJLhOKyFiIiIosGn6hM5HwNMm2CBSkRERERkDj7kJ34YYNpMIgwPT4RtICIiIiLn472X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A/CilHKt57d/AHgfwDYA2wH8FrctInKIWBq5AJYB6O15YmwGgKsA/MRGLpEmqnWfEOJiIUQ+gJMATBFCTAOChqd3AjDH8/yBZXDfg/mL57d/AbhPCLEN7uHqH8RzoxKJcJdlRNETQpwC4A8AawG4PF//G+57Ub4GcCSAXQCukFIe8jy2/XbvMFkhxM2e6QHgaSnlR57vhwGYCKAJ3A3cuyQzLBER2UCYui8TwP8AtAdQDGCVlPJcz2uA3vcOk/V0Br0KIBXAh1LKpz3f94T7oT9tAKwEcJ3iic9ERLbHAJOIiIiIiIgMwSGyREREREREZAgGmERERERERGQIBphERERERERkCAaYREREREREZAgGmERERERERGQIBphERERERERkCAaYRESUVIQQrYQQ//D83VkIMdnEdd0uhPiLyvfdhRDrzFovERGRVfgeTCIiSipCiO4AfpFSHpPMaSAiIjIDr2ASEVGymQCglxBilRDiG++VRCHEjUKIH4QQM4QQuUKIO4UQ9wkhVgohFgsh2nim6yWEmCqEWC6E+EMI0S/UioQQjwshHvD8PVQIsVoIsRrAHYpp7hVCfOj5e5AQYp0QoqmZO4CIiMgsDDCJiCjZjAWwXUo5BMCDAb8dA+ASAMMBPA2gUkp5HIBFALxDXd8FcJeUciiABwC8qXG9H3nmGxzw/X8BHC2EuNgzzW1Sykp9m0RERGQPaVYngIiIyEbmSCnLAJQJIUoA/Oz5fi2AY4UQzQCcDOAbIYR3nsxICxVCtALQSkr5u+erTwGcDwBSSpcQ4kYAawC8I6VcYNC2EBERxR0DTCIiokY1ir9dis8uuOvMFADFnqufRuoNoBxAZ4OXS0REFFccIktERMmmDEDzaGaUUpYC2CmEuBwAhFvgkFe1+YoBFAshTvF8da33NyFESwCvATgVQFshxGXRpI2IiMgOGGASEVFSkVIeBLDA83CfF6JYxLUAbvE8rGc9gIs0zncTgDeEEKsACMX3rwB4Q0q5BcAtACYIITpEkS4iIiLL8TUlREREREREZAhewSQiIiIiIiJD8CE/REREMRJC/AfA5QFffyOlfNqK9BAREVmFQ2SJiIiIiIjIEBwiS0RERERERIZggElERERERESGYIBJREREREREhmCASURERERERIZggElERERERESG+H8YbnsU6K+XPwAAAABJRU5ErkJggg==\n",
+ "image/png": "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iIiIiEizJ35ejw17Sw1d5oDOLfDYnwaG/L2iogJXXHEF8vPz0dDQgEceeQRffvklfvjhBwDAjBkz8Oabb2Ly5Mm45ZZbkJOTAyEEbr75ZnTr1g05OTm49tpr0aRJEyxatAgbNmzAfffdh/LycrRr1w4TJ05Ep06dcPrpp+O4447DH3/8gYqKCnzyySd49tlnsXbtWlx55ZUYP358UNqWLVuGu+++GxUVFcjMzMSsWbPw7bff4vvvv0dJSQn27NmD6667Do899hhyc3Nx4YUXYt26dQCAF198EeXl5UFXLidOnIicnBy8/vrrAIALL7wQDzzwAEaNGhW0fffeey+2b9+OO+64A4WFhWjatCnee+899OvXDzfeeCOysrKwcuVKjBw5Ei+//HJMx4kBpo0crqhF6+wMq5NBREREROQ4U6dORefOnTFlyhQAQElJCR577DEUFhaiffv2+Oijj3DzzTdj1apV2LNnjy+AKy4uRqtWrfD666/jxRdfxLBhw1BXV4e77roLP/74I9q3b49JkybhP//5Dz788EMAQEZGBnJycvDf//4XF110EZYvX442bdqgV69euPfee9G2bVtfumpra3HllVdi0qRJGD58OEpLS9GkSRMAwNKlS7Fu3To0bdoUw4cPx5gxY9CuXbuY9oPa9gHArbfeirfffhu9e/fGkiVL8I9//AOzZ88G4H7FzMKFC5GamhrTugEGmLbx46o9uPurVfjhjpEY0q2V1ckhIiIiIopauCuNZhk0aBDuv/9+/Otf/8KFF16IUaNG4frrr8dnn32Gm266CYsWLcInn3yCsrIy7NixA3fddRfGjBmDc845J2hZmzdvxrp163D22WcDABoaGtCpUyff7//3f//nW+fAgQN9v/Xs2RN5eXl+AebmzZvRqVMnDB8+HADQokUL329nn322b9pLLrkE8+fPx5///OeY9kPPnj2Dtq+8vBwLFy7E5Zdf7puupqbG9/fll19uSHAJMMC0jUXbDwIANu4rZYBJRERERKRTnz59sGLFCvz6668YN24czjrrLPz1r3/Fn/70J2RlZeHyyy9HWloaWrdujdWrV2PatGl4++238fXXX/uuTHpJKTFw4EAsWrRIdV2ZmZkAgJSUFN/f3s967mMMfA2IEAJpaWlwuVy+70K9hzLUdGrb9+qrr6JVq1ZYtWqV6rKys7M1pzkSPuSHiIiIiIgcb+/evWjatCmuu+46PPjgg1ixYgU6d+6Mzp07Y/z48bjpppsAAEVFRXC5XLj00ksxfvx4rFixAgDQvHlzlJWVAQD69u2LwsJCX4BZV1eH9evXR5Wuvn37Yt++fVi2bBkAoKyszBeEzpgxA4cOHUJVVRV++OEHjBw5Eh07dkRBQQEOHjyImpoa/PLLL6rL7d69O1atWgWXy4W8vDwsXbo05Pa1aNECPXr0wDfffAPAHUCvXr06qu2JhFcwiYiIiIjI8dauXYsHH3wQKSkpSE9Px1tvvQUAuPbaa1FYWIj+/fsDAPbs2YObbrrJd/Xv2WefBQDceOONuP32230P+Zk8eTL++c9/oqSkBPX19bjnnnswcKD2ob8XXHAB3n//fXTu3BmTJk3CXXfdhaqqKjRp0gQzZ84EAIwYMQKXXnop8vPzcd1112HYsGEAgEcffRQjRoxAly5d0K9fP9Xljxw5Ej169MCAAQPQv39/HH/88WG37/PPP8ff//53jB8/HnV1dbjqqqswePBgXftYCyGlNHyhw4YNkzk5OYYvN5GN/XYNvlqWh2cvGYSrRxxpdXKIiIiIiHTZuHGjL4izkzvvvBPHHXccbrnlFquT4ifwKbBa58nNzY3ruzDVjqsQYrmUcpja9LyCSURERERECWno0KHIzs7GSy+9ZHVSkgYDTCIiIiIiSkjLly+3Ogkh3Xjjjbjxxht1zTNkyBB0797dlPQYhQGmTZgwUpmIiIiIKK6klEFPRiXjDBkyJK7ri+Z2Sj5FloiIiIiIYpaVlYWDBw9GFZSQ/UgpcfDgQWRlZemaj1cwbYIdPURERETkZF27dkV+fj4KCwutTgoZJCsrC127dtU1DwNMIiIiIiKKWXp6Onr06GF1MshiHCJLREREREREhmCASURERERERIZggElERERERESGYIBpE3zYFhEREREROR0DTJvhw2SJiIiIiMipGGDaDC9kEhERERGRUzHAtAm+B5OIiIiIiJyOASYREREREREZggEmERERERERGYIBJhERERERERmCAaZN8DUlRERERETkdAwwbYbP+iEiIiIiIqdigGkzvJBJREREREROxQDTJviaEiIiIiIicjoGmERERERERGQIBphERERERERkCAaYNsGnyBIRERERkdMxwLQZ3opJREREREROxQCTiIiIiIiIDMEAk4iIiIiIiAzBANNmeCsmERERERE5FQNMm+B7MImIiIiIyOkYYBIREREREZEhGGDaBF9TQkRERERETscA02Y4UpaIiIiIiJyKASYREREREREZggEmERERERERGYIBJhERERERERmCAabN8Fk/RERERETkVAwwbYLvwSQiIiIiIqdjgGkTfE0JERERERE5HQNMm+GFTCIiIiIicioGmERERERERGQIBphERERERERkCAaYREREREREZAjNAaYQIlUIsVII8YuZCSIiIiIiIiJn0nMF824AG81KCLnxYbJERERERORUmgJMIURXAGMAvG9ucoiIiIgomU1fvx+FZTVWJ8P2dhZVYOH2IquTYXvVdQ34dnk+JN8JGDdar2C+CuAhAK5QEwghbhVC5AghcgoLC41IW1JKlteU3DJxGT6cv9PqZBAREZGNVNU24NZPl+P6D5ZYnRTbO+PFubjmPe6nSJ6fuhn3f7Ma87YwPomXiAGmEOJCAAVSyuXhppNSviulHCalHNa+fXvDEkiJadamAjz5ywark0FEREQ20uC5ypR3qNLilFCiOFBWDQAoq663OCXJQ8sVzJEA/k8IkQvgKwBnCiE+MzVVRERERERE5DgRA0wp5cNSyq5Syu4ArgIwW0p5nekpIyIiIqKkxLvliJyL78EkIiIiIltIlmdRECWyND0TSynnAphrSkqIiIiIiIhMwKvi8cMrmERERERElJB4VTz+GGASEREREVFC4pXL+GOASURERERECY1XMuOHASYRERER2YrkZScix2KASURERES2IHiZiUzCPov4YYBJREREREQJiX0W8ccAk4iIiIhsgUNjiZyPASYZZuO+Ujz83Rq4XKwdiIiSxTvztuPXtfusTgYREdkEA0wyzF8/zsGXS/Owt6TK6qQQEVGcPPvbJvzj8xVWJ4MSBO/BJHI+BphERERERJSQOK4u/hhgkuF4/wQRERHFQjIsIHIsBphEREREZAuCz/wkgzFHxR8DTCIiIiIiIjIEA0yb4FAQIiIiIiJyOgaYNsOnpxERERERkVMxwLQZPiCHiIiIiMhYko3suGGAaRO8qZ2IiIjIjbEAkXMxwCTDcHgvEREREdmRYEM1bhhgEhERERERkSEYYJLhOKyFiIiIosGn6hM5HwNMm2CBSkRERERkDj7kJ34YYNpMIgwPT4RtICIiIiLn472X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A/CilHKt57d/AHgfwDYA2wH8FrctInKIWBq5AJYB6O15YmwGgKsA/MRGLpEmqnWfEOJiIUQ+gJMATBFCTAOChqd3AjDH8/yBZXDfg/mL57d/AbhPCLEN7uHqH8RzoxKJcJdlRNETQpwC4A8AawG4PF//G+57Ub4GcCSAXQCukFIe8jy2/XbvMFkhxM2e6QHgaSnlR57vhwGYCKAJ3A3cuyQzLBER2UCYui8TwP8AtAdQDGCVlPJcz2uA3vcOk/V0Br0KIBXAh1LKpz3f94T7oT9tAKwEcJ3iic9ERLbHAJOIiIiIiIgMwSGyREREREREZAgGmERERERERGQIBphERERERERkCAaYREREREREZAgGmERERERERGQIBphERERERERkCAaYRESUVIQQrYQQ//D83VkIMdnEdd0uhPiLyvfdhRDrzFovERGRVfgeTCIiSipCiO4AfpFSHpPMaSAiIjIDr2ASEVGymQCglxBilRDiG++VRCHEjUKIH4QQM4QQuUKIO4UQ9wkhVgohFgsh2nim6yWEmCqEWC6E+EMI0S/UioQQjwshHvD8PVQIsVoIsRrAHYpp7hVCfOj5e5AQYp0QoqmZO4CIiMgsDDCJiCjZjAWwXUo5BMCDAb8dA+ASAMMBPA2gUkp5HIBFALxDXd8FcJeUciiABwC8qXG9H3nmGxzw/X8BHC2EuNgzzW1Sykp9m0RERGQPaVYngIiIyEbmSCnLAJQJIUoA/Oz5fi2AY4UQzQCcDOAbIYR3nsxICxVCtALQSkr5u+erTwGcDwBSSpcQ4kYAawC8I6VcYNC2EBERxR0DTCIiokY1ir9dis8uuOvMFADFnqufRuoNoBxAZ4OXS0REFFccIktERMmmDEDzaGaUUpYC2CmEuBwAhFvgkFe1+YoBFAshTvF8da33NyFESwCvATgVQFshxGXRpI2IiMgOGGASEVFSkVIeBLDA83CfF6JYxLUAbvE8rGc9gIs0zncTgDeEEKsACMX3rwB4Q0q5BcAtACYIITpEkS4iIiLL8TUlREREREREZAhewSQiIiIiIiJD8CE/REREMRJC/AfA5QFffyOlfNqK9BAREVmFQ2SJiIiIiIjIEBwiS0RERERERIZggElERERERESGYIBJREREREREhmCASURERERERIZggElERERERESG+H8YbnsU6K+XPwAAAABJRU5ErkJggg==",
"text/plain": [
"<Figure size 1152x432 with 1 Axes>"
]
@@ -352,10 +352,11 @@
]
},
{
+ "attachments": {},
"cell_type": "markdown",
"metadata": {},
"source": [
- "Below we will pick the last `n_tail_anomalous` observations and mess them up in some random but noticable way. In this case we randomly shuffle the data and then multiply each observation by some integer randomly chosen from `integers_to_pick_randomly`"
+ "Below we will pick the last `n_tail_anomalous` observations and mess them up in some random but noticeable way. In this case we randomly shuffle the data and then multiply each observation by some integer randomly chosen from `integers_to_pick_randomly`"
]
},
{
@@ -408,7 +409,7 @@
"outputs": [
{
"data": {
- "image/png": "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5Zdfrscee6xO2ubOnavf/e53KisrU2Zmpr799lt9/PHH+vTTT1VUVKQtW7bo6quv1kMPPaT8/HyNGTNGS5culSQ9/fTTKi0trVPS+frrrysvL08vvPCCJGnMmDG68847dfLJJ9fZv9tvv13r1q3Tb37zGxUWFionJ0cvv/yy+vfvr+uvv15ZWVlasGCBTjzxRP39739v/AkDAAAA0OI0q8AzWSZMmKBu3bpp3LhxkqSioiI99NBDKiwsVOfOnfXaa6/pxhtv1MKFC7VlyxZfYLdv3z61a9dOL7zwgp5++mmNGDFCNTU1uu222/T555+rc+fOev/993X//ffr1VdflSRlZGQoLy9P//jHP3ThhRdq3rx56tChgw4//HDdfvvt6tixoy9d1dXVuvzyy/X+++/rmGOOUXFxsbKzsyVJc+bM0dKlS5WTk6NjjjlGo0ePVqdOnRp1HELtnyTddNNNevHFF9W3b1/Nnj1bv/71rzVlyhRJ7uFwZsyYodTU1EZtGwAAADjQFFW4lJtllHIAdMzZrALPaCWTiTJ48GD94Q9/0N13360xY8bo5JNP1jXXXKO3335bN9xwg2bOnKk333xTJSUlWr9+vW677TaNHj1aZ599dp11rVq1SkuXLtVZZ50lSXI6neratatv+gUXXODb5qBBg3zTevfurc2bNwcEnqtWrVLXrl11zDHHSJLatGnjm3bWWWf55r344ov1ww8/6KKLLmrUcejdu3ed/SstLdWMGTN06aWX+uarqqry/X3ppZcSdAIAAAD1tLvMqVHPFupXJ7XSrafmJjs5CdesAs9kOeKIIzR//nyNHz9ef/rTn3TGGWfoF7/4hX7yk58oKytLl156qdLS0tS+fXstWrRIEydO1IsvvqgPPvjAV5LpZa3VoEGDNHPmzJDbyszMlCSlpKT4/vZ+rk87yeDhSowxSktLk8vl8n0XbhzNcPOF2r9nn31W7dq108KFC0Ouq1WrVjGnGQAAAIDb7jJ3fvzbVVUHROBJ50KStm7dqpycHF199dW66667NH/+fHXr1k3dunXTY489phtuuEGStGvXLrlcLl1yySV67LHHNH/+fElSbm6uSkpKJEn9+vVTYWGhL/CsqanRsmXLGpSufv36adu2bZo7d64kqaSkxBecTpo0SXv27FFFRYU+++wznXjiierSpYt27typ3bt3q6qqSl999VXI9fbs2VMLFy6Uy+XS5s2bNWfOnLD716ZNG/Xq1UsffvihJHdgvWjRogbtDwAAAIADEyWekpYsWaK77rpLKSkpSk9P17///W9J0lVXXaXCwkINGDBAkrRlyxbdcMMNvtLCJ554QpJ0/fXX6+abb/Z1LvTRRx/pt7/9rYqKiuRwOPT73/9egwbFXo34/PPP1yuvvKJu3brp/fff12233aaKigplZ2dr8uTJkqSRI0fqkksuUUFBga6++mqNGDFCkvTggw9q5MiR6t69u/r37x9y/SeeeKJ69eqlgQMHasCAATr66KMj7t8777yjW265RY899phqamr0f//3fxoyZEi9jjEAAACAA5ex1jbZxkaMGGHz8vICvluxYoUvsGtubr31Vg0bNkw///nPk52UAMG90sa6TH5+fpOO5dmczy0AAACQSCUl8yJOX72zRpe8vFt9Oqfp05sa10loc9KmzYh51toRwd9T4hnG8OHD1apVK/3tb39LdlIAAAAAYL9G4BnGvHmR31Ak0/XXX6/rr7++XssMHTpUPXv2TEh6AAAAACASAs8DxNChQ5OdBAAAAAAHKHq1BQAAAAAkFIEnAAAAACChCDwBAAAAAAnV7Np4FhXNksOxL27rS0trp7Ztj4vb+hJp1KhRevrpp31jcibS9ddfrzFjxuhnP/tZ2Hlef/11nX322erWrVvC0wMAAACg5Wp2gafDsU8ZGZ3jtr7q6sK4retA8/rrr+vII48k8AQAAADQKFS1lXTRRRdp+PDhGjRokF566SXf961bt9b999+vIUOG6LjjjtOOHTskSfn5+Tr99NN11FFH6YwzztCmTZskuUsRb7nlFh133HHq3bu3pk2bphtvvFEDBgwIGP7klltu0YgRIzRo0CA99NBDIdP03nvvafDgwTryyCN19913B6TJ66OPPvKt98MPP9SRRx6pIUOG6JRTTqmzPmutbr31VvXr109nnnmmdu7c6Zv26KOP6phjjtGRRx6pm266SdZaffTRR8rLy9NVV12loUOHqqKiIuR8AAAAABANgaekV199VfPmzVNeXp6ee+457d69W5JUVlam4447TosWLdIpp5yil19+WZJ022236brrrtPixYt11VVX6be//a1vXXv37tXMmTP1zDPP6IILLtDtt9+uZcuWacmSJVq4cKEk6fHHH1deXp4WL16s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\n",
+ "image/png": "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5Zdfrscee6xO2ubOnavf/e53KisrU2Zmpr799lt9/PHH+vTTT1VUVKQtW7bo6quv1kMPPaT8/HyNGTNGS5culSQ9/fTTKi0trVPS+frrrysvL08vvPCCJGnMmDG68847dfLJJ9fZv9tvv13r1q3Tb37zGxUWFionJ0cvv/yy+vfvr+uvv15ZWVlasGCBTjzxRP39739v/AkDAAAA0OI0q8AzWSZMmKBu3bpp3LhxkqSioiI99NBDKiwsVOfOnfXaa6/pxhtv1MKFC7VlyxZfYLdv3z61a9dOL7zwgp5++mmNGDFCNTU1uu222/T555+rc+fOev/993X//ffr1VdflSRlZGQoLy9P//jHP3ThhRdq3rx56tChgw4//HDdfvvt6tixoy9d1dXVuvzyy/X+++/rmGOOUXFxsbKzsyVJc+bM0dKlS5WTk6NjjjlGo0ePVqdOnRp1HELtnyTddNNNevHFF9W3b1/Nnj1bv/71rzVlyhRJ7uFwZsyYodTU1EZtGwAAADjQFFW4lJtllHIAdMzZrALPaCWTiTJ48GD94Q9/0N13360xY8bo5JNP1jXXXKO3335bN9xwg2bOnKk333xTJSUlWr9+vW677TaNHj1aZ599dp11rVq1SkuXLtVZZ50lSXI6neratatv+gUXXODb5qBBg3zTevfurc2bNwcEnqtWrVLXrl11zDHHSJLatGnjm3bWWWf55r344ov1ww8/6KKLLmrUcejdu3ed/SstLdWMGTN06aWX+uarqqry/X3ppZcSdAIAAAD1tLvMqVHPFupXJ7XSrafmJjs5CdesAs9kOeKIIzR//nyNHz9ef/rTn3TGGWfoF7/4hX7yk58oKytLl156qdLS0tS+fXstWrRIEydO1IsvvqgPPvjAV5LpZa3VoEGDNHPmzJDbyszMlCSlpKT4/vZ+rk87yeDhSowxSktLk8vl8n0XbhzNcPOF2r9nn31W7dq108KFC0Ouq1WrVjGnGQAAAIDb7jJ3fvzbVVUHROBJ50KStm7dqpycHF199dW66667NH/+fHXr1k3dunXTY489phtuuEGStGvXLrlcLl1yySV67LHHNH/+fElSbm6uSkpKJEn9+vVTYWGhL/CsqanRsmXLGpSufv36adu2bZo7d64kqaSkxBecTpo0SXv27FFFRYU+++wznXjiierSpYt27typ3bt3q6qqSl999VXI9fbs2VMLFy6Uy+XS5s2bNWfOnLD716ZNG/Xq1UsffvihJHdgvWjRogbtDwAAAIADEyWekpYsWaK77rpLKSkpSk9P17///W9J0lVXXaXCwkINGDBAkrRlyxbdcMMNvtLCJ554QpJ0/fXX6+abb/Z1LvTRRx/pt7/9rYqKiuRwOPT73/9egwbFXo34/PPP1yuvvKJu3brp/fff12233aaKigplZ2dr8uTJkqSRI0fqkksuUUFBga6++mqNGDFCkvTggw9q5MiR6t69u/r37x9y/SeeeKJ69eqlgQMHasCAATr66KMj7t8777yjW265RY899phqamr0f//3fxoyZEi9jjEAAACAA5ex1jbZxkaMGGHz8vICvluxYoUvsGtubr31Vg0bNkw///nPk52UAMG90sa6TH5+fpOO5dmczy0AAACQSCUl8yJOX72zRpe8vFt9Oqfp05sa10loc9KmzYh51toRwd9T4hnG8OHD1apVK/3tb39LdlIAAAAAYL9G4BnGvHmR31Ak0/XXX6/rr7++XssMHTpUPXv2TEh6AAAAACASAs8DxNChQ5OdBAAAAAAHKHq1BQAAAAAkFIEnAAAAACChCDwBAAAAAAnV7Np4FhXNksOxL27rS0trp7Ztj4vb+hJp1KhRevrpp31jcibS9ddfrzFjxuhnP/tZ2Hlef/11nX322erWrVvC0wMAAACg5Wp2gafDsU8ZGZ3jtr7q6sK4retA8/rrr+vII48k8AQAAADQKFS1lXTRRRdp+PDhGjRokF566SXf961bt9b999+vIUOG6LjjjtOOHTskSfn5+Tr99NN11FFH6YwzztCmTZskuUsRb7nlFh133HHq3bu3pk2bphtvvFEDBgwIGP7klltu0YgRIzRo0CA99NBDIdP03nvvafDgwTryyCN19913B6TJ66OPPvKt98MPP9SRRx6pIUOG6JRTTqmzPmutbr31VvXr109nnnmmdu7c6Zv26KOP6phjjtGRRx6pm266SdZaffTRR8rLy9NVV12loUOHqqKiIuR8AAAAABANgaekV199VfPmzVNeXp6ee+457d69W5JUVlam4447TosWLdIpp5yil19+WZJ022236brrrtPixYt11VVX6be//a1vXXv37tXMmTP1zDPP6IILLtDtt9+uZcuWacmSJVq4cKEk6fHHH1deXp4WL16s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",
"text/plain": [
"<Figure size 1152x432 with 1 Axes>"
]
@@ -435,6 +436,7 @@
]
},
{
+ "attachments": {},
"cell_type": "markdown",
"metadata": {},
"source": [
@@ -449,7 +451,7 @@
"1. Will initialize some empty objects to use during model training and inference.\n",
"2. Will loop over every observation and run training and inference in a similar way to how the Agent would process each observation.\n",
"\n",
- "Of course the Agent implemtation is a lot more efficient and uses more efficient streaming and buffer based approaches as opposed to the fairly naive implementation below. \n",
+ "Of course the Agent implementation is a lot more efficient and uses more efficient streaming and buffer based approaches as opposed to the fairly naive implementation below. \n",
"\n",
"The idea in this notebook is to make the general approach as readable and understandable as possible."
]
@@ -551,7 +553,7 @@
" # get the existing trained cluster centers\n",
" cluster_centers = models[dim]['model'].cluster_centers_\n",
"\n",
- " # get anomaly score based on the sum of the euclidian distances between the \n",
+ " # get anomaly score based on the sum of the euclidean distances between the \n",
" # feature vector and each cluster centroid\n",
" raw_anomaly_score = np.sum(cdist(X, cluster_centers, metric='euclidean'), axis=1)[0]\n",
"\n",
@@ -630,10 +632,11 @@
]
},
{
+ "attachments": {},
"cell_type": "markdown",
"metadata": {},
"source": [
- "The hard work is now all done. The above cell has processed all the data, trained or retrained models as defined by the inital config, and saved all anomaly scores and anomaly bits.\n",
+ "The hard work is now all done. The above cell has processed all the data, trained or retrained models as defined by the initial config, and saved all anomaly scores and anomaly bits.\n",
"\n",
"The rest of the notebook will try to help make more sense of all this."
]
@@ -795,7 +798,7 @@
"outputs": [
{
"data": {
- "image/png": "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Kdi+fTt8fX0RGxuLwsJCt8rr4+MDi8Wiv9bm8/HxwdatW7FixQrMnz8fn332GVauXFnm8nbu3ImuXbsCACZNmoSFCxeid+/emD17NlavXu1wHnenIyIiIiIiIqqrpJRYebgIl3Tyh4+X8HRxKqxcY/wIIWIB9AWwBUBTNSgEAOcBNK3aotWM+fPn44477sCpU6eQkJCAM2fOoG3btli3bp3TeTp37oyEhAQcO3YMAPDDDz/gkksucXudWVlZaNKkCXx9fbFq1SqcOnXKbprhw4dj3rx5MJvNSElJwdq1azFw4EC0adMGBw4cQFFRETIzM7FixQoAyrhBWVlZuOqqq/Dhhx9i9+7dZZZjzZo1mDlzJu69914AQE5ODpo3b46SkhLMmTNHny4kJAQ5OTn6a2fTEREREREREdUXa44W4fEFmZi5vvJP3r77x3T857u0KigV8OSCDFw7I8Xt6d1+nLsQIhjAAgCPSymzhSiNdkkppRBCOpnvPgD3AcrYN2UWyCccxcXub4A7y3Nl7ty5ePZZ60SlcePGYe7cubjlllsczhMQEIBvv/0W48ePh8lkwoABA/DAAw+4XaaJEyfi2muvRc+ePREXF+fw8fE33HADNm3ahN69e0MIgXfeeQfNmjUDANx8883o0aMH2rZti759+wJQgjFjx45FYWEhpJT44IMPHK573rx5WL9+PfLz89G2bVssWLBAz/h5/fXXMWjQIERHR2PQoEF6sGfChAm499578cknn2D+/PlOpyMiIiIiIiKqL9Lzld4257MtZUxZtm2niiu9DM2yQ0Xlml7YDirscCIhfAH8DeBfKeUH6nuHAYyQUp4TQjQHsFpK2dnVcuLi4mR8fLzVewcPHtQDD1S/8LslIiIiIiKi8sjJ2V72RAY93zgPANj7QrMqL8tvu/IxdVE2bugdiNeuCavUsqqynI6WFRoat11KGedoenee6iUAfAPgoBb0Uf0J4E717zsB2D+TnIiIiIiIiIioDnIjT6ZOcKer11AAdwDYK4TYpb73PIDpAH4RQtwD4BSAm6ulhEREREREREREVCHuPNVrPQBnw1ePqopCSClhHDOI6j53uhASERERERERUfUq11O9qkNAQADS0tIYKKhHpJRIS0tDQECAp4tCREREREREVCl1PU3F7ad6VZeYmBgkJiYiJaXqnuRFnhcQEICYmBhPF4OIiIiIiIioQfN44MfX1xdt27b1dDGIiIiIiIiIiHT1pV+Sx7t6ERERERERERFR9WDgh4iIiIiIiIjIRl0f20fDwA8RERERERERkQ129SIiIiIiIiIiqudEHU/9YeCHiIiIiIiIiKieYuCHiIiIiIiIiKieYuCHiIiIiIiIiKieYuCHiIiIiIiIiMiGrCejOzPwQ0RERERERERUTzHwQ0RERERERERko64/zUvDwA8RERERERERkY360tXLx9MFICIiIiIiIiKqDzLzLfDyAkIDqj7PprBEIqvAUu75GPghIiIiIiIionrl09U5yCqw4MUrw2p0vcM/vAAA2PtCsypf9sPzMrD1VHG552NXLyIiIiIiIiKqE06nm9DzjfNYcqDA5XQzN+Rh3g7X09Q1FQn6AAz8EBEREREREVEdcTDZBABYerDQwyWpOxj4ISIiIiIiIiKyUU/Gdmbgh4iIiIiIiIjImbr+WHcGfoiIiIiIiIiInKjrj3Vn4IeIiIiIiIiI6oQ6nnzjEQz8EBERERERERE5wa5eREREREREREQ1qK53v6pJDPwQEREREREREdmqJ8ElBn6IiIiIiIiIiOopBn6IiIiIiIiIqE6o0fF26vjYPhoGfoiIiIiIiIiIbLGrFxERERERERFR/VbXE38Y+CEiIiIiIiKiOqWeJOPUCAZ+iIiIiIiIiIjqKQZ+iIiIiIiIiIjqKQZ+iIiIiIiIiKhOqMnxdupLdzIGfoiIiIiIiIiI6ikGfoiIiIiIiIioTpE1kI5T15/mpSkz8COEmCWEuCCE2Gd47xUhRJIQYpf676rqLSYRERERERERUc1pSF29ZgMY4+D9D6WUfdR/i6u2WERERERERERE7ikxSyzYmQ9LNaQCiUqk/uxJKsa+syVVV5gKKDPwI6VcCyC9BspCRERERERERGQlM9+CRfsKAFh3vzqXZdb//m5zHl5ZnI2+byXXSJksUmL+znyUmF0HmibOTset36bprzeeKMKJVFN1F89KZcb4eUQIsUftChZRZSUiIiIiIiIiIlI9/XsmpvyRhaRM64DJzd+k6n9nFFgAAJYa6p/1995CvLo4G99szCvXfPfPzcDYL1PLnrAKVTTw8wWA9gD6ADgH4H1nEwoh7hNCxAsh4lNSUiq4OiIiIiIiIiJqiJKzlcyeIptEmcwCz43Ck11oUctg8VgZ3FWhwI+UMllKaZZSWgB8BWCgi2lnSinjpJRx0dHRFS0nERERERERETVEDsbYqYmQT008OawmVCjwI4Robnh5A4B9zqYlIiIiIiIiIqpO1RmkqeuPdfcpawIhxFwAIwBECSESAUwFMEII0QdKkC0BwP3VV0QiIiIiIiIiIs+o64k/ZQZ+pJS3Onj7m2ooCxERERERERFRrVeXgkGVeaoXEREREREREVG1cqerVXUGYup6Vy8GfoiIiIiIiIioblCjMJ4eeLkuBYMY+CEiIiIiIiKiWq+mgz3SRR4Ru3oREREREREREVUBUYvTa2px0XQM/BARERERERFRnVDbAi11IfOHgR8iIiIiIiIiqvVcdb2qVrU55cgNDPwQERERERERUa0l3Mnz8VRMyDOrLRcGfoiIiIiIiIiIKoBdvYiIiIiIiIiIyGMY+CEiIiIiIiKiWq+mH+fuDnb1IiIiIiIiIiKqBG1s5ZqO+7gTaKqFsSg7DPwQEREREREREdVTDPwQERERERERUZ2gZ//UQKqNO09xZ1cvIiIiIiIiIqIqUNNj/LCrFxERERERERFRLVAdAZjkHIvD9y1S1mgQymyp3MoY+CEiIiIiIiKiOsGdrlWHk0sqvPzcIgvOZ5uRV2TBrE15AIB52/Otprn4wwt4d3lOmeV569/sCpdDczLNhD5vJePfg4UVXoZPpUtBRERERERERFRNtLF25mzLh59P2aGfm75Oq/C6xn+dhsRMM9pGejudJqug7AycjSeK8FN8fpnTleXgeSWItfwQAz9EREREREREVI/9vrug2teRmGkGAJxMM1dqOQt2VU1ZtTBXZbqWsasXEREREREREdUptX1QZVlFgwC582SxsjDwQ0RERERERES1Vl14ZHp1q0wYiYEfIiIiIiIiIqJarDIZRAz8EBERERERERHVQqIK+nox8ENEREREREREdVoVDalTLzHwQ0RERERERES1lqOcl9oe6Kmq8vGpXkRERERERERE9ZTW04uDOxMRERERERER1RJVlZDEjB8iIiIiIiIiqteqYHzj6uOkbFXd1asyGPghIiIiIiIiIqqIGhpriF29iIiIiIiIiKhecifrpbaN9Vxl5dHG+GFXLyIiIiIiIiKiGlbN3dDY1YuIiIiIiIiIyFNqW6qRAwz8EBEREREREVGdImt5xKXKBndmVy8iIiIiIiIiIg9hVy8iIiIiIiIiokpwI/pRVRk25canehERERERERERVS2PBXrcVNVdvSqjzMCPEGKWEOKCEGKf4b3GQohlQoij6v8RlS8KEREREREREVEdUs1dvTTVPcbPbABjbN6bAmCFlLIjgBXqayIiIiIiIiKiKlVVsZXUXDPyiy1VtDTXqiohSagpP5UZzNqnrAmklGuFELE2b48FMEL9+zsAqwE8W+FSEBEREREREVGDV2KW2H+uBH1i/AAAZzJMOHDeVOHlFZsknlyQifuHNcJts9OtPpv/30h0burr1nLMFglvL8chqH1nS9C6sTe+25zndrky8y1IzjHjpq/T8OglwQgL9EJyjhmPjQjRpzmXZUZSprLtzjJ+lh4sxOdrc12uq8zAjxNNpZTn1L/PA2jqbEIhxH0A7gOA1q1bV3B1RERERERERFTfvbMsBz9vz8fC+6LQPtoHV32eWqnljfsqFQnpZpxKtw8erT9e5HbgZ/LvmfhgnP0oN7mFErd+m4aoRl5IzSvNJiqra9aEWWlIyjIDAL5YlwuTOqsx8HP5Zyllluup3zLLnKbSgztLKSVcZDFJKWdKKeOklHHR0dGVXR0RERERERER1VNHLpQAADIKytcly1lQIiHd7HSe8gycvOxQkcP3i8zKmo1BH3doQR93eeKpXslCiOYAoP5/oRJlICIiIiIiIiKialDRwM+fAO5U/74TwB9VUxwiIiIiIiIiorqtMoMxO1xedT7VSwgxF8AmAJ2FEIlCiHsATAdwmRDiKIDR6msiIiIiIiIiojpB1NSz2KtAZcJI7jzV61YnH42qxHqJiIiIiIiIiOql8mTolGesoYqo9ODORERERERERER1TXUHXKpStXb1IiIiIiIiIiKqTap2BJ2KqwuxIwZ+iIiIiIiIiKhWqUyGi7uqM2hTnuJXd/CIgR8iIiIiIiIiqttqIlJUDlVdHHb1IiIiIiIiIqIGY+OJ4sovpBpTbTaddL98HNyZiIiIiIiIiBqEaguCOFhuVayqpgaIrkwCEQM/RERERERERFTL1K6uW87UVA8zSyVWxMAPERERERERETU4VZGtUxfCUwz8EBERERERERFVRA1Ffji4MxERERERERHVeRVNwvFU5k1VrJePcyciIiIiIiIiqoVq8inyFQ0QMfBDRERERERERA1OVWTaVEnGjxuDDbGrFxERERER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eYUQogWAr7XuXmrm4UcAvAHMklK+ob7fDspgz40B7ARwu5SyqIY2i4iIPISBHyIiIiIiIiKieopj/BARERERERER1VMM/BARERERERER1VMM/BARERERERER1VMM/BARERERERER1VMM/BARERERERER1VMM/BARERERERER1VMM/BAREVGdJ4QIF0I8pP7dQggxvxrX9YAQ4j8O3o8VQuyrrvUSERERVYSQUnq6DERERESVIoSIBfC3lLJHQy4DERERkS1m/BAREVF9MB1AeyHELiHEr1rmjRBikhBioRBimRAiQQjxiBDiSSHETiHEZiFEY3W69kKIJUKI7UKIdUKILs5WJIR4RQjxtPp3fyHEbiHEbgAPG6Z5QggxS/27pxBinxAiqDp3ABEREZEjDPwQERFRfTAFwHEpZR8Ak20+6wHgRgADALwBIF9K2RfAJgBal62ZAB6VUvYH8DSAz91c77fqfL1t3v8YQAchxA3qNPdLKfPLt0lERERElefj6QIQERERVbNVUsocADlCiCwAf6nv7wXQSwgRDGAIgF+FENo8/mUtVAgRDiBcSrlWfesHAFcCgJTSIoSYBGAPgC+llBuqaFuIiIiIyoWBHyIiIqrvigx/WwyvLVDqQl4AMtVsoarUEUAugBZVvFwiIiIit7GrFxEREdUHOQBCKjKjlDIbwEkhxHgAEArbrluO5ssEkCmEGKa+NVH7TAgRBuATABcDiBRC3FSRshERERFVFgM/REREVOdJKdMAbFAHdX63AouYCOAedZDm/QDGujnfXQD+J4TYBUAY3v8QwP+klEcA3ANguhCiSQXKRURERFQpfJw7EREREREREVE9xYwfIiIiIiIiIqJ6ioM7ExERETkghHgBwHibt3+VUr7hifIQERERVQS7ehERERERERER1VPs6kVEREREREREVE8x8ENEREREREREVE8x8ENEREREREREVE8x8ENEREREREREVE/9P44ckr5e3L2eAAAAAElFTkSuQmCC\n",
+ "image/png": "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Kdi+fTt8fX0RGxuLwsJCt8rr4+MDi8Wiv9bm8/HxwdatW7FixQrMnz8fn332GVauXFnm8nbu3ImuXbsCACZNmoSFCxeid+/emD17NlavXu1wHnenIyIiIiIiIqqrpJRYebgIl3Tyh4+X8HRxKqxcY/wIIWIB9AWwBUBTNSgEAOcBNK3aotWM+fPn44477sCpU6eQkJCAM2fOoG3btli3bp3TeTp37oyEhAQcO3YMAPDDDz/gkksucXudWVlZaNKkCXx9fbFq1SqcOnXKbprhw4dj3rx5MJvNSElJwdq1azFw4EC0adMGBw4cQFFRETIzM7FixQoAyrhBWVlZuOqqq/Dhhx9i9+7dZZZjzZo1mDlzJu69914AQE5ODpo3b46SkhLMmTNHny4kJAQ5OTn6a2fTEREREREREdUXa44W4fEFmZi5vvJP3r77x3T857u0KigV8OSCDFw7I8Xt6d1+nLsQIhjAAgCPSymzhSiNdkkppRBCOpnvPgD3AcrYN2UWyCccxcXub4A7y3Nl7ty5ePZZ60SlcePGYe7cubjlllsczhMQEIBvv/0W48ePh8lkwoABA/DAAw+4XaaJEyfi2muvRc+ePREXF+fw8fE33HADNm3ahN69e0MIgXfeeQfNmjUDANx8883o0aMH2rZti759+wJQgjFjx45FYWEhpJT44IMPHK573rx5WL9+PfLz89G2bVssWLBAz/h5/fXXMWjQIERHR2PQoEF6sGfChAm499578cknn2D+/PlOpyMiIiIiIiKqL9Lzld4257MtZUxZtm2niiu9DM2yQ0Xlml7YDirscCIhfAH8DeBfKeUH6nuHAYyQUp4TQjQHsFpK2dnVcuLi4mR8fLzVewcPHtQDD1S/8LslIiIiIiKi8sjJ2V72RAY93zgPANj7QrMqL8tvu/IxdVE2bugdiNeuCavUsqqynI6WFRoat11KGedoenee6iUAfAPgoBb0Uf0J4E717zsB2D+TnIiIiIiIiIioDnIjT6ZOcKer11AAdwDYK4TYpb73PIDpAH4RQtwD4BSAm6ulhEREREREREREVCHuPNVrPQBnw1ePqopCSClhHDOI6j53uhASERERERERUfUq11O9qkNAQADS0tIYKKhHpJRIS0tDQECAp4tCREREREREVCl1PU3F7ad6VZeYmBgkJiYiJaXqnuRFnhcQEICYmBhPF4OIiIiIiIioQfN44MfX1xdt27b1dDGIiIiIiIiIiHT1pV+Sx7t6ERERERERERFR9WDgh4iIiIiIiIjIRl0f20fDwA8RERERERERkQ129SIiIiIiIiIiqudEHU/9YeCHiIiIiIiIiKieYuCHiIiIiIiIiKieYuCHiIiIiIiIiKieYuCHiIiIiIiIiMiGrCejOzPwQ0RERERERERUTzHwQ0RERERERERko64/zUvDwA8RERERERERkY360tXLx9MFICIiIiIiIiKqDzLzLfDyAkIDqj7PprBEIqvAUu75GPghIiIiIiIionrl09U5yCqw4MUrw2p0vcM/vAAA2PtCsypf9sPzMrD1VHG552NXLyIiIiIiIiKqE06nm9DzjfNYcqDA5XQzN+Rh3g7X09Q1FQn6AAz8EBEREREREVEdcTDZBABYerDQwyWpOxj4ISIiIiIiIiKyUU/Gdmbgh4iIiIiIiIjImbr+WHcGfoiIiIiIiIiInKjrj3Vn4IeIiIiIiIiI6oQ6nnzjEQz8EBERERERERE5wa5eREREREREREQ1qK53v6pJDPwQEREREREREdmqJ8ElBn6IiIiIiIiIiOopBn6IiIiIiIiIqE6o0fF26vjYPhoGfoiIiIiIiIiIbLGrFxERERERERFR/VbXE38Y+CEiIiIiIiKiOqWeJOPUCAZ+iIiIiIiIiIjqKQZ+iIiIiIiIiIjqKQZ+iIiIiIiIiKhOqMnxdupLdzIGfoiIiIiIiIiI6ikGfoiIiIiIiIioTpE1kI5T15/mpSkz8COEmCWEuCCE2Gd47xUhRJIQYpf676rqLSYRERERERERUc1pSF29ZgMY4+D9D6WUfdR/i6u2WERERERERERE7ikxSyzYmQ9LNaQCiUqk/uxJKsa+syVVV5gKKDPwI6VcCyC9BspCRERERERERGQlM9+CRfsKAFh3vzqXZdb//m5zHl5ZnI2+byXXSJksUmL+znyUmF0HmibOTset36bprzeeKMKJVFN1F89KZcb4eUQIsUftChZRZSUiIiIiIiIiIlI9/XsmpvyRhaRM64DJzd+k6n9nFFgAAJYa6p/1995CvLo4G99szCvXfPfPzcDYL1PLnrAKVTTw8wWA9gD6ADgH4H1nEwoh7hNCxAsh4lNSUiq4OiIiIiIiIiJqiJKzlcyeIptEmcwCz43Ck11oUctg8VgZ3FWhwI+UMllKaZZSWgB8BWCgi2lnSinjpJRx0dHRFS0nERERERERETVEDsbYqYmQT008OawmVCjwI4Robnh5A4B9zqYlIiIiIiIiIqpO1RmkqeuPdfcpawIhxFwAIwBECSESAUwFMEII0QdKkC0BwP3VV0QiIiIiIiIiIs+o64k/ZQZ+pJS3Onj7m2ooCxERERERERFRrVeXgkGVeaoXEREREREREVG1cqerVXUGYup6Vy8GfoiIiIiIiIioblCjMJ4eeLkuBYMY+CEiIiIiIiKiWq+mgz3SRR4Ru3oREREREREREVUBUYvTa2px0XQM/BARERERERFRnVDbAi11IfOHgR8iIiIiIiIiqvVcdb2qVrU55cgNDPwQERERERERUa0l3Mnz8VRMyDOrLRcGfoiIiIiIiIiIKoBdvYiIiIiIiIiIyGMY+CEiIiIiIiKiWq+mH+fuDnb1IiIiIiIiIiKqBG1s5ZqO+7gTaKqFsSg7DPwQEREREREREdVTDPwQERERERERUZ2gZ//UQKqNO09xZ1cvIiIiIiIiIqIqUNNj/LCrFxERERERERFRLVAdAZjkHIvD9y1S1mgQymyp3MoY+CEiIiIiIiKiOsGdrlWHk0sqvPzcIgvOZ5uRV2TBrE15AIB52/Otprn4wwt4d3lOmeV569/sCpdDczLNhD5vJePfg4UVXoZPpUtBRERERERERFRNtLF25mzLh59P2aGfm75Oq/C6xn+dhsRMM9pGejudJqug7AycjSeK8FN8fpnTleXgeSWItfwQAz9EREREREREVI/9vrug2teRmGkGAJxMM1dqOQt2VU1ZtTBXZbqWsasXEREREREREdUptX1QZVlFgwC582SxsjDwQ0RERERERES1Vl14ZHp1q0wYiYEfIiIiIiIiIqJarDIZRAz8EBERERERERHVQqIK+nox8ENEREREREREdVoVDalTLzHwQ0RERERERES1lqOcl9oe6Kmq8vGpXkRERERERERE9ZTW04uDOxMRERERERER1RJVlZDEjB8iIiIiIiIiqteqYHzj6uOkbFXd1asyGPghIiIiIiIiIqqIGhpriF29iIiIiIiIiKhecifrpbaN9Vxl5dHG+GFXLyIiIiIiIiKiGlbN3dDY1YuIiIiIiIiIyFNqW6qRAwz8EBEREREREVGdImt5xKXKBndmVy8iIiIiIiIiIg9hVy8iIiIiIiIiokpwI/pRVRk25canehERERERERERVS2PBXrcVNVdvSqjzMCPEGKWEOKCEGKf4b3GQohlQoij6v8RlS8KEREREREREVEdUs1dvTTVPcbPbABjbN6bAmCFlLIjgBXqayIiIiIiIiKiKlVVsZXUXDPyiy1VtDTXqiohSagpP5UZzNqnrAmklGuFELE2b48FMEL9+zsAqwE8W+FSEBEREREREVGDV2KW2H+uBH1i/AAAZzJMOHDeVOHlFZsknlyQifuHNcJts9OtPpv/30h0burr1nLMFglvL8chqH1nS9C6sTe+25zndrky8y1IzjHjpq/T8OglwQgL9EJyjhmPjQjRpzmXZUZSprLtzjJ+lh4sxOdrc12uq8zAjxNNpZTn1L/PA2jqbEIhxH0A7gOA1q1bV3B1RERERERERFTfvbMsBz9vz8fC+6LQPtoHV32eWqnljfsqFQnpZpxKtw8erT9e5HbgZ/LvmfhgnP0oN7mFErd+m4aoRl5IzSvNJiqra9aEWWlIyjIDAL5YlwuTOqsx8HP5Zyllluup3zLLnKbSgztLKSVcZDFJKWdKKeOklHHR0dGVXR0RERERERER1VNHLpQAADIKytcly1lQIiHd7HSe8gycvOxQkcP3i8zKmo1BH3doQR93eeKpXslCiOYAoP5/oRJlICIiIiIiIiKialDRwM+fAO5U/74TwB9VUxwiIiIiIiIiorqtMoMxO1xedT7VSwgxF8AmAJ2FEIlCiHsATAdwmRDiKIDR6msiIiIiIiIiojpB1NSz2KtAZcJI7jzV61YnH42qxHqJiIiIiIiIiOql8mTolGesoYqo9ODORERERERERER1TXUHXKpStXb1IiIiIiIiIiKqTap2BJ2KqwuxIwZ+iIiIiIiIiKhWqUyGi7uqM2hTnuJXd/CIgR8iIiIiIiIiqttqIlJUDlVdHHb1IiIiIiIiIqIGY+OJ4sovpBpTbTaddL98HNyZiIiIiIiIiBqEaguCOFhuVayqpgaIrkwCEQM/RERERERERFTL1K6uW87UVA8zSyVWxMAPERERERERETU4VZGtUxfCUwz8EBERERERERFVRA1Ffji4MxERERERERHVeRVNwvFU5k1VrJePcyciIiIiIiIiqoVq8inyFQ0QMfBDRERERERERA1OVWTaVEnGjxuDDbGrFxERERER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eYUQogWAr7XuXmrm4UcAvAHMklK+ob7fDspgz40B7ARwu5SyqIY2i4iIPISBHyIiIiIiIiKieopj/BARERERERER1VMM/BARERERERER1VMM/BARERERERER1VMM/BARERERERER1VMM/BARERERERER1VMM/BARERERERER1VMM/BAREVGdJ4QIF0I8pP7dQggxvxrX9YAQ4j8O3o8VQuyrrvUSERERVYSQUnq6DERERESVIoSIBfC3lLJHQy4DERERkS1m/BAREVF9MB1AeyHELiHEr1rmjRBikhBioRBimRAiQQjxiBDiSSHETiHEZiFEY3W69kKIJUKI7UKIdUKILs5WJIR4RQjxtPp3fyHEbiHEbgAPG6Z5QggxS/27pxBinxAiqDp3ABEREZEjDPwQERFRfTAFwHEpZR8Ak20+6wHgRgADALwBIF9K2RfAJgBal62ZAB6VUvYH8DSAz91c77fqfL1t3v8YQAchxA3qNPdLKfPLt0lERERElefj6QIQERERVbNVUsocADlCiCwAf6nv7wXQSwgRDGAIgF+FENo8/mUtVAgRDiBcSrlWfesHAFcCgJTSIoSYBGAPgC+llBuqaFuIiIiIyoWBHyIiIqrvigx/WwyvLVDqQl4AMtVsoarUEUAugBZVvFwiIiIit7GrFxEREdUHOQBCKjKjlDIbwEkhxHgAEArbrluO5ssEkCmEGKa+NVH7TAgRBuATABcDiBRC3FSRshERERFVFgM/REREVOdJKdMAbFAHdX63AouYCOAedZDm/QDGujnfXQD+J4TYBUAY3v8QwP+klEcA3ANguhCiSQXKRURERFQpfJw7EREREREREVE9xYwfIiIiIiIiIqJ6ioM7ExERETkghHgBwHibt3+VUr7hifIQERERVQS7ehERERERERER1VPs6kVEREREREREVE8x8ENEREREREREVE8x8ENEREREREREVE8x8ENEREREREREVE/9P44ckr5e3L2eAAAAAElFTkSuQmCC",
"text/plain": [
"<Figure size 1440x288 with 1 Axes>"
]
@@ -807,7 +810,7 @@
},
{
"data": {
- "image/png": "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DjtM8+fP18SJE/Xwww/rlFNO0U033aRzzz1XaWlpuvTSS5WUlKQWLVpo0aJF+vbbb/Xmm2/q448/DquusNaqZ8+emjVrVsRrpaamSpISEhICt/33y9N7xRgTdj8pKUlerzfwWEFBQcRjo+0X6fm9+OKLat68uRYuXBjxXI0aNYp7zH5///vfddJJJ+mLL77Q+vXrNWTIkMA292uSmJhY5muSnJwceC3cr6n79fzPf/6jdu3aadGiRfJ6vUpLC28L1bBhQ5122mkaP368Pv74Y82bNy/qNUeMGKGhQ4dq4sSJGjhwoL799tu4n7ub/7Ur63sGAAAAAICKiqeh8qmSfrfWpltriyV9LmlAGcfUOVu3blXDhg119dVX67777tP8+fPVoUMHdejQQU888YSuv/56SdKuXbvk9Xp18cUX64knntD8+fMlSU2aNFF2drYk6fDDD1d6enrgjXpxcbGWLVtWoXEdfvjh2rZtm+bMmSNJys7ODgQWkydP1u7du5Wfn69x48Zp4MCBateunXbu3KmMjAwVFhbq66+/jnjeTp06aeHChfJ6vdq0aZN+/fXXqM+vadOm6ty5sz755BNJviBi0aJFFXo+fllZWTrwwAMl+fr/lKV///6aOnWqdu3aJY/HozFjxgSqYeK9Xvv27ZWQkKAPPvhAHo8n4n433XST/vKXv6hfv35q0aJF1POtW7dOvXr10v33369+/fpp5cqVOu200zRy5Ejl5eVJknbv3q1mzZqpRYsWmj59uiTpgw8+iDjuqvyeAQAAAADALZ6eOxslHW+MaSjftKxTJM2NfUjds2TJEt13331KSEhQcnKy3njjDUm+6T/p6ek64ogjJElbtmzR9ddfH6h6efrppyVJw4cP16233hpoqPzpp5/qL3/5i7KyslRSUqK77rpLPXvGP93s7LPP1n//+1916NBBY8eO1R133KH8/Hw1aNBAU6ZMkeQLPC6++GJt3rxZV199tfr27StJeuSRR9S/f38deOCB6t69e8TzDxw4UJ07d1aPHj10xBFH6Jhjjon5/EaPHq3bbrtNTzzxhIqLi3XFFVdE7UcTj7/97W+67rrr9MQTT2jo0KFl7t++fXs988wzOumkkwINlc8///y4r/enP/1JF198sd5//32deeaZUauNjj32WDVt2jQQ5kXz4osv6scff1RCQoJ69uyps846S6mpqVq4cKH69u2rlJQUnX322Xrqqaf03nvvBRoqd+nSRSNHjgw7X0pKSqW/ZwAAAAAAiMRYW/YMKmPM45Iul1QiaYGkm6y1hdH279u3rw1tZrtixYpAgFKX3H777Tr66KN144031vZQgoSudhXvMevXrw9bOQultm7dqiFDhmjlypVKSIincK3m1dWfFQAAAACoKtnZ0dtkRNLrye2SpCUPHVAdw6k3mjbtO89a2zf08bje3VprH7XWdrfWHmmtvSZWsFOfHHvssVq8eLGuvvrq2h4KasD777+v4447Tk8++WSdDXYAAAAAACiveJdC3yfFaqhb24YPHx5zNadI+vTpo06dOlXLeCRp5MiReumll4IeW7Nmjbp16xb02MCBA/Xaa69V2zgq6tprr9W1114b9Fik51RXxw8AAAAAQCT7dbizr+nTp0+1nv/6668vs1dNfbMvPicAAAAAwP6FuSkAAAAAAAD1GOEOAAAAAACoF+JZFGp/RLgDAAAAAADqBaKdyGqt505W1myVlGRW2fmSkpqrWbPjy9xv3LhxuvDCC7VixQp17969yq5fXo0bN1ZOTk61nHvUqFG677771LFjR+Xk5KhLly569NFHNWDAgJjHjRs3Tocddph69OhRLeMCAAAAAABVr9Yqd0pKMpWS0qbK/os3KBozZoxOPPFEjRkzpnqfYC27/PLLtWDBAq1Zs0YjRozQRRddpBUrVsQ8Zty4cVq+fHkNjRAAAAAAgPJhVlZk+9W0rJycHM2YMUPvvPOOPvroo8DjP/30k4YMGaJLLrlE3bt317BhwwLz+L7//nsdffTR6tWrl2644QYVFhZKkjp16qQHHnhAffr0Ud++fTV//nydccYZ6tq1q958883A9U455RQdc8wx6tWrl8aPHx82Jmut7rvvPh155JHq1auXxo4dGxjTOeecE9jv9ttv16hRoyRJI0aMUI8ePXTUUUfp3nvvLfN5n3TSSbrlllv01ltvSZLefvtt9evXT71799bFF1+svLw8zZw5U19++aXuu+8+9enTR+vWrYu4HwAAAAAAtYVsJ7L9KtwZP368zjzzTB122GFq1aqV5s2bF9i2YMECvfjii1q+fLl+++03/fzzzyooKNDw4cM1duxYLVmyRCUlJXrjjTcCxxx88MFauHChBg0apOHDh+vTTz/V7Nmz9eijj0qS0tLS9MUXX2j+/Pn68ccf9de//jWs+dPnn3+uhQsXatGiRZoyZYruu+8+bdu2LepzyMjI0BdffKFly5Zp8eLFevjhh+N67sccc4xWrlwpSbrooos0Z84cLVq0SEcccYTeeecdDRgwQOedd56ef/55LVy4UF27do24HwAAAAAAtYXKncj2q3BnzJgxuuKKKyRJV1xxRdDUrP79+6tjx45KSEhQnz59tH79eq1atUqdO3fWYYcdJkm67rrrNG3atMAx5513niSpV69eOu6449SkSRO1adNGqampyszMlLVWDz74oI466iideuqp2rJli3bs2BE0phkzZujKK69UYmKi2rVrp8GDB2vOnDlRn0OzZs2UlpamG2+8UZ9//rkaNmwY13N3h0pLly7VoEGD1KtXL40ePVrLli2LeEy8+wEAAAAAgNpTaw2Va9ru3bv1ww8/aMmSJTLGyOPxyBij559/XpKUmpoa2DcxMVElJSVlntN/TEJCQtDxCQkJKikp0ejRo5Wenq558+YpOTlZnTp1UkFBQVzjTUpKktfrDdz3H5eUlKRff/1V33//vT799FO9+uqr+uGHH8o834IFC3TEEUdIkoYPH65x48apd+/eGjVqlH766aeIx8S7HwAAAAAANYHKncj2m8qdTz/9VNdcc402bNig9evXa9OmTercubOmT58e9ZjDDz9c69ev19q1ayVJH3zwgQYPHhz3NbOystS2bVslJyfrxx9/1IYNG8L2GTRokMaOHSuPx6P09HRNmzZN/fv31yGHHKLly5ersLBQmZmZ+v777yX5+vhkZWXp7LPP1n/+8x8tWrSozHFMnTpVb731lm6++WZJUnZ2ttq3b6/i4mKNHj06sF+TJk2UnZ0duB9tPwAAAAAAUHfUWuVOUlJzFRWlV+n5YhkzZozuv//+oMcuvvhijRkzRpdffnnEY9LS0jRy5EhdeumlKikpUb9+/XTrrbfGPaZhw4bp3HPPVa9evdS3b9+IS69feOGFmjVrlnr37i1jjJ577jkdcMABkqTLLrtMRx55pDp37qyjjz5aki9wOf/881VQUCBrrV544YWI1x47dqxmzJihvLw8de7cWZ999lmgcuef//ynjjvuOLVp00bHHXdcINC54oordPPNN+vll1/Wp59+GnU/AAAAAABqA4U7kZnQBr9VoW/fvnbu3LlBj61YsSIQLgCI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\n",
+ "image/png": "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DjtM8+fP18SJE/Xwww/rlFNO0U033aRzzz1XaWlpuvTSS5WUlKQWLVpo0aJF+vbbb/Xmm2/q448/DquusNaqZ8+emjVrVsRrpaamSpISEhICt/33y9N7xRgTdj8pKUlerzfwWEFBQcRjo+0X6fm9+OKLat68uRYuXBjxXI0aNYp7zH5///vfddJJJ+mLL77Q+vXrNWTIkMA292uSmJhY5muSnJwceC3cr6n79fzPf/6jdu3aadGiRfJ6vUpLC28L1bBhQ5122mkaP368Pv74Y82bNy/qNUeMGKGhQ4dq4sSJGjhwoL799tu4n7ub/7Ur63sGAAAAAICKiqeh8qmSfrfWpltriyV9LmlAGcfUOVu3blXDhg119dVX67777tP8+fPVoUMHdejQQU888YSuv/56SdKuXbvk9Xp18cUX64knntD8+fMlSU2aNFF2drYk6fDDD1d6enrgjXpxcbGWLVtWoXEdfvjh2rZtm+bMmSNJys7ODgQWkydP1u7du5Wfn69x48Zp4MCBateunXbu3KmMjAwVFhbq66+/jnjeTp06aeHChfJ6vdq0aZN+/fXXqM+vadOm6ty5sz755BNJviBi0aJFFXo+fllZWTrwwAMl+fr/lKV///6aOnWqdu3aJY/HozFjxgSqYeK9Xvv27ZWQkKAPPvhAHo8n4n433XST/vKXv6hfv35q0aJF1POtW7dOvXr10v33369+/fpp5cqVOu200zRy5Ejl5eVJknbv3q1mzZqpRYsWmj59uiTpgw8+iDjuqvyeAQAAAADALZ6eOxslHW+MaSjftKxTJM2NfUjds2TJEt13331KSEhQcnKy3njjDUm+6T/p6ek64ogjJElbtmzR9ddfH6h6efrppyVJw4cP16233hpoqPzpp5/qL3/5i7KyslRSUqK77rpLPXvGP93s7LPP1n//+1916NBBY8eO1R133KH8/Hw1aNBAU6ZMkeQLPC6++GJt3rxZV199tfr27StJeuSRR9S/f38deOCB6t69e8TzDxw4UJ07d1aPHj10xBFH6Jhjjon5/EaPHq3bbrtNTzzxhIqLi3XFFVdE7UcTj7/97W+67rrr9MQTT2jo0KFl7t++fXs988wzOumkkwINlc8///y4r/enP/1JF198sd5//32deeaZUauNjj32WDVt2jQQ5kXz4osv6scff1RCQoJ69uyps846S6mpqVq4cKH69u2rlJQUnX322Xrqqaf03nvvBRoqd+nSRSNHjgw7X0pKSqW/ZwAAAAAAiMRYW/YMKmPM45Iul1QiaYGkm6y1hdH279u3rw1tZrtixYpAgFKX3H777Tr66KN144031vZQgoSudhXvMevXrw9bOQultm7dqiFDhmjlypVKSIincK3m1dWfFQAAAACoKtnZ0dtkRNLrye2SpCUPHVAdw6k3mjbtO89a2zf08bje3VprH7XWdrfWHmmtvSZWsFOfHHvssVq8eLGuvvrq2h4KasD777+v4447Tk8++WSdDXYAAAAAACiveJdC3yfFaqhb24YPHx5zNadI+vTpo06dOlXLeCRp5MiReumll4IeW7Nmjbp16xb02MCBA/Xaa69V2zgq6tprr9W1114b9Fik51RXxw8AAAAAQCT7dbizr+nTp0+1nv/6668vs1dNfbMvPicAAAAAwP6FuSkAAAAAAAD1GOEOAAAAAACoF+JZFGp/RLgDAAAAAADqBaKdyGqt505W1myVlGRW2fmSkpqrWbPjy9xv3LhxuvDCC7VixQp17969yq5fXo0bN1ZOTk61nHvUqFG677771LFjR+Xk5KhLly569NFHNWDAgJjHjRs3Tocddph69OhRLeMCAAAAAABVr9Yqd0pKMpWS0qbK/os3KBozZoxOPPFEjRkzpnqfYC27/PLLtWDBAq1Zs0YjRozQRRddpBUrVsQ8Zty4cVq+fHkNjRAAAAAAgPJhVlZk+9W0rJycHM2YMUPvvPOOPvroo8DjP/30k4YMGaJLLrlE3bt317BhwwLz+L7//nsdffTR6tWrl2644QYVFhZKkjp16qQHHnhAffr0Ud++fTV//nydccYZ6tq1q958883A9U455RQdc8wx6tWrl8aPHx82Jmut7rvvPh155JHq1auXxo4dGxjTOeecE9jv9ttv16hRoyRJI0aMUI8ePXTUUUfp3nvvLfN5n3TSSbrlllv01ltvSZLefvtt9evXT71799bFF1+svLw8zZw5U19++aXuu+8+9enTR+vWrYu4HwAAAAAAtYVsJ7L9KtwZP368zjzzTB122GFq1aqV5s2bF9i2YMECvfjii1q+fLl+++03/fzzzyooKNDw4cM1duxYLVmyRCUlJXrjjTcCxxx88MFauHChBg0apOHDh+vTTz/V7Nmz9eijj0qS0tLS9MUXX2j+/Pn68ccf9de//jWs+dPnn3+uhQsXatGiRZoyZYruu+8+bdu2LepzyMjI0BdffKFly5Zp8eLFevjhh+N67sccc4xWrlwpSbrooos0Z84cLVq0SEcccYTeeecdDRgwQOedd56ef/55LVy4UF27do24HwAAAAAAtYXKncj2q3BnzJgxuuKKKyRJV1xxRdDUrP79+6tjx45KSEhQnz59tH79eq1atUqdO3fWYYcdJkm67rrrNG3atMAx5513niSpV69eOu6449SkSRO1adNGqampyszMlLVWDz74oI466iideuqp2rJli3bs2BE0phkzZujKK69UYmKi2rVrp8GDB2vOnDlRn0OzZs2UlpamG2+8UZ9//rkaNmwY13N3h0pLly7VoEGD1KtXL40ePVrLli2LeEy8+wEAAAAAgNpTaw2Va9ru3bv1ww8/aMmSJTLGyOPxyBij559/XpKUmpoa2DcxMVElJSVlntN/TEJCQtDxCQkJKikp0ejRo5Wenq558+YpOTlZnTp1UkFBQVzjTUpKktfrDdz3H5eUlKRff/1V33//vT799FO9+uqr+uGHH8o834IFC3TEEUdIkoYPH65x48apd+/eGjVqlH766aeIx8S7HwAAAAAANYHKncj2m8qdTz/9VNdcc402bNig9evXa9OmTercubOmT58e9ZjDDz9c69ev19q1ayVJH3zwgQYPHhz3NbOystS2bVslJyfrxx9/1IYNG8L2GTRokMaOHSuPx6P09HRNmzZN/fv31yGHHKLly5ersLBQmZmZ+v777yX5+vhkZWXp7LPP1n/+8x8tWrSozHFMnTpVb731lm6++WZJUnZ2ttq3b6/i4mKNHj06sF+TJk2UnZ0duB9tPwAAAAAAUHfUWuVOUlJzFRWlV+n5YhkzZozuv//+oMcuvvhijRkzRpdffnnEY9LS0jRy5EhdeumlKikpUb9+/XTrrbfGPaZhw4bp3HPPVa9evdS3b9+IS69feOGFmjVrlnr37i1jjJ577jkdcMABkqTLLrtMRx55pDp37qyjjz5aki9wOf/881VQUCBrrV544YWI1x47dqxmzJihvLw8de7cWZ999lmgcuef//ynjjvuOLVp00bHHXdcINC54oordPPNN+vll1/Wp59+GnU/AAAAAABqA4U7kZnQBr9VoW/fvnbu3LlBj61YsSIQLgCI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",
"text/plain": [
"<Figure size 1440x288 with 1 Axes>"
]
@@ -819,7 +822,7 @@
},
{
"data": {
- "image/png": "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PvjgDcb06U9/mptuuokBAwYAcOihh3LLLbfQv3//Ou9hxowZfOpTn+Ltt9/msssu49xzz61+/6dOncp3vvMd3n//fZ544gmuvPJKTjnllAa9N5IkSZIk5dNmA6GIGEOugPQOETEfuJpcEHRvRIwCXgdOzpo/BAwFXgHeA84uwJi3mj//+c/07NmTP/7xjwAsW7aMq6++msWLF9OtWzfuvPNOzjnnHKZPn86CBQuqA5elS5fSpUsXbr75Zn72s59RVVXF6tWrueiii5gwYQLdunVj7NixfOtb3+KOO+4AoE2bNkyePJkbb7yRYcOGMWXKFLbffnt23XVXvva1r9G1a9fqcX344YeccsopjB07lgMOOIB3332X9u1zU8+effZZXnjhBTp06MABBxzAMcccww477NCk96Gu+wM477zzuPXWW9ltt9145pln+MpXvsLDDz8MwPz583nyySdp2bLhSeWee+7J448/TqtWrfjrX//KN7/5Te6///7qMUybNo22bduyxx57cNFFF9GyZUsuv/xypkyZwnbbbcdRRx3F+PHjOf7441m5ciWf+cxnuO666zjhhBO46qqrmDhxIrNmzWLEiBEcd9xxfOxjH2PixIm0a9eOOXPmcNpppzF58uQNxjRq1ChGjx7NDTfcwD/+8Q9WrVpVbxgE8Pzzz/P000+zcuVKBg4cyDHHHFN9rE2bNnz/+9/fIHSTJEmSJKkYGvKUsdPqOXRkHW0TcEFTB1Xb5mbyFEq/fv34xje+weWXX86xxx7LYYcdxplnnslvf/tbzj77bJ566inuvvtuli9fzmuvvcZFF13EMcccw1FHHbVRXy+//DIvvPACQ4YMAWDt2rX06NGj+vhxxx1Xfc299967+tguu+zCvHnzNgiEXn75ZXr06MEBBxwAQOfOH62bHDJkSHXbE088kSeeeILjjz++Se/DLrvsstH9rVixgieffJLhw4dXt/vggw+qXw8fPnyLwiDIBW4jRoxgzpw5RASrV6+uPnbkkUey7bbbAtC3b19ef/11lixZwuDBg+nWLbfW8vTTT+exxx7j+OOPp02bNhx99NFA7j1t27YtrVu3pl+/fsydOxeA1atXc+GFFzJ9+nRatmzJP/7xj43GNHz4cH7wgx9w3XXXcccddzBy5MhN3sOwYcNo37497du354gjjuDZZ5+tnl0kSZIkSWqcci08XKhxp1p/NkZlF3fZjN13352pU6fy0EMPcdVVV3HkkUfy5S9/mS984Qu0a9eO4cOH06pVK7bbbjtmzJjBX/7yF2699Vbuvffe6pk/66WU2HvvvXnqqafqvFbbtm0BaNGiRfXr9dtr1qxp8Jij1qPqIoJWrVqxbt1H6w1Xraq7Cnl97eq6vxtuuIEuXbowffr0OvvaZpttGjzm9b797W9zxBFH8MADDzB37lwGDx5cfazme9KyZcvNvietW7eufi9qvqc138+f//zndO/enRkzZrBu3TratWu3UT8dOnRgyJAhTJgwgXvvvZcpU6Zs8rp1vf+SJEmSJJWaFsUeQClbuHAhHTp04IwzzuDSSy9l6tSp9OzZk549e3LNNddw9tm5FXFvv/0269at46STTuKaa65h6tSpAHTq1Inly5cDsMcee7B48eLqQGj16tW8+OKLjRrXHnvswaJFi3juuecAWL58eXXIMXHiRN555x3ef/99xo8fzyGHHEL37t156623WLJkCR988AF/+MMf6uy3d+/eTJ8+nXXr1jFv3jyeffbZeu+vc+fO9OnTh/vuuw/IBV4zZsxo1P2st2zZMnbccUcgV89ocwYNGsTf/vY33n77bdauXcuYMWM4/PDDt+h6PXr0oEWLFvzmN79h7dq1dbb78pe/zMUXX8wBBxzAdtttt8k+J0yYwKpVq1iyZAmPPvpo9Syu9Wr+TkiSJEmSGqdcvnuPWn9ucGwL7qF2203121AGQpswc+ZMBg0axIABA/je977HVVddBeSWJu20007stddeACxYsIDBgwczYMAAzjjjDH70ox8BMHLkSM4//3wGDBjA2rVrGTduHJdffjn9+/dnwIABPPnkk1s0nqFDh7Jw4ULatGnD2LFjueiii+jfvz9Dhgypns0zaNAgTjrpJPbdd19OOukkqqqqaN26Nd/5zncYNGgQQ4YMYc8996yz/0MOOYQ+ffrQt29fLr74Yvbbb79N3t8999zD7bffTv/+/dl7772ZMGFCnf021GWXXcaVV17JwIEDGzQrqkePHvz4xz/miCOOoH///uy///4MGzaswdf7yle+wl133UX//v156aWX6p3VtP/++9O5c+fqAHBT9t13X4444ggOOuggvv3tb2/0hLkjjjiCWbNmMWDAAMaOHdvgsUqSJEmSlE+RK/tTXFVVVal2Md/Zs2dXBy6l5sILL2TgwIGMGjWq2EPZQO2nhDX0nLlz5270xDF9ZOHChQwePJiXXnqJFi0Km6GW8u+9JEmSJBXL3U/N5TsTPlplc0L/9nz/2G0Ldr1+174BwMxvfbxB++tzzZ+WMXbq+1w+pBNnDNpwEsLUeR8y4u53GtTfsvfXcej1b1W3vXfKe/zgz+8yfGB7vjO0/vehc+eqKSmlqrqOOUNoC+2///48//zznHHGGcUeiraCu+++mwMPPJBrr7224GGQJEmSJKluJTCXpVEKXVS6KSwqvYU2V1S4mEaOHLnZp2DVNmDAAHr37l2Q8QDceeed3HjjjRvsmzNnDrvtttsG+w455BBuueWWgo2jsc466yzOOuusDfbVdU+lOn5JkiRJao7KpIRQtbrqBRX7HgyEKlyhH4l+9tlnN6j2TjlpjvckSZIkScq/QoU++ejXNTCSJEmSJEkVxkBIkiRJkiSpwhgISZIkSZKkklYKT0hvDItK58GyZU+zZs3SvPXXqlUXtt32oM22Gz9+PCeccAKzZ89mzz33zNv1t1THjh1ZsWJFQfoePXo0l156Kb169WLFihXssssuXH311Rx88MGbPG/8+PHsvvvu9O3btyDjkiRJkiSpLnUVaS43xb6HspkhtGbNUtq06Za3n4aGS2PGjOHQQw9lzJgxhb3BIjvllFOYNm0ac+bM4YorruDEE09k9uzZmzxn/PjxzJo1ayuNUJIkSZKk8pKvzKd2eGRR6QJbsWIFTzzxBLfffju/+93vqvc/+uijDB48mC9+8YvsueeenH766dXT1yZNmsTAgQPp168f55xzDh988AEAvXv35sorr2TAgAFUVVUxdepUPve5z7Hrrrty6623Vl/vyCOPZL/99qNfv35MmDBhozGllLj00kvZZ5996NevH2PHjq0e07HHHlvd7sILL2T06NEAXHHFFfTt25d9992XSy65ZLP3fcQRR3Deeedx2223AfCrX/2KAw44gP79+3PSSSfx3nvv8eSTT/Lggw9y6aWXMmDAAF599dU620mSJEmSpNJjILQJEyZM4Oijj2b33Xena9euTJkypfrYtGnTuOGGG5g1axavvfYaf//731m1ahUjR45k7NixzJw5kzVr1vDLX/6y+pydd96Z6dOnc9hhhzFy5EjGjRvH008/zdVXXw1Au3bteOCBB5g6dSqPPPII3/jGNzZaJ/n73/+e6dOnM2PGDP76179y6aWXsmjRonrvYcmSJTzwwAO8+OKLPP/881x11VUNuvf99tuPl156CYATTzyR5557jhkzZrDXXntx++23c/DBB3Pcccdx3XXXMX36dHbdddc620mSJEmSpNJjILQJY8aM4dRTTwXg1FNP3WDZ2KBBg+jVqxctWrRgwIABzJ07l5dffpk+ffqw++67AzBixAgee+yx6nOOO+44APr168eBBx5Ip06d6NatG23btmXp0qWklPjmN7/Jvvvuy2c/+1kWLFjAm2++ucGYnnjiCU477TRatmxJ9+7dOfzww3nuuefqvYdtt92Wdu3aMWrUKH7/+9/ToUOHBt17zSDqhRde4LDDDqNfv37cc889vPjii3We09B2kiRJkiRtifIsKW1R6bL0zjvv8PDDDzNz5kwigrVr1xIRXHfddQC0bdu2um3Lli1Zs2bNZvtcf06LFi02OL9FixasWbOGe+65h8WLFzNlyhRat25N7969WbVqVYPG26pVK9atW1e9vf68Vq1a8eyzzzJp0iTGjRvHzTffzMMPP7zZ/qZNm8Zee+0FwMiRIxk/fjz9+/dn9OjRPProo3We09B2kiRJkiRVklKsge0MoXqMGzeOM888k9dff525c+cyb948+vTpw+OPP17vOXvssQdz587llVdeAeA3v/kNhx9+eIOvuWzZMj72sY/RunVrHnnkEV5//fWN2hx22GGMHTuWtWvXsnjxYh577DEGDRrEJz7xCWbNmsUHH3zA0qVLmTRpEpCrS7Rs2TKGDh3Kz3/+c2bMmLHZcfztb3/jtttu49xzzwVg+fLl9OjRg9WrV3PPPfdUt+vUqRPLly+v3q6vnSRJkiRJlShvRaUL0G/ZzBBq1aoLH364OK/9bcqYMWO4/PLLN9h30kknMWbMGE455ZQ6z2nXrh133nknw4cPZ82aNRxwwAGcf/75DR7T6aefzhe+8AX69etHVVVVnY+5P+GEE3jqqafo378/EcFPf/pTPv7xjwNw8skns88++9CnTx8GDhwI5EKaYcOGsWrVKlJKXH/99XVee+zYsTzxxBO899579OnTh/vvv796htAPfvADDjzwQLp168aBBx5YHQKdeuqpnHvuudx0002MGzeu3naSJEmSJKm0RO2ixcVQVVWVJk+evMG+2bNnVwcSUqXw916S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\n",
+ "image/png": "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PvjgDcb06U9/mptuuokBAwYAcOihh3LLLbfQv3//Ou9hxowZfOpTn+Ltt9/msssu49xzz61+/6dOncp3vvMd3n//fZ544gmuvPJKTjnllAa9N5IkSZIk5dNmA6GIGEOugPQOETEfuJpcEHRvRIwCXgdOzpo/BAwFXgHeA84uwJi3mj//+c/07NmTP/7xjwAsW7aMq6++msWLF9OtWzfuvPNOzjnnHKZPn86CBQuqA5elS5fSpUsXbr75Zn72s59RVVXF6tWrueiii5gwYQLdunVj7NixfOtb3+KOO+4AoE2bNkyePJkbb7yRYcOGMWXKFLbffnt23XVXvva1r9G1a9fqcX344YeccsopjB07lgMOOIB3332X9u1zU8+effZZXnjhBTp06MABBxzAMcccww477NCk96Gu+wM477zzuPXWW9ltt9145pln+MpXvsLDDz8MwPz583nyySdp2bLhSeWee+7J448/TqtWrfjrX//KN7/5Te6///7qMUybNo22bduyxx57cNFFF9GyZUsuv/xypkyZwnbbbcdRRx3F+PHjOf7441m5ciWf+cxnuO666zjhhBO46qqrmDhxIrNmzWLEiBEcd9xxfOxjH2PixIm0a9eOOXPmcNpppzF58uQNxjRq1ChGjx7NDTfcwD/+8Q9WrVpVbxgE8Pzzz/P000+zcuVKBg4cyDHHHFN9rE2bNnz/+9/fIHSTJEmSJKkYGvKUsdPqOXRkHW0TcEFTB1Xb5mbyFEq/fv34xje+weWXX86xxx7LYYcdxplnnslvf/tbzj77bJ566inuvvtuli9fzmuvvcZFF13EMcccw1FHHbVRXy+//DIvvPACQ4YMAWDt2rX06NGj+vhxxx1Xfc299967+tguu+zCvHnzNgiEXn75ZXr06MEBBxwAQOfOH62bHDJkSHXbE088kSeeeILjjz++Se/DLrvsstH9rVixgieffJLhw4dXt/vggw+qXw8fPnyLwiDIBW4jRoxgzpw5RASrV6+uPnbkkUey7bbbAtC3b19ef/11lixZwuDBg+nWLbfW8vTTT+exxx7j+OOPp02bNhx99NFA7j1t27YtrVu3pl+/fsydOxeA1atXc+GFFzJ9+nRatmzJP/7xj43GNHz4cH7wgx9w3XXXcccddzBy5MhN3sOwYcNo37497du354gjjuDZZ5+tnl0kSZIkSWqcci08XKhxp1p/NkZlF3fZjN13352pU6fy0EMPcdVVV3HkkUfy5S9/mS984Qu0a9eO4cOH06pVK7bbbjtmzJjBX/7yF2699Vbuvffe6pk/66WU2HvvvXnqqafqvFbbtm0BaNGiRfXr9dtr1qxp8Jij1qPqIoJWrVqxbt1H6w1Xraq7Cnl97eq6vxtuuIEuXbowffr0OvvaZpttGjzm9b797W9zxBFH8MADDzB37lwGDx5cfazme9KyZcvNvietW7eufi9qvqc138+f//zndO/enRkzZrBu3TratWu3UT8dOnRgyJAhTJgwgXvvvZcpU6Zs8rp1vf+SJEmSJJWaFsUeQClbuHAhHTp04IwzzuDSSy9l6tSp9OzZk549e3LNNddw9tm5FXFvv/0269at46STTuKaa65h6tSpAHTq1Inly5cDsMcee7B48eLqQGj16tW8+OKLjRrXHnvswaJFi3juuecAWL58eXXIMXHiRN555x3ef/99xo8fzyGHHEL37t156623WLJkCR988AF/+MMf6uy3d+/eTJ8+nXXr1jFv3jyeffbZeu+vc+fO9OnTh/vuuw/IBV4zZsxo1P2st2zZMnbccUcgV89ocwYNGsTf/vY33n77bdauXcuYMWM4/PDDt+h6PXr0oEWLFvzmN79h7dq1dbb78pe/zMUXX8wBBxzAdtttt8k+J0yYwKpVq1iyZAmPPvpo9Syu9Wr+TkiSJEmSGqdcvnuPWn9ucGwL7qF2203121AGQpswc+ZMBg0axIABA/je977HVVddBeSWJu20007stddeACxYsIDBgwczYMAAzjjjDH70ox8BMHLkSM4//3wGDBjA2rVrGTduHJdffjn9+/dnwIABPPnkk1s0nqFDh7Jw4ULatGnD2LFjueiii+jfvz9Dhgypns0zaNAgTjrpJPbdd19OOukkqqqqaN26Nd/5zncYNGgQQ4YMYc8996yz/0MOOYQ+ffrQt29fLr74Yvbbb79N3t8999zD7bffTv/+/dl7772ZMGFCnf021GWXXcaVV17JwIEDGzQrqkePHvz4xz/miCOOoH///uy///4MGzaswdf7yle+wl133UX//v156aWX6p3VtP/++9O5c+fqAHBT9t13X4444ggOOuggvv3tb2/0hLkjjjiCWbNmMWDAAMaOHdvgsUqSJEmSlE+RK/tTXFVVVal2Md/Zs2dXBy6l5sILL2TgwIGMGjWq2EPZQO2nhDX0nLlz5270xDF9ZOHChQwePJiXXnqJFi0Km6GW8u+9JEmSJBXL3U/N5TsTPlplc0L/9nz/2G0Ldr1+174BwMxvfbxB++tzzZ+WMXbq+1w+pBNnDNpwEsLUeR8y4u53GtTfsvfXcej1b1W3vXfKe/zgz+8yfGB7vjO0/vehc+eqKSmlqrqOOUNoC+2///48//zznHHGGcUeiraCu+++mwMPPJBrr7224GGQJEmSJKluJTCXpVEKXVS6KSwqvYU2V1S4mEaOHLnZp2DVNmDAAHr37l2Q8QDceeed3HjjjRvsmzNnDrvtttsG+w455BBuueWWgo2jsc466yzOOuusDfbVdU+lOn5JkiRJao7KpIRQtbrqBRX7HgyEKlyhH4l+9tlnN6j2TjlpjvckSZIkScq/QoU++ejXNTCSJEmSJEkVxkBIkiRJkiSpwhgISZIkSZKkklYKT0hvDItK58GyZU+zZs3SvPXXqlUXtt32oM22Gz9+PCeccAKzZ89mzz33zNv1t1THjh1ZsWJFQfoePXo0l156Kb169WLFihXssssuXH311Rx88MGbPG/8+PHsvvvu9O3btyDjkiRJkiSpLnUVaS43xb6HspkhtGbNUtq06Za3n4aGS2PGjOHQQw9lzJgxhb3BIjvllFOYNm0ac+bM4YorruDEE09k9uzZmzxn/PjxzJo1ayuNUJIkSZKk8pKvzKd2eGRR6QJbsWIFTzzxBLfffju/+93vqvc/+uijDB48mC9+8YvsueeenH766dXT1yZNmsTAgQPp168f55xzDh988AEAvXv35sorr2TAgAFUVVUxdepUPve5z7Hrrrty6623Vl/vyCOPZL/99qNfv35MmDBhozGllLj00kvZZ5996NevH2PHjq0e07HHHlvd7sILL2T06NEAXHHFFfTt25d9992XSy65ZLP3fcQRR3Deeedx2223AfCrX/2KAw44gP79+3PSSSfx3nvv8eSTT/Lggw9y6aWXMmDAAF599dU620mSJEmSpNJjILQJEyZM4Oijj2b33Xena9euTJkypfrYtGnTuOGGG5g1axavvfYaf//731m1ahUjR45k7NixzJw5kzVr1vDLX/6y+pydd96Z6dOnc9hhhzFy5EjGjRvH008/zdVXXw1Au3bteOCBB5g6dSqPPPII3/jGNzZaJ/n73/+e6dOnM2PGDP76179y6aWXsmjRonrvYcmSJTzwwAO8+OKLPP/881x11VUNuvf99tuPl156CYATTzyR5557jhkzZrDXXntx++23c/DBB3Pcccdx3XXXMX36dHbdddc620mSJEmSpNJjILQJY8aM4dRTTwXg1FNP3WDZ2KBBg+jVqxctWrRgwIABzJ07l5dffpk+ffqw++67AzBixAgee+yx6nOOO+44APr168eBBx5Ip06d6NatG23btmXp0qWklPjmN7/Jvvvuy2c/+1kWLFjAm2++ucGYnnjiCU477TRatmxJ9+7dOfzww3nuuefqvYdtt92Wdu3aMWrUKH7/+9/ToUOHBt17zSDqhRde4LDDDqNfv37cc889vPjii3We09B2kiRJkiRtifIsKW1R6bL0zjvv8PDDDzNz5kwigrVr1xIRXHfddQC0bdu2um3Lli1Zs2bNZvtcf06LFi02OL9FixasWbOGe+65h8WLFzNlyhRat25N7969WbVqVYPG26pVK9atW1e9vf68Vq1a8eyzzzJp0iTGjRvHzTffzMMPP7zZ/qZNm8Zee+0FwMiRIxk/fjz9+/dn9OjRPProo3We09B2kiRJkiRVklKsge0MoXqMGzeOM888k9dff525c+cyb948+vTpw+OPP17vOXvssQdz587llVdeAeA3v/kNhx9+eIOvuWzZMj72sY/RunVrHnnkEV5//fWN2hx22GGMHTuWtWvXsnjxYh577DEGDRrEJz7xCWbNmsUHH3zA0qVLmTRpEpCrS7Rs2TKGDh3Kz3/+c2bMmLHZcfztb3/jtttu49xzzwVg+fLl9OjRg9WrV3PPPfdUt+vUqRPLly+v3q6vnSRJkiRJlShvRaUL0G/ZzBBq1aoLH364OK/9bcqYMWO4/PLLN9h30kknMWbMGE455ZQ6z2nXrh133nknw4cPZ82aNRxwwAGcf/75DR7T6aefzhe+8AX69etHVVVVnY+5P+GEE3jqqafo378/EcFPf/pTPv7xjwNw8skns88++9CnTx8GDhwI5EKaYcOGsWrVKlJKXH/99XVee+zYsTzxxBO899579OnTh/vvv796htAPfvADDjzwQLp168aBBx5YHQKdeuqpnHvuudx0002MGzeu3naSJEmSJKm0RO2ixcVQVVWVJk+evMG+2bNnVwcSUqXw916S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",
"text/plain": [
"<Figure size 1440x288 with 1 Axes>"
]
@@ -831,7 +834,7 @@
},
{
"data": {
- "image/png": "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0yb06tULTZs2xR9//KFtlx49eqBz587o3Lkz1q1b59WmG264Adu2bdOeX3/99UhMTDRs/6pVqxATE4OYmBh06tQJWVlZAID33nsP7du3R8eOHTF+/HgAwLZt23DNNdegQ4cOuPPOO3H+/HkAyr7w7LPPIjY2Fp988onPfYaIiIiIiIh8K2/dwcqj8ptyUMoZO74sWrQIDRo0wIIFCwAAGRkZmDhxIlJSUhAdHY3p06fj4YcfxrZt23DixAns3LkTAJCeno4aNWpg2rRp+OCDDxAbGwur1YpRo0Zh/vz5iI6OxuzZs/HKK6/gu+++AwCEh4cjPj4en3zyCQYNGoSEhATUqlULzZo1w3PPPYfatWtr7SosLMQ999yD2bNno2vXrsjMzERUVBQAJZCyc+dOVKpUCV27dsXAgQNRp06dYm0Ho88HAI8//ji+/PJLNG/eHBs3bsRTTz2F5cuXAwCSk5Oxbt06mM3moNfTqlUrrFmzBhaLBUuXLsXLL7+MX3/9VWvD1q1bERERgZYtW2LUqFEwm80YN24cEhISULNmTfTv3x/z5s3DHXfcgZycHNx4442YMmUK7rzzTkyYMAFLlizB7t278dBDD+H2229H3bp1sWTJEkRGRuLAgQO47777EB8f79amRx55BHFxcZg6dSr279+P/Px8dOzY0bD9H3zwAT777DN0794d2dnZiIyMxN9//4358+dj48aNqFSpEtLS0gAADz74IP73v/+hZ8+eeO211/DGG29g6tSpAJS/b3x8PKxWK3r27OlznyEiIiIiIiJj5ScIVH4LQ5ffIFAZad++PV544QWMGzcOt956K3r06IEHHngAP/74I0aOHIn169djxowZyMrKwuHDhzFq1CgMHDgQ/fv391rWvn37sHPnTvTr1w8AYLfbUb9+fW367bffrq2zbdu22rSmTZvi+PHjbkGgffv2oX79+ujatSsAoFq1atq0fv36afMOHjwYa9eudcteKoqmTZt6fb7s7GysW7cOQ4cO1eYrKCjQHg8dOjSkABCgBNkeeughHDhwAEIIWK1WbVqfPn1QvXp1AECbNm1w9OhRpKamolevXoiOjgYADBs2DKtXr8Ydd9yB8PBwDBgwAICyTSMiIhAWFob27dsjKSkJAGC1WvHMM89g27ZtMJvN2L9/v1ebhg4dirfeegtTpkzBd999hxEjRvhsf/fu3fH8889j2LBhGDx4MBo1aoSlS5di5MiRqFSpEgCgVq1ayMjIQHp6Onr27AkAeOihh9y24z333AMg8D5DRERERER0qRIBOny5gkCl0zHs30MF6NAwDFUjg+tUZdzesu20xiCQhxYtWmDLli1YuHAhJkyYgD59+uDRRx/FbbfdhsjISAwdOhQWiwU1a9ZEYmIiFi9ejC+//BJz5szxytaQUqJt27ZYv3694boiIiIAACaTSXusPrfZbEG3WXgUvRJCwGKxwOFwaK/l5+cbvtfXfEafb+rUqahRo4ZbVym9ypUrB91m1auvvorevXvj999/R1JSEnr16qVN028Ts9kccJuEhYVp20K/TfXb8+OPP0a9evWQmJgIh8OByMhIr+VUqlQJ/fr1w/z58zFnzhwkJCT4XOf48eMxcOBALFy4EN27d8fixYuD/ux66rYLtM8QERERERGRsdLOBHri5/O4vlk4vri3VsCWlASva32DeT5ZkYXv1ucg8eXLglomawJ5OHnyJCpVqoThw4djzJgx2LJlCxo0aIAGDRpg0qRJGDlyJADg3LlzcDgcGDJkCCZNmoQtW7YAAKpWrarVhWnZsiVSUlK0C3qr1Ypdu3YVqV0tW7bEqVOnsHnzZgBAVlaWFthYsmQJ0tLSkJeXh3nz5qF79+6oV68ezp49i9TUVBQUFOCvv/4yXG7jxo2xbds2OBwOHD9+HJs2bfL5+apVq4YmTZrgl19+AaAELHzVyglWRkYGGjZsCECpAxRIt27dsGrVKpw7dw52ux2zZs3SsmuCXV/9+vVhMpnwww8/wG63G8736KOPYvTo0ejatStq1qzpc3mHDh1C+/btMW7cOHTt2hV79+5Fv379MH36dOTm5gIA0tLSUL16ddSsWRNr1qwBAPzwww+G7S7JfYaIiIiIiOhSIrwelLykVONryLLyzbocOEKIfTETyMOOHTswZswYmEwmhIWF4YsvvgCgdDtKSUlB69atAQAnTpzAyJEjtSyad955B4CrQHJUVBTWr1+PuXPnYvTo0cjIyIDNZsOzzz6Ltm3bBt2eW265Bd988w0aNGiA2bNnY9SoUcjLy0NUVBSWLl0KQAmMDBkyBMnJyRg+fDhiY2MBAK+99hq6deuGhg0bolWrVobL7969O5o0aYI2bdqgdevW6Ny5s9/PN3PmTDz55JOYNGkSrFYr7r33Xp/1coIxduxYPPTQQ5g0aRIGDhwYcP769evj3XffRe/evSGlxMCBAzFo0KCg1/fUU09hyJAhmDFjBgYMGOAze6lLly6oVq2aFvTzZerUqVixYgVMJhPatm2Lm2++GREREdi2bRtiY2MRHh6OW265BW+//Ta+//57PPHEE8jNzUXTpk0xffp0r+WFh4cXe58hIiIiIiK6GEgZWmZP+akJVH6JUDdqSYmNjZWeBXn37NmjBVnKm2eeeQadOnXCI488UtZNcRMXF4f4+HhMmzYtpPckJSUZjhZGipMnT6JXr17Yu3cvTKbylzBXnr8rREREREREJeHH3T/ivc3vac9vb9wT4zv7vlEf/kF/RAgbCtvfgoL+L4a8vut+GwEAWDc4zvD1rD3volENM/5+OtrvcqZs/R6/H1mB5zsOx13N+rpN25F6EP9ZNclwPZ6yrbno/+dT2ry/HV6OD7bNwB1NemFsJ6VN7SefVpb7iqs7WLVqsQlSylijZZa/q9tyqEuXLti+fTuGDx9e1k2hC2DGjBm4+uqrMXny5HIZACIiIiIiIroUBSoMHSGCr617IRiWhS7butDsDhYMf4WBy9qIESP8jl5lJCYmBo0bNy6V9gDA9OnT8cknn7i9duDAATRv3tztte7du+Ozzz4rtXYU1YMPPogHH3zQ7TWjz1Re209ERERERERlqYQKQyNwYehQMQh0CYqJiSnV5Y8cOTJgLZ2K5mL8TERERERERBen0ku3KetMnuJiXxciIiIiIiIiKndkhS30XDrtLomlMghEREREREREROVe0Fk4FT1dpxQxCEREREREREREVOK8g1GBiluXNgaBiIiIiIiIiIhKTOkEekpiqQwClbC4uDicPHmyrJvhV1xcHF5//fWybka5kpSUhHbt2oX8vtdffx0ffPCB1+snT57EXXfdBQDYtm0bFi5cWOw2EhERERERERVHuR0d7L1N72Fv2t4SXWarWq0wrtu4El2mp7i4OLRr1w4NGjQo1fWUZzabDRZLud21LogGDRpg7ty5AJQgUHx8PG655ZYybhUREREREVHFIWURSyFLR8k2RCe4bBwWhq4wcnJyMHDgQHTs2BHt2rXD7Nmzcccdd2jTlyxZgjvvvBN2ux0jRoxAu3bt0L59e3z88ceYO3cu4uPjMWzYMMTExCAvLw8JCQno2bMnunTpgptu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\n",
+ "image/png": "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0yb06tULTZs2xR9//KFtlx49eqBz587o3Lkz1q1b59WmG264Adu2bdOeX3/99UhMTDRs/6pVqxATE4OYmBh06tQJWVlZAID33nsP7du3R8eOHTF+/HgAwLZt23DNNdegQ4cOuPPOO3H+/HkAyr7w7LPPIjY2Fp988onPfYaIiIiIiIh8K2/dwcqj8ptyUMoZO74sWrQIDRo0wIIFCwAAGRkZmDhxIlJSUhAdHY3p06fj4YcfxrZt23DixAns3LkTAJCeno4aNWpg2rRp+OCDDxAbGwur1YpRo0Zh/vz5iI6OxuzZs/HKK6/gu+++AwCEh4cjPj4en3zyCQYNGoSEhATUqlULzZo1w3PPPYfatWtr7SosLMQ999yD2bNno2vXrsjMzERUVBQAJZCyc+dOVKpUCV27dsXAgQNRp06dYm0Ho88HAI8//ji+/PJLNG/eHBs3bsRTTz2F5cuXAwCSk5Oxbt06mM3moNfTqlUrrFmzBhaLBUuXLsXLL7+MX3/9VWvD1q1bERERgZYtW2LUqFEwm80YN24cEhISULNmTfTv3x/z5s3DHXfcgZycHNx4442YMmUK7rzzTkyYMAFLlizB7t278dBDD+H2229H3bp1sWTJEkRGRuLAgQO47777EB8f79amRx55BHFxcZg6dSr279+P/Px8dOzY0bD9H3zwAT777DN0794d2dnZiIyMxN9//4358+dj48aNqFSpEtLS0gAADz74IP73v/+hZ8+eeO211/DGG29g6tSpAJS/b3x8PKxWK3r27OlznyEiIiIiIiJj5ScIVH4LQ5ffIFAZad++PV544QWMGzcOt956K3r06IEHHngAP/74I0aOHIn169djxowZyMrKwuHDhzFq1CgMHDgQ/fv391rWvn37sHPnTvTr1w8AYLfbUb9+fW367bffrq2zbdu22rSmTZvi+PHjbkGgffv2oX79+ujatSsAoFq1atq0fv36afMOHjwYa9eudcteKoqmTZt6fb7s7GysW7cOQ4cO1eYrKCjQHg8dOjSkABCgBNkeeughHDhwAEIIWK1WbVqfPn1QvXp1AECbNm1w9OhRpKamolevXoiOjgYADBs2DKtXr8Ydd9yB8PBwDBgwAICyTSMiIhAWFob27dsjKSkJAGC1WvHMM89g27ZtMJvN2L9/v1ebhg4dirfeegtTpkzBd999hxEjRvhsf/fu3fH8889j2LBhGDx4MBo1aoSlS5di5MiRqFSpEgCgVq1ayMjIQHp6Onr27AkAeOihh9y24z333AMg8D5DRERERER0qRIBOny5gkCl0zHs30MF6NAwDFUjg+tUZdzesu20xiCQhxYtWmDLli1YuHAhJkyYgD59+uDRRx/FbbfdhsjISAwdOhQWiwU1a9ZEYmIiFi9ejC+//BJz5szxytaQUqJt27ZYv3694boiIiIAACaTSXusPrfZbEG3WXgUvRJCwGKxwOFwaK/l5+cbvtfXfEafb+rUqahRo4ZbVym9ypUrB91m1auvvorevXvj999/R1JSEnr16qVN028Ts9kccJuEhYVp20K/TfXb8+OPP0a9evWQmJgIh8OByMhIr+VUqlQJ/fr1w/z58zFnzhwkJCT4XOf48eMxcOBALFy4EN27d8fixYuD/ux66rYLtM8QERERERGRsdLOBHri5/O4vlk4vri3VsCWlASva32DeT5ZkYXv1ucg8eXLglomawJ5OHnyJCpVqoThw4djzJgx2LJlCxo0aIAGDRpg0qRJGDlyJADg3LlzcDgcGDJkCCZNmoQtW7YAAKpWrarVhWnZsiVSUlK0C3qr1Ypdu3YVqV0tW7bEqVOnsHnzZgBAVlaWFthYsmQJ0tLSkJeXh3nz5qF79+6oV68ezp49i9TUVBQUFOCvv/4yXG7jxo2xbds2OBwOHD9+HJs2bfL5+apVq4YmTZrgl19+AaAELHzVyglWRkYGGjZsCECpAxRIt27dsGrVKpw7dw52ux2zZs3SsmuCXV/9+vVhMpnwww8/wG63G8736KOPYvTo0ejatStq1qzpc3mHDh1C+/btMW7cOHTt2hV79+5Fv379MH36dOTm5gIA0tLSUL16ddSsWRNr1qwBAPzwww+G7S7JfYaIiIiIiOhSIrwelLykVONryLLyzbocOEKIfTETyMOOHTswZswYmEwmhIWF4YsvvgCgdDtKSUlB69atAQAnTpzAyJEjtSyad955B4CrQHJUVBTWr1+PuXPnYvTo0cjIyIDNZsOzzz6Ltm3bBt2eW265Bd988w0aNGiA2bNnY9SoUcjLy0NUVBSWLl0KQAmMDBkyBMnJyRg+fDhiY2MBAK+99hq6deuGhg0bolWrVobL7969O5o0aYI2bdqgdevW6Ny5s9/PN3PmTDz55JOYNGkSrFYr7r33Xp/1coIxduxYPPTQQ5g0aRIGDhwYcP769evj3XffRe/evSGlxMCBAzFo0KCg1/fUU09hyJAhmDFjBgYMGOAze6lLly6oVq2aFvTzZerUqVixYgVMJhPatm2Lm2++GREREdi2bRtiY2MRHh6OW265BW+//Ta+//57PPHEE8jNzUXTpk0xffp0r+WFh4cXe58hIiIiIiK6GEgZWmZP+akJVH6JUDdqSYmNjZWeBXn37NmjBVnKm2eeeQadOnXCI488UtZNcRMXF4f4+HhMmzYtpPckJSUZjhZGipMnT6JXr17Yu3cvTKbylzBXnr8rREREREREJeHH3T/ivc3vac9vb9wT4zv7vlEf/kF/RAgbCtvfgoL+L4a8vut+GwEAWDc4zvD1rD3volENM/5+OtrvcqZs/R6/H1mB5zsOx13N+rpN25F6EP9ZNclwPZ6yrbno/+dT2ry/HV6OD7bNwB1NemFsJ6VN7SefVpb7iqs7WLVqsQlSylijZZa/q9tyqEuXLti+fTuGDx9e1k2hC2DGjBm4+uqrMXny5HIZACIiIiIiIroUBSoMHSGCr617IRiWhS7butDsDhYMf4WBy9qIESP8jl5lJCYmBo0bNy6V9gDA9OnT8cknn7i9duDAATRv3tztte7du+Ozzz4rtXYU1YMPPogHH3zQ7TWjz1Re209ERERERERlqYQKQyNwYehQMQh0CYqJiSnV5Y8cOTJgLZ2K5mL8TERERERERBen0ku3KetMnuJiXxciIiIiIiIiKndkhS30XDrtLomlMghEREREREREROVe0Fk4FT1dpxQxCEREREREREREVOK8g1GBiluXNgaBiIiIiIiIiIhKTOkEekpiqQwClbC4uDicPHmyrJvhV1xcHF5//fWybka5kpSUhHbt2oX8vtdffx0ffPCB1+snT57EXXfdBQDYtm0bFi5cWOw2EhERERERERVHuR0d7L1N72Fv2t4SXWarWq0wrtu4El2mp7i4OLRr1w4NGjQo1fWUZzabDRZLud21LogGDRpg7ty5AJQgUHx8PG655ZYybhUREREREVHFIWURSyFLR8k2RCe4bBwWhq4wcnJyMHDgQHTs2BHt2rXD7Nmzcccdd2jTlyxZgjvvvBN2ux0jRoxAu3bt0L59e3z88ceYO3cu4uPjMWzYMMTExCAvLw8JCQno2bMnunTpgptu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",
"text/plain": [
"<Figure size 1440x288 with 1 Axes>"
]
@@ -920,12 +923,13 @@
]
},
{
+ "attachments": {},
"cell_type": "markdown",
"metadata": {},
"source": [
- "In this final section of the notebook below we will dig in to try understand this a bit more intuitivley.\n",
+ "In this final section of the notebook below we will dig in to try understand this a bit more intuitively.\n",
"\n",
- "First we will \"[featureize](https://brilliant.org/wiki/feature-vector/)\" or \"preprocess\" all the data. Then we will explore what these feature vectors actually are, how they look, and how we derive anomaly scores based on thier distance to the models cluster centroids."
+ "First we will \"[featureize](https://brilliant.org/wiki/feature-vector/)\" or \"preprocess\" all the data. Then we will explore what these feature vectors actually are, how they look, and how we derive anomaly scores based on their distance to the models cluster centroids."
]
},
{
@@ -944,12 +948,13 @@
]
},
{
+ "attachments": {},
"cell_type": "markdown",
"metadata": {},
"source": [
"Now that we have preprocessed all of our data, lets just take a look at it.\n",
"\n",
- "You will see that we have essentially just added `num_samples_to_lag` additional columns to the dataframe, one for each lag. The numbers themselve also are now longer the original raw metric values, instead they have first been differenced (just take difference of latest value with pervious value so that we are working with delta's as opposed to original raw metric) and also smoothed (in this case by just averaging the previous `num_samples_to_smooth` previous differenced values).\n",
+ "You will see that we have essentially just added `num_samples_to_lag` additional columns to the dataframe, one for each lag. The numbers themselves also are now longer the original raw metric values, instead they have first been differenced (just take difference of latest value with previous value so that we are working with delta's as opposed to original raw metric) and also smoothed (in this case by just averaging the previous `num_samples_to_smooth` previous differenced values).\n",
"\n",
"The idea here is to define the representation that the model will work in. In this case the model will decide if a recent observation is anomalous based on it's corresponding feature vector which is a differenced, smoothed, and lagged array or list of recent values."
]
@@ -1175,12 +1180,13 @@
]
},
{
+ "attachments": {},
"cell_type": "markdown",
"metadata": {},
"source": [
"At inference time we can now use our `n_clusters_per_dimension` cluster centers as a sort of set of \"reference\" feature vectors we can compare against. \n",
"\n",
- "When we see a new feature vector that is very far away from these \"reference\" feature vectors, we can take that as a signal that the recent data the feature vecotr was derived from may look significantly different than most of the data the clusters where initially train on. And as such it may be \"anomalous\" or \"strange\" in some way that might be meaningful to you are a user trying to monitor and troubleshoot systems based on these metrics.\n",
+ "When we see a new feature vector that is very far away from these \"reference\" feature vectors, we can take that as a signal that the recent data the feature vector was derived from may look significantly different than most of the data the clusters where initially train on. And as such it may be \"anomalous\" or \"strange\" in some way that might be meaningful to you are a user trying to monitor and troubleshoot systems based on these metrics.\n",
"\n",
"To try make this visually clearer we will take 10 random feature vectors from the first half of our data where things were generally normal and we will also take 10 random feature vectors from the yellow anomalous period of time. Lastly we will also include the cluster centroids themselves to see how they compare to both sets of 10 feature vectors. \n",
"\n",
@@ -1208,7 +1214,7 @@
},
{
"data": {
- "image/png": "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\n",
+ "image/png": "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",
"text/plain": [
"<Figure size 720x720 with 2 Axes>"
]
@@ -1237,12 +1243,13 @@
]
},
{
+ "attachments": {},
"cell_type": "markdown",
"metadata": {},
"source": [
"You should see from the above heatmap that the top 10 rows all tend to have lower anomaly scores (AS) and anomaly bits (AB) that are 0. While its the opposite for rows 11-20.\n",
"\n",
- "The final two rows are the cluster centroids themselve which should look more similar to the first 10 rows, fairly different to rows 11-20. And of course, you would expect that each cluster centroid itself has a low anomaly score and non-anomalous anomaly bit."
+ "The final two rows are the cluster centroids themselves which should look more similar to the first 10 rows, fairly different to rows 11-20. And of course, you would expect that each cluster centroid itself has a low anomaly score and non-anomalous anomaly bit."
]
},
{
@@ -1276,7 +1283,7 @@
},
{
"data": {
- "image/png": "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\n",
+ "image/png": "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",
"text/plain": [
"<Figure size 720x360 with 1 Axes>"
]
@@ -1308,7 +1315,7 @@
"outputs": [
{
"data": {
- "image/png": "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\n",
+ "image/png": "iVBORw0KGgoAAAANSUhEUgAAAlEAAADvCAYAAADSI4HyAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjUuMSwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/YYfK9AAAACXBIWXMAAAsTAAALEwEAmpwYAAAhEElEQVR4nO3debglVXnv8e8PmkkQiNIigtig4IDIYIuixqBeFUeuQ4yKxim2SSSiRowZTPRqInq9zsGEqCiJRozGGU00Is5iMygCgogIiECDoiDK+N4/Vm3ZbE53n64+p3qf09/P89TTe1fVrlr1VvXe71lr1apUFZIkSVo3m2zoAkiSJC1EJlGSJEk9mERJkiT1YBIlSZLUg0mUJElSDyZRkiRJPZhESVrQkrw+yUs2dDk2Zkken+S4DV0OaWgmURKQ5LAkK5Ncm+R96/jZ9yV53XzsN8ltkhyV5PIkv0jy5RnW2TzJWUkuGpv3u0munpgqyZO75VskeUuSi5P8vNvHZmOfX5bk+G7ZJUnemWTJ2PJ9k5yc5Jru333Hlh2R5HtJrkryoyRHzFDmw7tlv+rKvmc3/6FJTk9yZZIrknwsyc5riN9S4A+Bf56Yv1uSm5K8a4bPHJLktCS/7OL6xSS7rW4fa5Pkf7rYLpmYv7pj3CfJGd2+Xza2/mZJvpXkzj3KcFBXhr+YmL+smz+6Bi6dPNfrsI/bdefjV0l+nOQZo2VV9SlgryT3WdftSguZSZTUXAy8DnjvlO33aOB2wD27f186wzpHAKvGZ1TVV6pqm9EEPA64Gvhct8orgeXAvYE9gf2BvxnbxFHAZcBOwL7A7wF/Ci1pAz4B/BvwO8D7gU908wFCS2x+BzgYOCzJ00YbTvJHwPOBxwKjsl3eLT4TeFRVbQ/cCfgBcKtEaMxzgOOr6tcT8/8Q+DnwB0m2GNv33YBjgT8HtgN2A/4RuHEN+1itJIcCt0pI1nKMrwdeDuwD/HWSO3bzXwZ8tKou7FGUZwM/ox33TLbvroO9gQOBF/XYxz8C1wE7AocC70qy19jyfwdW9NiutHBVlZOTUzfREpr3rcP6K4DraT8uVwOfmqv9AvcAfglsu4bP7QacBTwauGgN6x0DHDP2fiXw+2PvnwFcOPb+LOAxY+//L/DP3etHAj8BMrb8AuDg1ez77cA7utebABcCD59FTLagJRxnrmGdLwLPnJgX4IfAnwCXAk8ZW/YU4LQ5ula2A84BHgAUsGQ2x9jFdovu9TeBA4C7ACcBm/Uox9bAVcDTuutw+diyZeNl6+a9ETi6xz6uA/Ycm/evwJFj7x8E/GguYuvktFAma6Kk9VBVRwMfAN5Yrdbn8QBJPt01Sc00fXqWmz8A+DHwmq7p5/RRc9yYdwB/BUzWxPxWkq1pycP7JxdNvN4lyXbd+7cCT+uaE3emJWmjWqy9gO9W1fgzo77bzZ/cd4DfBc7oZu3STfdOcmHX3PWaJJuMfWbXJFd2x/Ry2o/+6uwNnD0x78HdPj4EfJhWSzNyCnCPrinzoUm2mSjvM9Zw3q5MsuvY6v9AqyW7ZGL/azvG7wGPTLILLcn5IfA24Iiqun4Nx7o6T6Il8P8B/NfE8d5CkjsBj6Ilb6N5s7lW9wRuqKpzxjb3HW55zs8CliXZtscxSAuSSZQ0D6rqcVW1/Wqmx81yM7vQmtt+QWvaOgx4f5J7AiR5IrBpVX1sLdt5Eq0p6cSxeZ8DDk+ytGtOenE3/zbdv1+m/UD+EriIVnP18W7ZNl2Zxv0CuO0M+3417XvmmLFjglabtTfwUODptKYvAKrqgmrNeTvQmhi/v4Zj255WCzPu2cBnq+rnwAeBg5Pcodv2ecBBwM60BOvytD5t23TLP7iG87Z9VV0AkGQ5reblHTOUaW3H+HJaLdknac2zD+qO4UdJPpHkxCS/v4ZjnvRs4LiqurE73qfN0Ofp8i4x/QnwK+AjowWzvFa3oV0L4ybP+eg8bL8OZZcWNJMoaXr9mtZU+Lqquq6qTgROoNVibE2roXnxmjbQeTZw7ETN0d8DpwKnAV+nJUjXA5d2NSafA/6T1oyzA61/0xu6z14NTNY2bMtEMpPkMFofncdW1bVjxwSt5u7Kqjqf1in8MZOFrqqfcXN/qyWTyzs/Z+yHPMlWwO/Tagepqm/QmhrHO0F/s6qeWlVLabVkDwH+ejXbv5UuPkcBh1fVDTOsssZjrKofV9Vjqmp/Wt+y19ISqzcBxwFPAN6c5HazKMudaUnaB7pZnwC2pPXFGrdDl5jeBvgarcZqXczmnI/Ow5XruG1pwTKJktZfTc5I8tnc+u640fTZWW73u2vY1x60pqCvJLmElvDslHYn3bKxctyZVvNy7C02UvXrqjqsqnauqt2BK4CTq+omWgf2XYF3VtW1VXUFrSZplOicAdyna6obuQ83N9mR5Hm0zusPr6qLxtY7m9a3Zjxmt4rfmCXAHbj1D/jId2lNTSNP7NY9qovFJbRapxmbuKrq27TY3bsr96FrOG9Xd81529I65R/Xbf/b3eYuSvK763iMfwv8S1VdSqu1WllVv6DV/t1tdUEZ8yza9/inurKcR0uiVne8vwbeBzwgyQ7dMc/mWj0HWJJkj7HN7cPYOafd/HB+VU3WWEmL14bulOXkNA0T7cd6S1pH5n/tXi+Z5WePBD441/ul3fV1LvCqbr1Rs889uvd3HJueRLvT7460Jr7R9v8K+PIM+92Z1kQYWsfoC4FHji0/j5YELaE1z3xsdIzA5rS+WofTOn8f1r3fvFt+KK2f0D1Xc8zHAp+m1VzsQmuue3637EnA3WmJwVJak9spa4jfyxjrJE2rYXnPRGzuC9xES1IeDLwAuEO3/j1oCcJfr8M5y8T270dLknYei8Fqj3FsO/ei9U3atHt/PPDHtLvfLgfu2M3/EvDq1ZTlbFqT6Xh5ngBcC9yeiY7l3fk6EvgpYzcGzPK4P0S7A2/r7lr8BbDXxLV21Ib+v+zkNOS0wQvg5DQNU/dDVBPTq7tlu9KaM3ZdzWf3oDWLXQl8fK722y3fC/gGrR/LmcATV7Odg5jh7ryZfry7+Q8Bzgeu6X6ID51Yvm/34/3z7gf9w8COY8v3A06mNV2dAuw3tuxHtKbBq8emfxpbvm33g3wVLXn729EPOvBn3ed/RUvEPgTcZQ3x24FWa7MVLYm5Adh7hvWOpzWX3Rv4FO2uvau7GLyBHnfFjW37FonK2o5xbJ0TgPuPvd+nO8eXAy8bm/9D4BEz7PcBwG+ApTMsO4OW3I7KNjoPV9L6xt2vx3Hejtbs+yu6JtKJ5acD+wz9f9fJaUNOoy8uSVqQkvwDcFlVvXVDl2WudXfwfbiqHrihy7ImSR4PPKuqnrqhyyINySRKkiSpBzuWS5Ik9WASJUmS1INJlCRJUg8mUZIkST2sbhTg9bLDDjvUsmXL5mPTkiRJc+rkk0++vNpTDNbJvCRRy5YtY+XKlfOxaUmSpDmV5Md9PmdzniRJUg+zSqKSbJ/kI0m+n+SsJAfOd8EkSZKm2Wyb894GfK6qnpJkc9qTwCVJkjZaa02ikmxHe87WcwCq6jraE8olSZI2WrNpztsNWAUck+TUJO9OsvXkSklWJFmZZOWqVavmvKCSJEnTZDZJ1BJgf+BdVbUf7Qner5xcqaqOrqrlVbV86dJ1vktQkiRpQZlNEnURcFFVfat7/xFaUiVJkrTRWmsSVVWXABcmuXs36+HAmfNaKkmSpCk327vz/gz4QHdn3nnAc+evSJIkSdNvVklUVZ0GLJ/fokiSJC0cjlguSZLUg0mUJElSDyZRkiRJPZhESZIk9WASJUmS1INJlCRJUg8mUZIkST2YREmSJPVgEiVJktSDSZQkSVIPJlGSJEk9mERJkiT1YBIlSZLUg0mUJElSDyZRkiRJPZhESZIk9WASJUmS1MOS2ayU5HzgKuBG4IaqWj6fhZIkSZp2s0qiOg+tqsvnrSSSJEkLiM15kiRJPcw2iSrgv5OcnGTFTCskWZFkZZKVq1atmrsSSpIkTaHZJlEPrqr9gUcDL0rykMkVquroqlpeVcuXLl06p4WUJEmaNrNKoqrqJ92/lwEfAw6Yz0JJkiRNu7UmUUm2TnLb0WvgkcD35rtgkiRJ02w2d+ftCHwsyWj9D1bV5+a1VJIkSVNurUlUVZ0H7DNAWSRJkhYMhziQJEnqwSRKkiSpB5MoSZKkHkyiJEmSejCJkiRJ6sEkSpIkqQeTKEmSpB5MoiRJknowiZIkSerBJEqSJKkHkyhJkqQeTKIkSZJ6MImSJEnqwSRKkiSpB5MoSZKkHkyiJEmSejCJkiRJ6mHWSVSSTZOcmuTT81kgSZKkhWBdaqIOB86ar4JIkiQtJLNKopLsAjwWePf8FkeSJGlhmG1N1FuBVwA3zV9RJEmSFo61JlFJHgdcVlUnr2W9FUlWJlm5atWqOSugJEnSNJpNTdSDgCckOR/4EPCwJP82uVJVHV1Vy6tq+dKlS+e4mJIkSdNlrUlUVf1lVe1SVcuApwFfrKpnznvJJEmSppjjREmSJPWwZF1WrqovAV+al5JIkiQtINZESZIk9WASJUmS1INJlCRJUg8mUZIkST2YREmSJPVgEiVJktSDSZQkSVIPJlGSJEk9mERJkiT1YBIlSZLUg0mUJElSDyZRkiRJPZhESZIk9WASJUmS1INJlCRJUg8mUZIkST2YREmSJPWw1iQqyZZJTkrynSRnJHnNEAWTJEmaZktmsc61wMOq6uokmwFfTfLZqvrmPJdNkiRpaq01iaqqAq7u3m7WTTWfhZIkSZp2s+oTlWTTJKcBlwGfr6pvzWupJEmSptyskqiqurGq9gV2AQ5Icu/JdZKsSLIyycpVq1bNcTElSZKmyzrdnVdVVwInAAfPsOzoqlpeVcuXLl06R8WTJEmaTrO5O29pku2711sBjwC+P8/lkiRJmmqzuTtvJ+D9STalJV0frqpPz2+xJEmSptts7s77LrDfAGWRJElaMByxXJIkqQeTKEmSpB5MoiRJknowiZIkSerBJEqSJKkHkyhJkqQeTKIkSZJ6MImSJEnqwSRKkiSpB5MoSZKkHkyiJEmSejCJkiRJ6sEkSpIkqQeTKEmSpB5MoiRJknowiZIkSerBJEqSJKmHtSZRSe6c5IQkZyY5I8nhQxRMkiRpmi2ZxTo3AH9eVackuS1wcpLPV9WZ81w2SZKkqbXWmqiq+mlVndK9vgo4C9h5vgsmSZI0zdapT1SSZcB+wLfmpTSSJEkLxKyTqCTbAB8FXlJVv5xh+YokK5OsXLVq1VyWUZIkaerMKolKshktgfpAVf3nTOtU1dFVtbyqli9dunQuyyhJkjR1ZnN3XoD3AGdV1Zvnv0iSJEnTbzY1UQ8CngU8LMlp3fSYeS6XJEnSVFvrEAdV9VUgA5RFkiRpwXDEckmSpB5MoiRJknowiZIkSerBJEqSJKkHkyhJkqQeTKIkSZJ6MImSJEnqwSRKkiSpB5MoSZKkHkyiJEmSejCJkiRJ6sEkSpIkqQeTKEmSpB5MoiRJknowiZIkSerBJEqSJKkHkyhJkqQe1ppEJXlvksuSfG+IAkmSJC0Es6mJeh9w8DyXQ5IkaUFZaxJVVV8GfjZAWSRJkhYM+0RJkiT1MGdJVJIVSVYmWblq1aq52qwkSdJUmrMkqqqOrqrlVbV86dKlc7VZSZKkqWRzniRJUg+zGeLg34FvAHdPclGS589/sSRJkqbbkrWtUFVPH6IgkiRJC4nNeZIkST2YREmSJPVgEiVJktSDSZQkSVIPJlGSJEk9mERJkiT1YBIlSZLUg0mUJElSDyZRkiRJPZhESZIk9WASJUmS1INJlCRJUg8mUZIkST2YREmSJPWwZEMXYF0te+VnNnQRejn/yMdu6CJIkqQ5ZE2UJElSDyZRkiRJPcyqOS/JwcDbgE2Bd1fVkfNaKk0Vm1CHZ8y1MfA6H54xn1trrYlKsinwj8CjgXsBT09yr/kumCRJ0jSbTXPeAcC5VXVeVV0HfAg4ZH6LJUmSNN1mk0TtDFw49v6ibp4kSdJGK1W15hWSpwAHV9Ufde+fBdy/qg6bWG8FsKJ7e3fg7Lkv7rzbAbh8QxdiI2PMh2fMh2fMh2fMh7eQY36Xqlq6rh+aTcfynwB3Hnu/SzfvFqrqaODodS3ANEmysqqWb+hybEyM+fCM+fCM+fCM+fA2xpjPpjnv28AeSXZLsjnwNOCT81ssSZKk6bbWmqiquiHJYcB/0YY4eG9VnTHvJZMkSZpisxonqqqOB46f57JMgwXdHLlAGfPhGfPhGfPhGfPhbXQxX2vHckmSJN2aj32RJEnqwSRKkiSpB5OoRSTJFkk2615nQ5dnY5Bkk+5f4z2QJJt3j6My7gPpvlu26F4b83k0im+SrZIs7V77Wz2AJNskWda9ntV17olZBJI8OMkZwP8ALwUoO7vNmyS3TXJEku8Cb+9m+39pHiXZMcnfJfka8DngxeB1Pp+S3CHJ65N8Efgi8NIkWxjz+VVVlWRf4ALgLzZwcRa9JLdL8toknwFOBZ4Ns/9umdXdeZou3V8lqaobk2xJGyn+L4EvA59Jch7wUb/s5k4X802q6gbaUB87AccChwJU1Y0bsHiL0vh1ThvwdyfgJcCPgS8m+U5VfXEDFnHRmbjOtwA2A/4GOB34OrAS+MKGK+HiM6plqqqbxmbfk/ZH8W4zLNN6mrjObwu8EnhkVZ2wrtvyr+cFZFS9WFU3jX60q+o3tIdEn1pVVwL/DziI9ugdraeJmN/Qvb4SeD3wZuDaJPuNr6v1M9N1DpwLvLyqvl1VlwEn0f3AaP2t5jq/sKpeXlVfr6qrgPOA32zIci4mEzGfTJKeAhwH/CbJfcfXV3+ruc5/DJzRTSTZaV22aRI1hdJsOtkO3lXz3jHJQUneluTxSbYDvgrcu1vtDOBawB/2dTDLmL81ySHd/FXdF9/pwKO61f3/tA7WIeZPqKorq+rq7qkJ0GpJrP1bR+sS87HPPDfJ9bTnot1p6DIvdOv63dI15Z0LnAZcSquVAr9fZm0dYv7kbtH3gG8mORl4Z5IVs+2H5kmZAkm2T/LYLiGimhur6qbxJCjJobQq9ccA/wt4LnANcDE3/0dbBVwC7Dza1nBHsnD0jPnDged380f/d04EHjJs6Rem9Yj5C7r5m1XVdUkOAO4CfMQ/EtZsfWPe+Sxw+27ek0Y/9prZesT8j7tFewIXV9WPgCuBFyZ5oV0GVm89Yv5H3aK3AkcCDwLeAPxv4Emz2bd9oqbDvWh9a64FvpDk7sAzgfsDX0nyTlo1+gOBw6vqU0m+ALwbCPB94GCAqvpZ9/nPDn8YC8rqYn4A8NU1xPy90KqDu/+c3wKO6Ob5Jbdm6xvz67vtHAEcVVVXD30AC9B6xRygqi7pXp6Z5CJgtySb2E9ntfp+n7+nq2ndE3hWkhXANrTv+Is3wHEsJH2v82MAqmolrb8fwElJzgR2nM11bk3UQLrqxdXF+3xa9e3duvcH0WqUjgB+Bfwt7eJYDnyn+4v8v2nn757Ax4F9kzyy+/yu3ec3aj1j/gpujvl13DrmoztnRn/t/AC4Jsmbkzw/yY7zdTwLwTzGfNQ8fSDty3BlkkOSPCPJbefreBaC+b7Ox/azKbAH8P2NPYGap+/zAHel3SF2JPB44H7At2nNeht194x5us5vmuE6X0LrU/yD2VznJlED6X5wV3dCVgE/pf0FAvB+4GTgT2nVjQ8GNu/Wu//YX+RXAYdU1TXAa4DnJLkC+G43bdTmIOabAZcBD5iI+WMAkjwgyYm0H5b9gOtp1e8brXmM+eO6139G+4vz3bS7Uq8Bfj3Hh7GgzGPMHwWQ5IVJvk3ro3MurfZ1ozaP3+dPrKrPVNUxVXUeLdk6nq7/38bcPWMer/NHAyR5dlqfqFOBs2k3r6yVzXlzbKbqvy573h14DnB9Vb1mfHlVXZ/kAmD/JLvSMuYX0vrb/D3tLrADgX8Bntq1+wa4nFaNCa026n+q3Tm2UZlFzK+rqv8zvnwi5nehfVm9kDZMxHjM302L+bbdR68A9uleXwC8pKpOnZcDm2IDx/xnwD261+8F3l5V35yXA5tiGyDm9+lenwocVlUbXfK0Ab7P9+72sUVVXVtVvwDeM4+HOHU2wHW+d/f6dOBF6/rdYk3UekiySVfF/Vujk5/k3mljOEE7gW+j/TXx/oltjKpnL6DVZOwM/B6wXVW9B7iBVsX75Kr6BO0ieDxtbIt30VVfdp3oruy2uelirfbtGfNjJ7YxGfM7cXPM383qY74tLeZ37fZ78SiB6mJ+i3ItFlMQ86Pohuyoqi+MvuRmKtdiMWUxP2mUQPndAszv9/ke3X6vnSzbHB3mVJmS63zPbr+njH23zPo6tyZqHSTJeHXqTFWLSV5JG+Pjl8CJSY6ljfZ7P+A/qur88fXHtvfTbtqXVqv0h0k+SjvRH6ddCND+mjkNuC+tmvKNk2WoRdTB2ZgPb9pjPirfGqr2F5wFFHOv84G/W1ZXtoVo2mPe5zo3iVqL8arF8ZOf9oy6RwFPpbWrvpHW5lq0uzC2Az4CbA+8hTZQ3ZrifUU3HQgcDRxGu+PuxKo6c2y9Lbt9bQd8BvjU+h7jtDHmw1tIMR8v30JmzIe3kGK+WCykmPe6zqvKaWyijYK8Ath5hmU7A4/rXj8S+DzwZGCvbt6jaB2Lv0C7o+I93cVwG1q17xPWsu/dgd1Ws2zTDR0bY754JmNuzI25MTfm6z9ZE9UZy5Z3oHViPRf4SZKHAltV1fG0PgIvTXI2rYpwCa0z2jXdZk6mZdEvqDZQ2vj2LwX2TnJCVV3VtbeGm58NRrW7McY/MxqivmoRVaOPGPPhGfPhGfPhGfPhbawxX5Sd1WZjFNyxII8GTzyHNibH6LlcD+Hmx3p8nVbteCfaYJZXAC8C3pJkVCV4EnBI2tDyj0gbP+iOtEezXAL8tlqzxp4NlmTXtJGYJ0/8oqhGB2O+IRjz4Rnz4Rnz4RnzZqOoicrEU7KT1nks3W2kSbagVT9uV1WvS3IJsHt3Ik4DHp3kDlV1WZKf0m6J/HpVPaXb3m1pbbAHAn9Cuw3zM7SxKz4BXF1Vn5wo05bAY4GHAfvTbm99R1fOBf8fzZgPz5gPz5gPz5gPz5iv3qJMoiZPeI3dAZDk9lV1RZI70Hr+P7Cqfp7kOmD77mSeRxsjZWfgh7TbKu9Da6e9kPbMneOSbE97xs6+tDbbb3UXyWuBv5s8kbnl+BePoI0s/k+0EYCvZwFbQDG/M8bcmPdkzIe3gGLu9/lGeJ0vyua8alV8o4x56yQPS3JUknOAY5IcWFWX0YaKP6j72A9pT4bfo3t9He2RKufQqh8f2623La29dydgx279/wSe1W2Tqrq+y9JvMQbG+IVYVZ+qqrdU1ekL/T8cLKiYv9WYG/O+jPnwFlDM/T7fCK/zRZdEJdku7XlaH0xyP9rJ+gdaz/w9gW8Af5zkbsAJ3NxWez6trXUPWnvu5cA9q+o62ijJ+yb5Hq0j20uAs6vqq1W1oqo+WlW/nCxLjbXXLmbGfHjGfHjGfHjGfHjGfN0siOa85Lftr7cYqGuG9TYBXk2rQvwy7aRuAnyfNrw7wL/T2lsPBL4EPAugqs5Ncn/gqqo6LsmFwD5Jtq2qc5L8wShLnmGft8iQFwNjPjxjPjxjPjxjPjxjPn+mNolKMrp18abRSR/9m2RP4PKq+tnERfEQ4MFVdb+x7WwBrKS1V1NV5yfZHfheVZ2UNrz7G4Db09pqr0nrsHYhrbPa7sBpo5M/ecIX6omfiTEfnjEfnjEfnjEfnjEfxtQkUV1gx8d7KNp4EaRVG+5A66n/oe4jpwPPm8iqf0Y33kTaaKg3Vbtz4HxgRZIPVNV3aJ3RRln104FDuu19oqqu6j5/Ke0hhrsDp40utIV+wscZ8+EZ8+EZ8+EZ8+EZ8w1jgyVRmXhS82Rg055s/SJaR7XHA7+hjRHxpKq6MMkPkuxfVaeMfewK4NokD6qqr3XbGY1b8WPgTWl3FJwAnNLt9zvAd8b2O8rKLwL+m+5CmbjQFiRjPjxjPjxjPjxjPjxjPh0GT6K6E3AQ8B/d+1Fb7UHAo2nZ8quq6uIkhwBnVtX+SfajtcVu023qBOCBSU6rm6sFf5LkFODwbnsPpbXrvhX4JrBJVb12hjLNVO15HfC1uY/A8Iz58Iz58Iz58Iz58Iz5dJn3u/PStX+OVGsXXQE8LckrgG2T3BV4Ji2bPR54c5Kdgc8BP+1O0EW0Jznfv9vUt4B9gK2TbJbk0d38v6UNuHU74M3A64Ff0W6x3L0rU8bLVc2iqWI05sMz5sMz5sMz5sMz5tNt3pOoUWCT3CXJg5PsT+v5/xrgbrQT8xLgYuBqWk//5bTxI04HdgG2oj2U8GxaRzVow8cfQDvRWwIPS7JlVV1XVV+pqj+vquOrjTdxI61a8U1dmRb1CTfmwzPmwzPmwzPmwzPmU67W72nNYTVPRqadtK2A+wIn0k7mq4A7Ai8H3jS27l/R2k1fDTwB2KKbvyvtxN21e/844CujfQJPBLZezf43oVU9rtcxTttkzI25MTfmxnxxTMZ84U9zfUFs1/27LfAu4BnAocAbJtbbD/gvYBmtX9YjgC9PrPN73b9fBw7pXt8euPvoBM9wMWZDB3TwE2jMjflGMBlzY74xTMZ84U29OpaPdWTbE3gKsDmtGnFPWlvtjt1F8AVaFv3crv30UuCcqvpk2lDuS6vqfODzSf4iydtpVYv7Ax+nZd/PpT2Hh6q6gnb3ADVRlVjdVbBYGfPhGfPhGfPhGfPhGfPFI33jluQewPuAz9NO9GXAscDzaO2uZwF7VdVvkiyn3RHwAODZtGfoPJ327Jw7AP9SVZ9P8ge0hxB+oaouXI/jWpSM+fCM+fCM+fCM+fCM+eKwPkMc3BU4F3g/8JOq+nWSI4GXAZ8GPgrsmOSCqloJvx186+7AZsBRtKrKK2ljUFBVx61HeTYGxnx4xnx4xnx4xnx4xnwRWJ+aqNsCx9BGJN2Edgvkm2nDxr8GOK6qXpxkK1p15atomfZxwDtXV3WYiQHEdDNjPjxjPjxjPjxjPjxjvjj0TqJusZFWLfk82lObjwLeBuxUVY9JEtrgX9dX1ZUzfHZT2tDytseuA2M+PGM+PGM+PGM+PGO+cPVuzutO7J2AvWmDd+0H/GlVXZ3kJOB2STatNr7EqrHPbNLNA2D8tdbMmA/PmA/PmA/PmA/PmC8OvQfb7LLeOwMvAG4AXlFVP0iyB/BC4JSqurE76b/9jCe8P2M+PGM+PGM+PGM+PGO+OMxJc94tNtjuDtgLeFu12yk1z4z58Iz58Iz58Iz58Iz5wrLeSdSoepGWJNuZbQDGfHjGfHjGfHjGfHjGfGGb85ooSZKkjcG8P4BYkiRpMTKJkiRJ6sEkSpIkqQeTKEmSpB5MoiRJknowiZIkSerh/wOaZo8FhbaIiAAAAABJRU5ErkJggg==",
"text/plain": [
"<Figure size 720x216 with 1 Axes>"
]
@@ -1320,7 +1327,7 @@
},
{
"data": {
- "image/png": "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\n",
+ "image/png": "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",
"text/plain": [
"<Figure size 720x216 with 1 Axes>"
]
@@ -1332,7 +1339,7 @@
},
{
"data": {
- "image/png": "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\n",
+ "image/png": "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",
"text/plain": [
"<Figure size 720x216 with 1 Axes>"
]
@@ -1344,7 +1351,7 @@
},
{
"data": {
- "image/png": "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\n",
+ "image/png": "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",
"text/plain": [
"<Figure size 720x216 with 1 Axes>"
]
@@ -1356,7 +1363,7 @@
},
{
"data": {
- "image/png": "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\n",
+ "image/png": "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",
"text/plain": [
"<Figure size 720x216 with 1 Axes>"
]
@@ -1368,7 +1375,7 @@
},
{
"data": {
- "image/png": "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\n",
+ "image/png": "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",
"text/plain": [
"<Figure size 720x216 with 1 Axes>"
]
@@ -1380,7 +1387,7 @@
},
{
"data": {
- "image/png": "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\n",
+ "image/png": "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",
"text/plain": [
"<Figure size 720x216 with 1 Axes>"
]
@@ -1392,7 +1399,7 @@
},
{
"data": {
- "image/png": "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\n",
+ "image/png": "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",
"text/plain": [
"<Figure size 720x216 with 1 Axes>"
]
@@ -1404,7 +1411,7 @@
},
{
"data": {
- "image/png": "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\n",
+ "image/png": "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",
"text/plain": [
"<Figure size 720x216 with 1 Axes>"
]
@@ -1416,7 +1423,7 @@
},
{
"data": {
- "image/png": "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\n",
+ "image/png": "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",
"text/plain": [
"<Figure size 720x216 with 1 Axes>"
]
@@ -1428,7 +1435,7 @@
},
{
"data": {
- "image/png": "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\n",
+ "image/png": "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",
"text/plain": [
"<Figure size 720x216 with 1 Axes>"
]
@@ -1440,7 +1447,7 @@
},
{
"data": {
- "image/png": "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\n",
+ "image/png": "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",
"text/plain": [
"<Figure size 720x216 with 1 Axes>"
]
@@ -1452,7 +1459,7 @@
},
{
"data": {
- "image/png": "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\n",
+ "image/png": "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",
"text/plain": [
"<Figure size 720x216 with 1 Axes>"
]
@@ -1464,7 +1471,7 @@
},
{
"data": {
- "image/png": "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\n",
+ "image/png": "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",
"text/plain": [
"<Figure size 720x216 with 1 Axes>"
]
@@ -1476,7 +1483,7 @@
},
{
"data": {
- "image/png": "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\n",
+ "image/png": "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",
"text/plain": [
"<Figure size 720x216 with 1 Axes>"
]
@@ -1488,7 +1495,7 @@
},
{
"data": {
- "image/png": "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\n",
+ "image/png": "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",
"text/plain": [
"<Figure size 720x216 with 1 Axes>"
]
@@ -1500,7 +1507,7 @@
},
{
"data": {
- "image/png": "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\n",
+ "image/png": "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",
"text/plain": [
"<Figure size 720x216 with 1 Axes>"
]
@@ -1512,7 +1519,7 @@
},
{
"data": {
- "image/png": "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\n",
+ "image/png": "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",
"text/plain": [
"<Figure size 720x216 with 1 Axes>"
]
@@ -1524,7 +1531,7 @@
},
{
"data": {
- "image/png": "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\n",
+ "image/png": "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",
"text/plain": [
"<Figure size 720x216 with 1 Axes>"
]
@@ -1536,7 +1543,7 @@
},
{
"data": {
- "image/png": "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\n",
+ "image/png": "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",
"text/plain": [
"<Figure size 720x216 with 1 Axes>"
]
@@ -1548,7 +1555,7 @@
},
{
"data": {
- "image/png": "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\n",
+ "image/png": "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",
"text/plain": [
"<Figure size 720x216 with 1 Axes>"
]
@@ -1560,7 +1567,7 @@
},
{
"data": {
- "image/png": "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\n",
+ "image/png": "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",
"text/plain": [
"<Figure size 720x216 with 1 Axes>"
]
@@ -1596,7 +1603,7 @@
"outputs": [
{
"data": {
- "image/png": "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\n",
+ "image/png": "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",
"text/plain": [
"<Figure size 432x288 with 1 Axes>"
]
@@ -1608,7 +1615,7 @@
},
{
"data": {
- "image/png": "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\n",
+ "image/png": "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",
"text/plain": [
"<Figure size 432x288 with 1 Axes>"
]
@@ -1620,7 +1627,7 @@
},
{
"data": {
- "image/png": "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\n",
+ "image/png": "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",
"text/plain": [
"<Figure size 432x288 with 1 Axes>"
]
@@ -1632,7 +1639,7 @@
},
{
"data": {
- "image/png": "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\n",
+ "image/png": "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",
"text/plain": [
"<Figure size 432x288 with 1 Axes>"
]
@@ -1644,7 +1651,7 @@
},
{
"data": {
- "image/png": "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\n",
+ "image/png": "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",
"text/plain": [
"<Figure size 432x288 with 1 Axes>"
]
@@ -1709,4 +1716,4 @@
},
"nbformat": 4,
"nbformat_minor": 1
-} \ No newline at end of file
+}
diff --git a/netdata-installer.sh b/netdata-installer.sh
index 94745a29..e45eead1 100755
--- a/netdata-installer.sh
+++ b/netdata-installer.sh
@@ -163,10 +163,6 @@ banner_nonroot_install() {
$PROGRAM ${@} --install-prefix /tmp
- or
-
- $PROGRAM ${@} --install /tmp
-
or, run the installer as root:
sudo $PROGRAM ${@}
@@ -203,8 +199,7 @@ usage() {
USAGE: ${PROGRAM} [options]
where options include:
- --install <path> Install netdata in <path>. Ex. --install /opt will put netdata in /opt/netdata, this option is deprecated and will be removed in a future version, please use --install-prefix instead.
- --install-prefix <path> Install netdata in <path>. Ex. --install-prefix /opt will put netdata in /opt/netdata.
+ --install-prefix <path> Install netdata in <path>. Ex. --install-prefix /opt will put netdata in /opt/netdata.
--dont-start-it Do not (re)start netdata after installation.
--dont-wait Run installation in non-interactive mode.
--stable-channel Use packages from GitHub release pages instead of nightly updates.
@@ -337,8 +332,6 @@ while [ -n "${1}" ]; do
NETDATA_CONFIGURE_OPTIONS="$(echo "${NETDATA_CONFIGURE_OPTIONS%--disable-ml)}" | sed 's/$/ --disable-ml/g')"
NETDATA_ENABLE_ML=0
;;
- "--enable-ml-tests") NETDATA_CONFIGURE_OPTIONS="$(echo "${NETDATA_CONFIGURE_OPTIONS%--enable-ml-tests)}" | sed 's/$/ --enable-ml-tests/g')" ;;
- "--disable-ml-tests") NETDATA_CONFIGURE_OPTIONS="$(echo "${NETDATA_CONFIGURE_OPTIONS%--disable-ml-tests)}" | sed 's/$/ --disable-ml-tests/g')" ;;
"--disable-lto") NETDATA_CONFIGURE_OPTIONS="$(echo "${NETDATA_CONFIGURE_OPTIONS%--disable-lto)}" | sed 's/$/ --disable-lto/g')" ;;
"--disable-x86-sse") NETDATA_CONFIGURE_OPTIONS="$(echo "${NETDATA_CONFIGURE_OPTIONS%--disable-x86-sse)}" | sed 's/$/ --disable-x86-sse/g')" ;;
"--disable-telemetry") NETDATA_DISABLE_TELEMETRY=1 ;;
@@ -366,10 +359,6 @@ while [ -n "${1}" ]; do
"--build-json-c")
NETDATA_BUILD_JSON_C=1
;;
- "--install")
- NETDATA_PREFIX="${2}/netdata"
- shift 1
- ;;
"--install-prefix")
NETDATA_PREFIX="${2}/netdata"
shift 1
@@ -440,7 +429,7 @@ if [ "$(uname -s)" = "Linux" ] && [ -f /proc/meminfo ]; then
target_ram="$(echo "${target_ram}" | awk '{$1/=1024*1024*1024;printf "%.2fGiB\n",$1}')"
total_ram="$(echo "${total_ram}" | awk '{$1/=1024*1024*1024;printf "%.2fGiB\n",$1}')"
run_failed "Netdata needs ${target_ram} of RAM to safely install, but this system only has ${total_ram}. Try reducing the number of processes used for the install using the \$MAKEOPTS variable."
- exit_reason "Insufficent RAM to safely install." I000F
+ exit_reason "Insufficient RAM to safely install." I000F
exit 2
fi
fi
@@ -1089,6 +1078,11 @@ if [ "$(id -u)" -eq 0 ]; then
# shellcheck disable=SC2086
portable_add_user_to_group ${g} netdata && NETDATA_ADDED_TO_GROUPS="${NETDATA_ADDED_TO_GROUPS} ${g}"
done
+ # Netdata must be able to read /etc/pve/qemu-server/* and /etc/pve/lxc/*
+ # for reading VMs/containers names, CPU and memory limits on Proxmox.
+ if [ -d "/etc/pve" ]; then
+ portable_add_user_to_group "www-data" netdata && NETDATA_ADDED_TO_GROUPS="${NETDATA_ADDED_TO_GROUPS} www-data"
+ fi
else
run_failed "The installer does not run as root. Nothing to do for user and groups"
fi
@@ -1206,8 +1200,8 @@ run chmod 770 "${NETDATA_CLAIMING_DIR}"
if [ "$(id -u)" -eq 0 ]; then
# find the admin group
admin_group=
- test -z "${admin_group}" && getent group root > /dev/null 2>&1 && admin_group="root"
- test -z "${admin_group}" && getent group daemon > /dev/null 2>&1 && admin_group="daemon"
+ test -z "${admin_group}" && get_group root > /dev/null 2>&1 && admin_group="root"
+ test -z "${admin_group}" && get_group daemon > /dev/null 2>&1 && admin_group="daemon"
test -z "${admin_group}" && admin_group="${NETDATA_GROUP}"
run chown "${NETDATA_USER}:${admin_group}" "${NETDATA_LOG_DIR}"
diff --git a/netdata.spec.in b/netdata.spec.in
index cbbe6ab7..1c6aa61d 100644
--- a/netdata.spec.in
+++ b/netdata.spec.in
@@ -40,7 +40,7 @@ AutoReqProv: yes
#
# Conditional build:
%bcond_without systemd # systemd
-%bcond_with netns # build with netns support (cgroup-network)
+%bcond_without netns # build with netns support (cgroup-network)
%if 0%{?fedora} || 0%{?rhel} >= 7 || 0%{?suse_version} >= 1140
%else
@@ -504,6 +504,11 @@ rm -rf "${RPM_BUILD_ROOT}"
%attr(0750,root,netdata) %{_libexecdir}/%{name}/plugins.d/cgroup-network-helper.sh
%endif
+# ebpf plugin
+%if 0%{?_have_ebpf}
+%attr(4750,root,netdata) %{_libexecdir}/%{name}/plugins.d/ebpf.plugin
+%endif
+
# perf plugin
# This should be CAP_PERFMON once RPM finally learns about it, but needs to be CAP_SYS_ADMIN for now.
# %caps(cap_perfmon=ep) %attr(0750,root,netdata) %{_libexecdir}/%{name}/plugins.d/perf.plugin
diff --git a/packaging/PLATFORM_SUPPORT.md b/packaging/PLATFORM_SUPPORT.md
index 106c8089..62e208c7 100644
--- a/packaging/PLATFORM_SUPPORT.md
+++ b/packaging/PLATFORM_SUPPORT.md
@@ -1,6 +1,12 @@
<!--
title: "Netdata platform support policy"
-custom_edit_url: https://github.com/netdata/netdata/edit/master/packaging/PLATFORM_SUPPORT.md
+sidebar_label: "Supported platforms"
+custom_edit_url: "https://github.com/netdata/netdata/edit/master/packaging/PLATFORM_SUPPORT.md"
+learn_status: "Published"
+sidebar_position: "30"
+learn_topic_type: "Getting started"
+learn_rel_path: "Getting started"
+learn_docs_purpose: "Present all the supported platform in the Netdata solution"
-->
# Netdata platform support policy
@@ -33,7 +39,8 @@ The following table shows a general outline of the various support tiers and cat
| Previously Supported | Users asked to upgrade | None | None | Yes, but only already published versions | Best Effort |
- ‘Bug Support’: How we handle of platform-specific bugs.
-- ‘Guaranteed Configurations’: Which runtime configurations for the agent we try to guarantee will work with minimal effort from users.
+- ‘Guaranteed Configurations’: Which runtime configurations for the agent we try to guarantee will work with minimal
+ effort from users.
- ‘CI Coverage’: What level of coverage we provide for the platform in CI.
- ‘Native Packages’: Whether we provide native packages for the system package manager for the platform.
- ‘Static Build Support’: How well our static builds are expected to work on the platform.
@@ -44,33 +51,32 @@ The following table shows a general outline of the various support tiers and cat
Platforms in the core support tier are our top priority. They are covered rigorously in our CI, usually
include official binary packages, and any platform-specific bugs receive a high priority. From the perspective
-of our developers, platforms in the core support tier _must_ work, with almost no exceptions. Our [static
-builds](#static-builds) are expected to work on these platforms if available. Source-based installs are expected
+of our developers, platforms in the core support tier _must_ work, with almost no exceptions.
+Our [static builds](#static-builds) are expected to work on these platforms if available. Source-based installs are
+expected
to work on these platforms with minimal user effort.
-| Platform | Version | Official Native Packages | Notes |
-| -------- | ------- | ------------------------ | ----- |
-| Alpine Linux | 3.17 | No | The latest release of Alpine Linux is guaranteed to remain at **Core** tier due to usage for our Docker images |
-| Alma Linux | 9.x | x86\_64, AArch64 | Also includes support for Rocky Linux and other ABI compatible RHEL derivatives |
-| Alma Linux | 8.x | x86\_64, AArch64 | Also includes support for Rocky Linux and other ABI compatible RHEL derivatives |
-| CentOS | 7.x | x86\_64 | |
-| Docker | 19.03 or newer | x86\_64, i386, ARMv7, AArch64, POWER8+ | See our [Docker documentation](/packaging/docker/README.md) for more info on using Netdata on Docker |
-| Debian | 11.x | x86\_64, i386, ARMv7, AArch64 | |
-| Debian | 10.x | x86\_64, i386, ARMv7, AArch64 | |
-| Fedora | 37 | x86\_64, AArch64 | |
-| Fedora | 36 | x86\_64, ARMv7, AArch64 | |
-| Fedora | 35 | x86\_64, ARMv7, AArch64 | |
-| openSUSE | Leap 15.4 | x86\_64, AArch64 | |
-| openSUSE | Leap 15.3 | x86\_64, AArch64 | |
-| Oracle Linux | 9.x | x86\_64, AArch64 | |
-| Oracle Linux | 8.x | x86\_64, AArch64 | |
-| Red Hat Enterprise Linux | 9.x | x86\_64, AArch64 | |
-| Red Hat Enterprise Linux | 8.x | x86\_64, AArch64 | |
-| Red Hat Enterprise Linux | 7.x | x86\_64 | |
-| Ubuntu | 22.10 | x86\_64, ARMv7, AArch64 | |
-| Ubuntu | 22.04 | x86\_64, ARMv7, AArch64 | |
-| Ubuntu | 20.04 | x86\_64, ARMv7, AArch64 | |
-| Ubuntu | 18.04 | x86\_64, i386, ARMv7, AArch64 | |
+| Platform | Version | Official Native Packages | Notes |
+|--------------------------|----------------|----------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------|
+| Alpine Linux | 3.17 | No | The latest release of Alpine Linux is guaranteed to remain at **Core** tier due to usage for our Docker images |
+| Alma Linux | 9.x | x86\_64, AArch64 | Also includes support for Rocky Linux and other ABI compatible RHEL derivatives |
+| Alma Linux | 8.x | x86\_64, AArch64 | Also includes support for Rocky Linux and other ABI compatible RHEL derivatives |
+| CentOS | 7.x | x86\_64 | |
+| Docker | 19.03 or newer | x86\_64, i386, ARMv7, AArch64, POWER8+ | See our [Docker documentation](https://github.com/netdata/netdata/blob/master/packaging/docker/README.md) for more info on using Netdata on Docker |
+| Debian | 11.x | x86\_64, i386, ARMv7, AArch64 | |
+| Debian | 10.x | x86\_64, i386, ARMv7, AArch64 | |
+| Fedora | 37 | x86\_64, AArch64 | |
+| Fedora | 36 | x86\_64, AArch64 | |
+| openSUSE | Leap 15.4 | x86\_64, AArch64 | |
+| Oracle Linux | 9.x | x86\_64, AArch64 | |
+| Oracle Linux | 8.x | x86\_64, AArch64 | |
+| Red Hat Enterprise Linux | 9.x | x86\_64, AArch64 | |
+| Red Hat Enterprise Linux | 8.x | x86\_64, AArch64 | |
+| Red Hat Enterprise Linux | 7.x | x86\_64 | |
+| Ubuntu | 22.10 | x86\_64, ARMv7, AArch64 | |
+| Ubuntu | 22.04 | x86\_64, ARMv7, AArch64 | |
+| Ubuntu | 20.04 | x86\_64, ARMv7, AArch64 | |
+| Ubuntu | 18.04 | x86\_64, i386, ARMv7, AArch64 | |
### Intermediate
@@ -81,13 +87,13 @@ platforms that we officially support ourselves to the intermediate tier. Our [st
expected to work on these platforms if available. Source-based installs are expected to work on these platforms
with minimal user effort.
-| Platform | Version | Official Native Packages | Notes |
-| -------- | ------- | ------------------------ | ----- |
-| Alpine Linux | 3.16 | No | |
-| Alpine Linux | 3.15 | No | |
-| Alpine Linux | 3.14 | No | |
-| Arch Linux | Latest | No | We officially recommend the community packages available for Arch Linux |
-| Manjaro Linux | Latest | No | We officially recommend the community packages available for Arch Linux |
+| Platform | Version | Official Native Packages | Notes |
+|---------------|---------|--------------------------|-------------------------------------------------------------------------|
+| Alpine Linux | 3.16 | No | |
+| Alpine Linux | 3.15 | No | |
+| Alpine Linux | 3.14 | No | |
+| Arch Linux | Latest | No | We officially recommend the community packages available for Arch Linux |
+| Manjaro Linux | Latest | No | We officially recommend the community packages available for Arch Linux |
### Community
@@ -97,19 +103,19 @@ to add support for a new platform, that platform generally will start in this ti
are expected to work on these platforms if available. Source-based installs are usually expected to work on these
platforms, but may require some extra effort from users.
-| Platform | Version | Official Native Packages | Notes |
-| -------- | ------- | ------------------------ | ----- |
-| Alpine Linux | Edge | No | |
-| Clear Linux | Latest | No | |
-| Debian | Sid | No | |
-| Fedora | Rawhide | No | |
-| FreeBSD | 13-STABLE | No | Netdata is included in the FreeBSD Ports Tree, and this is the recommended installation method on FreeBSD |
-| FreeBSD | 12-STABLE | No | Netdata is included in the FreeBSD Ports Tree, and this is the recommended installation method on FreeBSD |
-| Gentoo | Latest | No | |
-| macOS | 12 | No | Currently only works for Intel-based hardware. Requires Homebrew for dependencies |
-| macOS | 11 | No | Currently only works for Intel-based hardware. Requires Homebrew for dependencies. |
-| macOS | 10.15 | No | Requires Homebrew for dependencies. |
-| openSUSE | Tumbleweed | No | |
+| Platform | Version | Official Native Packages | Notes |
+|--------------|------------|--------------------------|-----------------------------------------------------------------------------------------------------------|
+| Alpine Linux | Edge | No | |
+| Clear Linux | Latest | No | |
+| Debian | Sid | No | |
+| Fedora | Rawhide | No | |
+| FreeBSD | 13-STABLE | No | Netdata is included in the FreeBSD Ports Tree, and this is the recommended installation method on FreeBSD |
+| FreeBSD | 12-STABLE | No | Netdata is included in the FreeBSD Ports Tree, and this is the recommended installation method on FreeBSD |
+| Gentoo | Latest | No | |
+| macOS | 12 | No | Currently only works for Intel-based hardware. Requires Homebrew for dependencies |
+| macOS | 11 | No | Currently only works for Intel-based hardware. Requires Homebrew for dependencies. |
+| macOS | 10.15 | No | Requires Homebrew for dependencies. |
+| openSUSE | Tumbleweed | No | |
## Third-party supported platforms
@@ -138,23 +144,22 @@ Platforms that meet these criteria will be immediately transitioned to the **Pre
with no prior warning from Netdata and no deprecation notice, unlike those being dropped for technical reasons,
as our end of support should already coincide with the end of the normal support lifecycle for that platform.
-On occasion, we may also drop support for a platform due to technical limitations. In such cases, this will be
+On occasion, we may also drop support for a platform due to technical limitations. In such cases, this will be
announced in the release notes of the next stable release with a deprecation notice. The platform will be supported
for _that release_, and will be removed from nightlies some time before the next release after that one.
This is a list of platforms that we have supported in the recent past but no longer officially support:
-| Platform | Version | Notes |
-| -------- | ------- | ----- |
-| Alpine Linux | 3.13 | EOL as of 2022-11-01 |
-| Alpine Linux | 3.12 | EOL as of 2022-05-01 |
-| Debian | 9.x | EOL as of 2022-06-30 |
-| Fedora | 34 | EOL as of 2022-06-07 |
-| Fedora | 33 | EOL as of 2021-11-30 |
-| FreeBSD | 11-STABLE | EOL as of 2021-10-30 |
-| openSUSE | Leap 15.2 | EOL as of 2021-12-01 |
-| Ubuntu | 21.10 | EOL as of 2022-07-31 |
-| Ubuntu | 21.04 | EOL as of 2022-01-01 |
+| Platform | Version | Notes |
+|--------------|-----------|----------------------|
+| Alpine Linux | 3.13 | EOL as of 2022-11-01 |
+| Alpine Linux | 3.12 | EOL as of 2022-05-01 |
+| Debian | 9.x | EOL as of 2022-06-30 |
+| Fedora | 35 | EOL as of 2022-12-13 |
+| Fedora | 34 | EOL as of 2022-06-07 |
+| openSUSE | Leap 15.3 | EOL as of 2022-12-01 |
+| Ubuntu | 21.10 | EOL as of 2022-07-31 |
+| Ubuntu | 21.04 | EOL as of 2022-01-01 |
## Static builds
diff --git a/packaging/building-native-packages-locally.md b/packaging/building-native-packages-locally.md
index d4949cf5..84a0fb4d 100644
--- a/packaging/building-native-packages-locally.md
+++ b/packaging/building-native-packages-locally.md
@@ -2,7 +2,6 @@
title: How to build native (DEB/RPM) packages locally for testing
description: Instructions for developers who need to build native packages locally for testing.
custom_edit_url: https://github.com/netdata/netdata/edit/master/packaging/building-native-packages-locally.md
-keywords: [Netdata native package, Netdata RPM, Netdata DEB, Testing native packages Netdata]
-->
# How to build native (DEB/RPM) packages locally for testing
diff --git a/packaging/current_libbpf.checksums b/packaging/current_libbpf.checksums
index 9a8b8f8c..e0b91c0c 100644
--- a/packaging/current_libbpf.checksums
+++ b/packaging/current_libbpf.checksums
@@ -1 +1 @@
-63fe4ac3f6807e8ff4cd3af2ffae0091eb177fb7f6aca2f03d3f201a609b988c v1.0.1_netdata.tar.gz
+f2a8214c967153fcbb7a8f2af59c23a38f6e175384878dd37648649c5d8182c4 v1.1_netdata.tar.gz
diff --git a/packaging/current_libbpf.version b/packaging/current_libbpf.version
index bb58ffc4..b0797d5a 100644
--- a/packaging/current_libbpf.version
+++ b/packaging/current_libbpf.version
@@ -1 +1 @@
-1.0.1_netdata
+1.1_netdata
diff --git a/packaging/docker/Dockerfile b/packaging/docker/Dockerfile
index 287c592b..ce5a0b93 100644
--- a/packaging/docker/Dockerfile
+++ b/packaging/docker/Dockerfile
@@ -76,16 +76,13 @@ RUN mkdir -p /opt/src /var/log/netdata && \
ln -sf /dev/stdout /var/log/netdata/access.log && \
ln -sf /dev/stdout /var/log/netdata/debug.log && \
ln -sf /dev/stderr /var/log/netdata/error.log && \
- # fping from alpine apk is on a different location. Moving it.
- ln -snf /usr/sbin/fping /usr/local/bin/fping && \
- chmod 4755 /usr/local/bin/fping && \
+ ln -sf /dev/stdout /var/log/netdata/collector.log && \
# Add netdata user
addgroup -g ${NETDATA_GID} -S "${DOCKER_GRP}" && \
adduser -S -H -s /usr/sbin/nologin -u ${NETDATA_GID} -h /etc/netdata -G "${DOCKER_GRP}" "${DOCKER_USR}"
# Fix handling of config directory
# Long-term this should leverage BuildKit’s mount option.
-COPY --from=builder /wheels /wheels
COPY --from=builder /app /
# Apply the permissions as described in
@@ -112,8 +109,6 @@ RUN chown -R root:root \
# Group write permissions due to: https://github.com/netdata/netdata/pull/6543
find /var/lib/netdata /var/cache/netdata -type d -exec chmod 0770 {} \; && \
find /var/lib/netdata /var/cache/netdata -type f -exec chmod 0660 {} \; && \
- pip --no-cache-dir install /wheels/* && \
- rm -rf /wheels && \
cp -va /etc/netdata /etc/netdata.stock
ENV NETDATA_LISTENER_PORT 19999
@@ -124,3 +119,11 @@ ENTRYPOINT ["/usr/sbin/run.sh"]
HEALTHCHECK --interval=60s --timeout=10s --retries=3 CMD /usr/sbin/health.sh
ONBUILD ENV NETDATA_OFFICIAL_IMAGE=false
+
+LABEL org.opencontainers.image.authors="Netdatabot <bot@netdata.cloud>"
+LABEL org.opencontainers.image.url="https://netdata.cloud"
+LABEL org.opencontainers.image.documentation="https://learn.netdata.cloud"
+LABEL org.opencontainers.image.source="https://github.com/netdata/netdata"
+LABEL org.opencontainers.image.title="Netdata Agent"
+LABEL org.opencontainers.image.description="Official Netdata Agent Docker Image"
+LABEL org.opencontainers.image.vendor="Netdata Inc."
diff --git a/packaging/docker/README.md b/packaging/docker/README.md
index d00262a1..aec5723e 100644
--- a/packaging/docker/README.md
+++ b/packaging/docker/README.md
@@ -1,7 +1,11 @@
<!--
title: "Install Netdata with Docker"
-date: 2020-04-23
-custom_edit_url: https://github.com/netdata/netdata/edit/master/packaging/docker/README.md
+date: "2020-04-23"
+custom_edit_url: "https://github.com/netdata/netdata/edit/master/packaging/docker/README.md"
+sidebar_label: "Install Netdata with Docker"
+learn_status: "Published"
+learn_topic_type: "Tasks"
+learn_rel_path: "Installation"
-->
# Install the Netdata Agent with Docker
@@ -12,7 +16,7 @@ you get set up quickly, and doesn't install anything permanent on the system, wh
See our full list of Docker images at [Docker Hub](https://hub.docker.com/r/netdata/netdata).
Starting with v1.30, Netdata collects anonymous usage information by default and sends it to a self-hosted PostHog instance within the Netdata infrastructure. Read
-about the information collected, and learn how to-opt, on our [anonymous statistics](/docs/anonymous-statistics.md)
+about the information collected, and learn how to-opt, on our [anonymous statistics](https://github.com/netdata/netdata/blob/master/docs/anonymous-statistics.md)
page.
The usage statistics are _vital_ for us, as we use them to discover bugs and prioritize new features. We thank you for
@@ -37,6 +41,21 @@ and unfortunately not something we can realistically work around.
## Create a new Netdata Agent container
+> **Notice**: all `docker run` commands and `docker-compose` configurations explicitly set the `nofile` limit. This is
+> required on some distros until [14177](https://github.com/netdata/netdata/issues/14177) is resolved. Failure to do so
+> may cause a task running in a container to hang and consume 100% of the CPU core.
+
+<details>
+<summary>What are these "some distros"?</summary>
+
+If `LimitNOFILE=infinity` results in an open file limit of 1073741816:
+
+```bash
+[fedora37 ~]$ docker run --rm busybox grep open /proc/self/limits
+Max open files 1073741816 1073741816 files
+```
+</details>
+
You can create a new Agent container using either `docker run` or Docker Compose. After using either method, you can
visit the Agent dashboard `http://NODE:19999`.
@@ -61,6 +80,7 @@ docker run -d --name=netdata \
--restart unless-stopped \
--cap-add SYS_PTRACE \
--security-opt apparmor=unconfined \
+ --ulimit nofile=4096 \
netdata/netdata
```
@@ -81,6 +101,9 @@ services:
- SYS_PTRACE
security_opt:
- apparmor:unconfined
+ ulimits:
+ nofile:
+ soft: 4096
volumes:
- netdataconfig:/etc/netdata
- netdatalib:/var/lib/netdata
@@ -153,7 +176,7 @@ to restart the container: `docker restart netdata`.
### Host-editable configuration
-> **Warning**: [edit-config](/docs/configure/nodes.md#the-netdata-config-directory) script doesn't work when executed on
+> **Warning**: [edit-config](https://github.com/netdata/netdata/blob/master/docs/configure/nodes.md#the-netdata-config-directory) script doesn't work when executed on
> the host system.
If you want to make your container's configuration directory accessible from the host system, you need to use a
@@ -169,12 +192,12 @@ docker rm -f netdata_tmp
```
**`docker run`**: Use the `docker run` command, along with the following options, to start a new container. Note the
-changed `-v $(pwd)/netdataconfig/netdata:/etc/netdata:ro \` line from the recommended example above.
+changed `-v $(pwd)/netdataconfig/netdata:/etc/netdata \` line from the recommended example above.
```bash
docker run -d --name=netdata \
-p 19999:19999 \
- -v $(pwd)/netdataconfig/netdata:/etc/netdata:ro \
+ -v $(pwd)/netdataconfig/netdata:/etc/netdata \
-v netdatalib:/var/lib/netdata \
-v netdatacache:/var/cache/netdata \
-v /etc/passwd:/host/etc/passwd:ro \
@@ -185,6 +208,7 @@ docker run -d --name=netdata \
--restart unless-stopped \
--cap-add SYS_PTRACE \
--security-opt apparmor=unconfined \
+ --ulimit nofile=4096 \
netdata/netdata
```
@@ -206,6 +230,9 @@ services:
- SYS_PTRACE
security_opt:
- apparmor:unconfined
+ ulimits:
+ nofile:
+ soft: 4096
volumes:
- ./netdataconfig/netdata:/etc/netdata:ro
- netdatalib:/var/lib/netdata
@@ -295,7 +322,7 @@ your machine from within the container. Please read the following carefully.
#### Docker socket proxy (safest option)
Deploy a Docker socket proxy that accepts and filters out requests using something like
-[HAProxy](/docs/Running-behind-haproxy.md) so that it restricts connections to read-only access to the CONTAINERS
+[HAProxy](https://github.com/netdata/netdata/blob/master/docs/Running-behind-haproxy.md) so that it restricts connections to read-only access to the CONTAINERS
endpoint.
The reason it's safer to expose the socket to the proxy is because Netdata has a TCP port exposed outside the Docker
@@ -414,13 +441,13 @@ services:
### Pass command line options to Netdata
Since we use an [ENTRYPOINT](https://docs.docker.com/engine/reference/builder/#entrypoint) directive, you can provide
-[Netdata daemon command line options](/daemon/README.md#command-line-options) such as the IP address Netdata will be
+[Netdata daemon command line options](https://github.com/netdata/netdata/blob/master/daemon/README.md#command-line-options) such as the IP address Netdata will be
running on, using the [command instruction](https://docs.docker.com/engine/reference/builder/#cmd).
## Install the Agent using Docker Compose with SSL/TLS enabled HTTP Proxy
For a permanent installation on a public server, you should [secure the Netdata
-instance](/docs/netdata-security.md). This section contains an example of how to install Netdata with an SSL
+instance](https://github.com/netdata/netdata/blob/master/docs/netdata-security.md). This section contains an example of how to install Netdata with an SSL
reverse proxy and basic authentication.
You can use the following `docker-compose.yml` and Caddyfile files to run Netdata with Docker. Replace the domains and
@@ -468,6 +495,9 @@ services:
- SYS_PTRACE
security_opt:
- apparmor:unconfined
+ ulimits:
+ nofile:
+ soft: 4096
volumes:
- netdatalib:/var/lib/netdata
- netdatacache:/var/cache/netdata
@@ -490,47 +520,4 @@ Caddyfile.
## Publish a test image to your own repository
At Netdata, we provide multiple ways of testing your Docker images using your own repositories.
-You may either use the command line tools available or take advantage of our Travis CI infrastructure.
-
-### Inside Netdata organization, using Travis CI
-
-To enable Travis CI integration on your own repositories (Docker and GitHub), you need to be part of the Netdata
-organization.
-
-Once you have contacted the Netdata owners to setup you up on GitHub and Travis, execute the following steps
-
-- Preparation
- - Have Netdata forked on your personal GitHub account
- - Get a GitHub token: Go to **GitHub settings** -> **Developer Settings** -> **Personal access tokens**, and
- generate a new token with full access to `repo_hook`, read-only access to `admin:org`, `public_repo`,
- `repo_deployment`, `repo:status`, and `user:email` settings enabled. This will be your `GITHUB_TOKEN` that is
- described later in the instructions, so keep it somewhere safe.
- - Contact the Netdata team and seek for permissions on `https://scan.coverity.com` should you require Travis to be
- able to push your forked code to coverity for analysis and report. Once you are setup, you should have your
- email you used in coverity and a token from them. These will be your `COVERITY_SCAN_SUBMIT_EMAIL` and
- `COVERITY_SCAN_TOKEN` that we will refer to later.
- - Have a valid Docker hub account, the credentials from this account will be your `DOCKER_USERNAME` and
- `DOCKER_PWD` mentioned later.
-
-- Setting up Travis CI for your own fork (Detailed instructions provided by Travis team [here](https://docs.travis-ci.com/user/tutorial/))
- - Login to travis with your own GITHUB credentials (There is Open Auth access)
- - Go to your profile settings, under [repositories](https://travis-ci.com/account/repositories) section and setup
- your Netdata fork to be built by Travis CI.
- - Once the repository has been setup, go to repository settings within Travis CI (usually under
- `https://travis-ci.com/NETDATA_DEVELOPER/netdata/settings`, where `NETDATA_DEVELOPER` is your GitHub handle),
- and select your desired settings.
-
-- While in Travis settings, under Netdata repository settings in the Environment Variables section, you need to add
- the following:
- - `DOCKER_USERNAME` and `DOCKER_PWD` variables so that Travis can log in to your Docker Hub account and publish
- Docker images there.
- - `REPOSITORY` variable to `NETDATA_DEVELOPER/netdata`, where `NETDATA_DEVELOPER` is your GitHub handle again.
- - `GITHUB_TOKEN` variable with the token generated on the preparation step, for Travis workflows to function
- properly.
- - `COVERITY_SCAN_SUBMIT_EMAIL` and `COVERITY_SCAN_TOKEN` variables to enable Travis to submit your code for
- analysis to Coverity.
-
-Having followed these instructions, your forked repository should be all set up for integration with Travis CI. Happy
-testing!
-
-
+You may either use the command line tools available or take advantage of our GitHub Acions infrastructure.
diff --git a/packaging/docker/run.sh b/packaging/docker/run.sh
index 1e001256..9029e22b 100755
--- a/packaging/docker/run.sh
+++ b/packaging/docker/run.sh
@@ -49,6 +49,14 @@ if mountpoint -q /etc/netdata && [ -z "$(ls -A /etc/netdata)" ]; then
cp -a /etc/netdata.stock/. /etc/netdata
fi
+if [ -w "/etc/netdata" ]; then
+ if mountpoint -q /etc/netdata; then
+ hostname >/etc/netdata/.container-hostname
+ else
+ rm -f /etc/netdata/.container-hostname
+ fi
+fi
+
if [ -n "${NETDATA_CLAIM_URL}" ] && [ -n "${NETDATA_CLAIM_TOKEN}" ] && [ ! -f /var/lib/netdata/cloud.d/claimed_id ]; then
# shellcheck disable=SC2086
/usr/sbin/netdata-claim.sh -token="${NETDATA_CLAIM_TOKEN}" \
diff --git a/packaging/ebpf-co-re.checksums b/packaging/ebpf-co-re.checksums
index da7cb051..7f08d732 100644
--- a/packaging/ebpf-co-re.checksums
+++ b/packaging/ebpf-co-re.checksums
@@ -1 +1 @@
-9fe4fccc160ca9e0fd0ffd8894dbf6e5fddcf9ca3a4ee04cb645bb7703e8cef2 netdata-ebpf-co-re-glibc-v1.0.1.tar.xz
+d1864cd736d236aa3738152d86096529830822a26405a62fe164779949bb3658 netdata-ebpf-co-re-glibc-v1.1.0.tar.xz
diff --git a/packaging/ebpf-co-re.version b/packaging/ebpf-co-re.version
index b18d4654..795460fc 100644
--- a/packaging/ebpf-co-re.version
+++ b/packaging/ebpf-co-re.version
@@ -1 +1 @@
-v1.0.1
+v1.1.0
diff --git a/packaging/ebpf.checksums b/packaging/ebpf.checksums
index e5afcb86..e7434911 100644
--- a/packaging/ebpf.checksums
+++ b/packaging/ebpf.checksums
@@ -1,3 +1,3 @@
-e3836908a5cfd5a1b6c71463645ed7040be1a4890767844b744fe1054910b51f ./netdata-kernel-collector-glibc-v1.0.1.tar.xz
-091382fece7a470e505df4c655d834a8a49e50aa2a2fec4a52a324ab0ccd9870 ./netdata-kernel-collector-musl-v1.0.1.tar.xz
-11497ccb4f2cdac0d80b00bf97d2f1633c972e8720abdda2a63bd0de95859840 ./netdata-kernel-collector-static-v1.0.1.tar.xz
+7f28bb61b1e9fdac59e5f8f041502c54f319048c1cf4adaa96ace3360f55a80e ./netdata-kernel-collector-glibc-v1.1.0.tar.xz
+5d927deadac9a4a5bc8a5be386aec2ea4f9b8335e60eadf375b11e7656404270 ./netdata-kernel-collector-musl-v1.1.0.tar.xz
+0d8825b77b8ba20e10b6e24f15c1d65a43f1c47dced93798839adc789f1427d3 ./netdata-kernel-collector-static-v1.1.0.tar.xz
diff --git a/packaging/ebpf.version b/packaging/ebpf.version
index b18d4654..795460fc 100644
--- a/packaging/ebpf.version
+++ b/packaging/ebpf.version
@@ -1 +1 @@
-v1.0.1
+v1.1.0
diff --git a/packaging/go.d.checksums b/packaging/go.d.checksums
index 806a45f4..3e70b04f 100644
--- a/packaging/go.d.checksums
+++ b/packaging/go.d.checksums
@@ -1,17 +1,17 @@
-67335df67f1629d9dc6dc559f91d26499f023b43a3fb2cece22d7c2919653dae *config.tar.gz
-cae0acc8c19112c35927ccd4ef53f594c8d805467f204db84305ca8ddfccfcfa *go.d.plugin-v0.45.0.darwin-amd64.tar.gz
-dcaddcea32a26392db7f13867226c62ba87aeb00c93931d1fc10d0641eb04e3a *go.d.plugin-v0.45.0.darwin-arm64.tar.gz
-e89153033d2b8d6b59f163f1419ebaaae8414054f1a33bca52f76651848b528d *go.d.plugin-v0.45.0.freebsd-386.tar.gz
-da3ba485995a60879cb51819ac2f9ecf81d23f64a0afb584bdc134a1fe4a2251 *go.d.plugin-v0.45.0.freebsd-amd64.tar.gz
-ca1e153ac90714c0956a7ed4bcf14bf263f2c65a1876e9c8c24dd1b2bcd5b74a *go.d.plugin-v0.45.0.freebsd-arm.tar.gz
-e25fcd7c3fa8aed01efff8ea25c3276de65d2efacff70fa4670a970272ffbba3 *go.d.plugin-v0.45.0.freebsd-arm64.tar.gz
-7bfd345bdc30292a9145e4397b15a9004d78049006702727cd5a52900531dfa1 *go.d.plugin-v0.45.0.linux-386.tar.gz
-7a6dd92ab6892aedd0a257ffbded54a0efa7cf95fb7f738387c8d7394e57eb09 *go.d.plugin-v0.45.0.linux-amd64.tar.gz
-834a26c486a579991d76dc07625e135bf13fe4387627076bdbbb14bcb55ff978 *go.d.plugin-v0.45.0.linux-arm.tar.gz
-1dda73a062ba20d2996334d878fd74207ad49b9f164530a5ad2144c70947d5a8 *go.d.plugin-v0.45.0.linux-arm64.tar.gz
-8b52e67cf1e4d11776d5c5c8a4e246412caa1cffd36bafa5a8cb38a8e1867dee *go.d.plugin-v0.45.0.linux-mips.tar.gz
-9a95f9df3527693a0a92c7b8ef1bec537e84014c75f32604adce4540e924576e *go.d.plugin-v0.45.0.linux-mips64.tar.gz
-4ce0ec6068edf8218f68785f6ce258ed76ffea50a5953f7f75b9a48e1d912a81 *go.d.plugin-v0.45.0.linux-mips64le.tar.gz
-360812ea936768926b38031c9dff2ecda2c80733e875f1c317371dee9d56a76c *go.d.plugin-v0.45.0.linux-mipsle.tar.gz
-d7b594d84d47a0d2c6265c8fa9317285a53821f3e9aac5d31bcce0c8706fb369 *go.d.plugin-v0.45.0.linux-ppc64.tar.gz
-dc29665076eecfb05d63ac243f97bd4d09f8dedae8c5285b349bd227e34ce249 *go.d.plugin-v0.45.0.linux-ppc64le.tar.gz
+dee3c1e54cad22796489abd1924462cfcbd2cf8ff7329f9a595966291e18714d *config.tar.gz
+4188350b0c7f0f3dcfabf01e0281b41baa085327655a32215a0863ec651c0186 *go.d.plugin-v0.50.0.darwin-amd64.tar.gz
+6068657f08c21eeb57508c47dab544f7493ac14f63261c8bdfeb2b326f5e980c *go.d.plugin-v0.50.0.darwin-arm64.tar.gz
+cdee540016daa37b84ac8c66feb141f83d328371e7bd464e99de99584d0813ca *go.d.plugin-v0.50.0.freebsd-386.tar.gz
+4196b233aff75747749df9894d15f078a40945709b0b2d6d9c6387d992cf4933 *go.d.plugin-v0.50.0.freebsd-amd64.tar.gz
+188c29003f1394a7136b1f69a659fff46b3ea9cbfd994fd9e315834745eac63a *go.d.plugin-v0.50.0.freebsd-arm.tar.gz
+80ce0196693d735d578c19a3561ea5bda0b54101c22d129c999807942d076f2a *go.d.plugin-v0.50.0.freebsd-arm64.tar.gz
+a75ba3cdebd1b428dacd4a14759c7e22d50432b49a9c8d5c846f8aa4e23aa9e2 *go.d.plugin-v0.50.0.linux-386.tar.gz
+7eb762de4103a8930a7962b6b1202ce188340ae1cd6ea04757e40e7482bfaed0 *go.d.plugin-v0.50.0.linux-amd64.tar.gz
+cfa3b1a28664fbd76a338f574c2e37d41b58552ace98f86cbe4dc3ac48785371 *go.d.plugin-v0.50.0.linux-arm.tar.gz
+2b77f8a3d33290a7cb9fa95f8635cb1cd3ce4331a7543312cfdac3902a2489e0 *go.d.plugin-v0.50.0.linux-arm64.tar.gz
+56205b61eb4c1a6798a0d6852b3c734e7c764861ae6baa490e2db55ba593c52a *go.d.plugin-v0.50.0.linux-mips.tar.gz
+384af1658f02f9b295a2c2aa021dd64c46c03c52b9a573dbbd7f9fe0ffd841e0 *go.d.plugin-v0.50.0.linux-mips64.tar.gz
+d93711f93e9da7f2b8f9162c0af80b3443687b1e01494c42b9ccd099534f2fae *go.d.plugin-v0.50.0.linux-mips64le.tar.gz
+9462186774633294b46f04fbd78dc930bdff54bb1e8d5fa634f1d9b99d8d4e4b *go.d.plugin-v0.50.0.linux-mipsle.tar.gz
+bdcfdc75dc5073556fdece5865cd9b19fc5306d68fe653afc3e97594f185386e *go.d.plugin-v0.50.0.linux-ppc64.tar.gz
+e13c9c1ad13e664b477762b7be9b17877945e1337ee1832b11dd7be2c6cfb1c9 *go.d.plugin-v0.50.0.linux-ppc64le.tar.gz
diff --git a/packaging/go.d.version b/packaging/go.d.version
index 67d3cf47..f6460f6b 100644
--- a/packaging/go.d.version
+++ b/packaging/go.d.version
@@ -1 +1 @@
-v0.45.0
+v0.50.0
diff --git a/packaging/installer/README.md b/packaging/installer/README.md
index 3a4237d5..90d3b8de 100644
--- a/packaging/installer/README.md
+++ b/packaging/installer/README.md
@@ -24,7 +24,7 @@ packages. We recommend you install Netdata using one of the methods listed below
checksum-verified packages.
Netdata collects anonymous usage information by default and sends it to our self hosted [PostHog](https://github.com/PostHog/posthog) installation. PostHog is an open source product analytics platform, you can read
-about the information collected, and learn how to-opt, on our [anonymous statistics](/docs/anonymous-statistics.md)
+about the information collected, and learn how to-opt, on our [anonymous statistics](https://github.com/netdata/netdata/blob/master/docs/anonymous-statistics.md)
page.
The usage statistics are _vital_ for us, as we use them to discover bugs and prioritize new features. We thank you for
@@ -32,7 +32,7 @@ _actively_ contributing to Netdata's future.
## Automatic one-line installation script
-![](https://registry.my-netdata.io/api/v1/badge.svg?chart=web_log_nginx.requests_per_url&options=unaligned&dimensions=kickstart&group=sum&after=-3600&label=last+hour&units=installations&value_color=orange&precision=0) ![](https://registry.my-netdata.io/api/v1/badge.svg?chart=web_log_nginx.requests_per_url&options=unaligned&dimensions=kickstart&group=sum&after=-86400&label=today&units=installations&precision=0)
+![](https://registry.my-netdata.io/api/v1/badge.svg?chart=web_log_nginx.requests_by_url_pattern&options=unaligned&dimensions=kickstart&group=sum&after=-3600&label=last+hour&units=installations&value_color=orange&precision=0) ![](https://registry.my-netdata.io/api/v1/badge.svg?chart=web_log_nginx.requests_by_url_pattern&options=unaligned&dimensions=kickstart&group=sum&after=-86400&label=today&units=installations&precision=0)
This method is fully automatic on all Linux distributions, including Ubuntu, Debian, Fedora, CentOS, and others, as well as on mac OS environments.
@@ -49,17 +49,17 @@ This script will preferentially use native DEB/RPM packages if we provide them f
To see more information about this installation script, including how to disable automatic updates, get nightly vs.
stable releases, or disable anonymous statistics, see the [`kickstart.sh` method
-page](/packaging/installer/methods/kickstart.md).
+page](https://github.com/netdata/netdata/blob/master/packaging/installer/methods/kickstart.md).
Scroll down for details about [automatic updates](#automatic-updates) or [nightly vs. stable
releases](#nightly-vs-stable-releases).
### Post-installation
-When you're finished with installation, check out our [single-node](/docs/quickstart/single-node.md) or
-[infrastructure](/docs/quickstart/infrastructure.md) monitoring quickstart guides based on your use case.
+When you're finished with installation, check out our [single-node](https://github.com/netdata/netdata/blob/master/docs/quickstart/single-node.md) or
+[infrastructure](https://github.com/netdata/netdata/blob/master/docs/quickstart/infrastructure.md) monitoring quickstart guides based on your use case.
-Or, skip straight to [configuring the Netdata Agent](/docs/configure/nodes.md).
+Or, skip straight to [configuring the Netdata Agent](https://github.com/netdata/netdata/blob/master/docs/configure/nodes.md).
Read through Netdata's [documentation](https://learn.netdata.cloud/docs), which is structured based on actions and
solutions, to enable features like health monitoring, alarm notifications, long-term metrics storage, exporting to
@@ -68,7 +68,7 @@ external databases, and more.
## Have a different operating system, or want to try another method?
Netdata works on many different platforms. To see all supported platforms, check out our [platform support
-policy](/packaging/PLATFORM_SUPPORT.md).
+policy](https://github.com/netdata/netdata/blob/master/packaging/PLATFORM_SUPPORT.md).
Below, you can find a few additional installation methods, followed by separate instructions for a variety of unique
operating systems.
@@ -81,6 +81,10 @@ operating systems.
os="General Linux with one-line installer (recommended)"
svg="linux" />
<InstallBox
+ to="/docs/agent/packaging/installer/methods/packages"
+ os="Native DEB/RPM packages for Linux"
+ svg="linux" />
+ <InstallBox
to="/docs/agent/packaging/docker"
os="Run with Docker"
svg="docker" />
@@ -119,7 +123,7 @@ wget -O /tmp/netdata-kickstart.sh https://my-netdata.io/kickstart.sh && sh /tmp/
```
With automatic updates disabled, you can choose exactly when and how you [update
-Netdata](/packaging/installer/UPDATE.md).
+Netdata](https://github.com/netdata/netdata/blob/master/packaging/installer/UPDATE.md).
### Network usage of Netdata’s automatic updater
@@ -178,8 +182,8 @@ man-in-the-middle attacks.
### CentOS 6 and CentOS 8
To install the Agent on certain CentOS and RHEL systems, you must enable non-default repositories, such as EPEL or
-PowerTools, to gather hard dependencies. See the [CentOS 6](/packaging/installer/methods/manual.md#centos--rhel-6x) and
-[CentOS 8](/packaging/installer/methods/manual.md#centos--rhel-8x) sections for more information.
+PowerTools, to gather hard dependencies. See the [CentOS 6](https://github.com/netdata/netdata/blob/master/packaging/installer/methods/manual.md#centos--rhel-6x) and
+[CentOS 8](https://github.com/netdata/netdata/blob/master/packaging/installer/methods/manual.md#centos--rhel-8x) sections for more information.
### Access to file is not permitted
@@ -213,6 +217,6 @@ both.
Our current build process has some issues when using certain configurations of the `clang` C compiler on Linux. See [the
section on `nonrepresentable section on output`
-errors](/packaging/installer/methods/manual.md#nonrepresentable-section-on-output-errors) for a workaround.
+errors](https://github.com/netdata/netdata/blob/master/packaging/installer/methods/manual.md#nonrepresentable-section-on-output-errors) for a workaround.
diff --git a/packaging/installer/REINSTALL.md b/packaging/installer/REINSTALL.md
index 1356c70d..c24fdee8 100644
--- a/packaging/installer/REINSTALL.md
+++ b/packaging/installer/REINSTALL.md
@@ -1,7 +1,11 @@
<!--
title: "Reinstall the Netdata Agent"
description: "Troubleshooting installation issues or force an update of the Netdata Agent by reinstalling it using the same method you used during installation."
-custom_edit_url: https://github.com/netdata/netdata/edit/master/packaging/installer/REINSTALL.md
+custom_edit_url: "https://github.com/netdata/netdata/edit/master/packaging/installer/REINSTALL.md"
+sidebar_label: "Reinstall the Netdata Agent"
+learn_status: "Published"
+learn_topic_type: "Tasks"
+learn_rel_path: "Installation"
-->
# Reinstall the Netdata Agent
@@ -14,11 +18,11 @@ Netdata Agent on your node.
### Reinstalling with the same install type
Run the one-line installer script with the `--reinstall` parameter to reinstall the Netdata Agent. This will preserve
-any [user configuration](/docs/configure/nodes.md) in `netdata.conf` or other files, and will keep the same install
+any [user configuration](https://github.com/netdata/netdata/blob/master/docs/configure/nodes.md) in `netdata.conf` or other files, and will keep the same install
type that was used for the original install.
If you used any [optional
-parameters](/packaging/installer/methods/kickstart.md#optional-parameters-to-alter-your-installation) during initial
+parameters](https://github.com/netdata/netdata/blob/master/packaging/installer/methods/kickstart.md#optional-parameters-to-alter-your-installation) during initial
installation, you need to pass them to the script again during reinstallation. If you cannot remember which options you
used, read the contents of the `.environment` file and look for a `REINSTALL_OPTIONS` line. This line contains a list of
optional parameters.
@@ -35,7 +39,7 @@ getting a badly broken installation working again. Unlike the regular `--reinsta
different install type than the original install used.
If you used any [optional
-parameters](/packaging/installer/methods/kickstart.md#optional-parameters-to-alter-your-installation) during initial
+parameters](https://github.com/netdata/netdata/blob/master/packaging/installer/methods/kickstart.md#optional-parameters-to-alter-your-installation) during initial
installation, you need to pass them to the script again during reinstallation. If you cannot remember which options you
used, read the contents of the `.environment` file and look for a `REINSTALL_OPTIONS` line. This line contains a list of
optional parameters.
@@ -65,8 +69,8 @@ When copying these directories back after the reinstall, you may need to update
## Troubleshooting
If you still experience problems with your Netdata Agent installation after following one of these processes, the next
-best route is to [uninstall](/packaging/installer/UNINSTALL.md) and then try a fresh installation using the [one-line
-installer](/packaging/installer/methods/kickstart.md).
+best route is to [uninstall](https://github.com/netdata/netdata/blob/master/packaging/installer/UNINSTALL.md) and then try a fresh installation using the [one-line
+installer](https://github.com/netdata/netdata/blob/master/packaging/installer/methods/kickstart.md).
You can also post to our [community forums](https://community.netdata.cloud/c/support/13) or create a new [bug
report](https://github.com/netdata/netdata/issues/new?assignees=&labels=bug%2Cneeds+triage&template=BUG_REPORT.yml).
diff --git a/packaging/installer/UNINSTALL.md b/packaging/installer/UNINSTALL.md
index af2314f6..2ff22f5c 100644
--- a/packaging/installer/UNINSTALL.md
+++ b/packaging/installer/UNINSTALL.md
@@ -1,14 +1,18 @@
<!--
title: "Uninstall Netdata"
description: "If you are no longer interested in using the Netdata Agent, use the self-contained uninstaller to remove all traces of binaries and configuration files."
-custom_edit_url: https://github.com/netdata/netdata/edit/master/packaging/installer/UNINSTALL.md
+custom_edit_url: "https://github.com/netdata/netdata/edit/master/packaging/installer/UNINSTALL.md"
+sidebar_label: "Uninstall Netdata"
+learn_status: "Published"
+learn_topic_type: "Tasks"
+learn_rel_path: "Installation"
-->
# Uninstall Netdata
> ⚠️ If you're having trouble updating Netdata, moving from one installation method to another, or generally having
> issues with your Netdata Agent installation, consider our [**reinstall Netdata**
-> doc](/packaging/installer/REINSTALL.md) instead of removing the Netdata Agent entirely.
+> doc](https://github.com/netdata/netdata/blob/master/packaging/installer/REINSTALL.md) instead of removing the Netdata Agent entirely.
The recommended method to uninstall Netdata on a system is to use our kickstart installer script with the `--uninstall` option like so:
@@ -51,7 +55,7 @@ A workflow for uninstallation looks like this:
2. If you cannot find that file and would like to uninstall Netdata, then create a new file with the following content:
```sh
-NETDATA_PREFIX="<installation prefix>" # put what you used as a parameter to shell installed `--install` flag. Otherwise it should be empty
+NETDATA_PREFIX="<installation prefix>" # put what you used as a parameter to shell installed `--install-prefix` flag. Otherwise it should be empty
NETDATA_ADDED_TO_GROUPS="<additional groups>" # Additional groups for a user running the Netdata process
```
diff --git a/packaging/installer/UPDATE.md b/packaging/installer/UPDATE.md
index 009e970f..9d4289f8 100644
--- a/packaging/installer/UPDATE.md
+++ b/packaging/installer/UPDATE.md
@@ -1,7 +1,11 @@
<!--
title: "Update the Netdata Agent"
description: "If you opted out of automatic updates, you need to update your Netdata Agent to the latest nightly or stable version."
-custom_edit_url: https://github.com/netdata/netdata/edit/master/packaging/installer/UPDATE.md
+custom_edit_url: "https://github.com/netdata/netdata/edit/master/packaging/installer/UPDATE.md"
+sidebar_label: "Update the Netdata Agent"
+learn_status: "Published"
+learn_topic_type: "Tasks"
+learn_rel_path: "Installation"
-->
# Update the Netdata Agent
@@ -11,7 +15,7 @@ you installed. If you opted out of automatic updates, you need to update your Ne
or stable version. You can also [enable or disable automatic updates on an existing install](#control-automatic-updates).
> 💡 Looking to reinstall the Netdata Agent to enable a feature, update an Agent that cannot update automatically, or
-> troubleshoot an error during the installation process? See our [reinstallation doc](/packaging/installer/REINSTALL.md)
+> troubleshoot an error during the installation process? See our [reinstallation doc](https://github.com/netdata/netdata/blob/master/packaging/installer/REINSTALL.md)
> for reinstallation steps.
Before you update the Netdata Agent, check to see if your Netdata Agent is already up-to-date by clicking on the update
@@ -45,14 +49,14 @@ command:
wget -O /tmp/netdata-kickstart.sh https://my-netdata.io/kickstart.sh && sh /tmp/netdata-kickstart.sh --dry-run
```
-Note that if you installed Netdata using an installation prefix, you will need to add an `--install` option
+Note that if you installed Netdata using an installation prefix, you will need to add an `--install-prefix` option
specifying that prefix to make sure it finds the existing install.
If you see a line starting with `--- Would attempt to update existing installation by running the updater script
located at:`, then our [regular update method](#updates-for-most-systems) will work for you.
Otherwise, it should either indicate that the installation type is not supported (which probably means you either
-have a `custom` instal or built Netdata manually) or indicate that it would create a new install (which means that
+have a `custom` install or built Netdata manually) or indicate that it would create a new install (which means that
you either used a non-standard install path, or that you don’t actually have Netdata installed).
## Updates for most systems
@@ -61,7 +65,7 @@ In most cases, you can update netdata using our one-line installation script. T
run the update script that was installed as part of the initial install (even if you disabled automatic updates)
and preserve the existing install options you specified.
-If you installed Netdata using an installation prefix, you will need to add an `--install` option specifying
+If you installed Netdata using an installation prefix, you will need to add an `--install-prefix` option specifying
that prefix to this command to make sure it finds Netdata.
```bash
@@ -80,8 +84,8 @@ On such installs, you can update Netdata using your distribution package manager
### If the kickstart script does not work
If the above command fails, you can [reinstall
-Netdata](/packaging/installer/REINSTALL.md#one-line-installer-script-kickstartsh) to get the latest version. This
-also preserves your [configuration](/docs/configure/nodes.md) in `netdata.conf` or other files just like updating
+Netdata](https://github.com/netdata/netdata/blob/master/packaging/installer/REINSTALL.md#one-line-installer-script-kickstartsh) to get the latest version. This
+also preserves your [configuration](https://github.com/netdata/netdata/blob/master/docs/configure/nodes.md) in `netdata.conf` or other files just like updating
normally would, though you will need to specify any installation options you used originally again.
## Docker
@@ -105,7 +109,7 @@ docker rm netdata
```
You can now re-create your Netdata container using the `docker` command or a `docker-compose.yml` file. See our [Docker
-installation instructions](/packaging/docker/README.md#create-a-new-netdata-agent-container) for details.
+installation instructions](https://github.com/netdata/netdata/blob/master/packaging/docker/README.md#create-a-new-netdata-agent-container) for details.
## macOS
@@ -124,7 +128,7 @@ instructions](#updates-for-most-systems) to update Netdata.
## Manual installation from Git
-If you installed [Netdata manually from Git](/packaging/installer/methods/manual.md), you can run that installer again
+If you installed [Netdata manually from Git](https://github.com/netdata/netdata/blob/master/packaging/installer/methods/manual.md), you can run that installer again
to update your agent. First, run our automatic requirements installer, which works on many Linux distributions, to
ensure your system has the dependencies necessary for new features.
diff --git a/packaging/installer/functions.sh b/packaging/installer/functions.sh
index e354ac65..ebb4aab7 100644
--- a/packaging/installer/functions.sh
+++ b/packaging/installer/functions.sh
@@ -396,6 +396,14 @@ get_os_key() {
fi
}
+get_group(){
+ if command -v getent > /dev/null 2>&1; then
+ getent group "${1:-""}"
+ else
+ cat /etc/group | grep "^${1}:"
+ fi
+}
+
issystemd() {
pids=''
p=''
@@ -933,7 +941,7 @@ portable_add_group() {
groupname="${1}"
# Check if group exist
- if cut -d ':' -f 1 < /etc/group | grep "^${groupname}$" 1> /dev/null 2>&1; then
+ if get_group "${groupname}" > /dev/null 2>&1; then
echo >&2 "Group '${groupname}' already exists."
return 0
fi
@@ -969,14 +977,14 @@ portable_add_user_to_group() {
username="${2}"
# Check if group exist
- if ! cut -d ':' -f 1 < /etc/group | grep "^${groupname}$" > /dev/null 2>&1; then
+ if ! get_group "${groupname}" > /dev/null 2>&1; then
echo >&2 "Group '${groupname}' does not exist."
# Don’t treat this as a failure, if the group does not exist we should not be trying to add the user to it.
return 0
fi
# Check if user is in group
- if expr ",$(grep "^${groupname}:" < /etc/group | cut -d ':' -f 4)," : ",""${username}"","; then
+ if get_group "${groupname}" | cut -d ':' -f 4 | grep -wq "${username}"; then
# username is already there
echo >&2 "User '${username}' is already in group '${groupname}'."
return 0
diff --git a/packaging/installer/install-required-packages.sh b/packaging/installer/install-required-packages.sh
index 6547dd82..c906cce3 100755
--- a/packaging/installer/install-required-packages.sh
+++ b/packaging/installer/install-required-packages.sh
@@ -20,7 +20,6 @@ fi
PACKAGES_NETDATA=${PACKAGES_NETDATA-1}
PACKAGES_NETDATA_PYTHON=${PACKAGES_NETDATA_PYTHON-0}
PACKAGES_NETDATA_PYTHON3=${PACKAGES_NETDATA_PYTHON3-1}
-PACKAGES_NETDATA_PYTHON_MONGO=${PACKAGES_NETDATA_PYTHON_MONGO-0}
PACKAGES_DEBUG=${PACKAGES_DEBUG-0}
PACKAGES_IPRANGE=${PACKAGES_IPRANGE-0}
PACKAGES_FIREHOL=${PACKAGES_FIREHOL-0}
@@ -105,8 +104,6 @@ Supported packages (you can append many of them):
- python3 install python3
- - python-pymongo install python-pymongo (or python3-pymongo for python3)
-
- sensors install lm_sensors for monitoring h/w sensors
- firehol-all packages required for FireHOL, FireQOS, update-ipsets
@@ -715,6 +712,7 @@ declare -A pkg_tar=(
['gentoo']="app-arch/tar"
['clearlinux']="os-core-update"
['macos']="NOTREQUIRED"
+ ['freebsd']="NOTREQUIRED"
['default']="tar"
)
@@ -934,41 +932,6 @@ declare -A pkg_python3_pip=(
['default']="python3-pip"
)
-declare -A pkg_python_pymongo=(
- ['alpine']="WARNING|"
- ['arch']="python2-pymongo"
- ['centos']="WARNING|"
- ['debian']="python-pymongo"
- ['gentoo']="dev-python/pymongo"
- ['suse']="python-pymongo"
- ['clearlinux']="WARNING|"
- ['rhel']="WARNING|"
- ['ol']="WARNING|"
- ['macos']="WARNING|"
- ['default']="python-pymongo"
-)
-
-declare -A pkg_python3_pymongo=(
- ['alpine']="WARNING|"
- ['arch']="python-pymongo"
- ['centos']="WARNING|"
- ['debian']="python3-pymongo"
- ['gentoo']="dev-python/pymongo"
- ['suse']="python3-pymongo"
- ['clearlinux']="WARNING|"
- ['rhel']="WARNING|"
- ['ol']="WARNING|"
- ['freebsd']="py37-pymongo"
- ['macos']="WARNING|"
- ['default']="python3-pymongo"
-
- ['centos-7']="python36-pymongo"
- ['centos-8']="python3-pymongo"
- ['rhel-7']="python36-pymongo"
- ['rhel-8']="python3-pymongo"
- ['ol-8']="python3-pymongo"
-)
-
declare -A pkg_python_requests=(
['alpine']="py-requests"
['arch']="python2-requests"
@@ -1293,7 +1256,6 @@ packages() {
if [ "${PACKAGES_NETDATA_PYTHON}" -ne 0 ]; then
require_cmd python || suitable_package python
- [ "${PACKAGES_NETDATA_PYTHON_MONGO}" -ne 0 ] && suitable_package python-pymongo
# suitable_package python-requests
# suitable_package python-pip
fi
@@ -1304,7 +1266,6 @@ packages() {
if [ "${PACKAGES_NETDATA_PYTHON3}" -ne 0 ]; then
require_cmd python3 || suitable_package python3
- [ "${PACKAGES_NETDATA_PYTHON_MONGO}" -ne 0 ] && suitable_package python3-pymongo
# suitable_package python3-requests
# suitable_package python3-pip
fi
@@ -1846,7 +1807,7 @@ EOF
remote_log() {
# log success or failure on our system
# to help us solve installation issues
- curl > /dev/null 2>&1 -Ss --max-time 3 "https://registry.my-netdata.io/log/installer?status=${1}&error=${2}&distribution=${distribution}&version=${version}&installer=${package_installer}&tree=${tree}&detection=${detection}&netdata=${PACKAGES_NETDATA}&python=${PACKAGES_NETDATA_PYTHON}&python3=${PACKAGES_NETDATA_PYTHON3}&pymongo=${PACKAGES_NETDATA_PYTHON_MONGO}&sensors=${PACKAGES_NETDATA_SENSORS}&database=${PACKAGES_NETDATA_DATABASE}&ebpf=${PACKAGES_NETDATA_EBPF}&firehol=${PACKAGES_FIREHOL}&fireqos=${PACKAGES_FIREQOS}&iprange=${PACKAGES_IPRANGE}&update_ipsets=${PACKAGES_UPDATE_IPSETS}&demo=${PACKAGES_NETDATA_DEMO_SITE}"
+ curl > /dev/null 2>&1 -Ss --max-time 3 "https://registry.my-netdata.io/log/installer?status=${1}&error=${2}&distribution=${distribution}&version=${version}&installer=${package_installer}&tree=${tree}&detection=${detection}&netdata=${PACKAGES_NETDATA}&python=${PACKAGES_NETDATA_PYTHON}&python3=${PACKAGES_NETDATA_PYTHON3}&sensors=${PACKAGES_NETDATA_SENSORS}&database=${PACKAGES_NETDATA_DATABASE}&ebpf=${PACKAGES_NETDATA_EBPF}&firehol=${PACKAGES_FIREHOL}&fireqos=${PACKAGES_FIREQOS}&iprange=${PACKAGES_IPRANGE}&update_ipsets=${PACKAGES_UPDATE_IPSETS}&demo=${PACKAGES_NETDATA_DEMO_SITE}"
}
if [ -z "${1}" ]; then
@@ -1909,10 +1870,8 @@ while [ -n "${1}" ]; do
PACKAGES_NETDATA=1
if [ "${pv}" -eq 2 ]; then
PACKAGES_NETDATA_PYTHON=1
- PACKAGES_NETDATA_PYTHON_MONGO=1
else
PACKAGES_NETDATA_PYTHON3=1
- PACKAGES_NETDATA_PYTHON3_MONGO=1
fi
PACKAGES_NETDATA_SENSORS=1
PACKAGES_NETDATA_DATABASE=1
@@ -1934,16 +1893,6 @@ while [ -n "${1}" ]; do
PACKAGES_NETDATA_PYTHON3=1
;;
- python-pymongo)
- if [ "${pv}" -eq 2 ]; then
- PACKAGES_NETDATA_PYTHON=1
- PACKAGES_NETDATA_PYTHON_MONGO=1
- else
- PACKAGES_NETDATA_PYTHON3=1
- PACKAGES_NETDATA_PYTHON3_MONGO=1
- fi
- ;;
-
sensors | netdata-sensors)
PACKAGES_NETDATA=1
PACKAGES_NETDATA_PYTHON3=1
@@ -1963,10 +1912,8 @@ while [ -n "${1}" ]; do
PACKAGES_NETDATA=1
if [ "${pv}" -eq 2 ]; then
PACKAGES_NETDATA_PYTHON=1
- PACKAGES_NETDATA_PYTHON_MONGO=1
else
PACKAGES_NETDATA_PYTHON3=1
- PACKAGES_NETDATA_PYTHON3_MONGO=1
fi
PACKAGES_DEBUG=1
PACKAGES_IPRANGE=1
diff --git a/packaging/installer/kickstart.sh b/packaging/installer/kickstart.sh
index 295fcdca..30c7b4ca 100755
--- a/packaging/installer/kickstart.sh
+++ b/packaging/installer/kickstart.sh
@@ -2,7 +2,7 @@
#
# SPDX-License-Identifier: GPL-3.0-or-later
#
-# Next unused error code: F050D
+# Next unused error code: F050F
# ======================================================================
# Constants
@@ -28,8 +28,10 @@ KICKSTART_SOURCE="$(
PACKAGES_SCRIPT="https://raw.githubusercontent.com/netdata/netdata/master/packaging/installer/install-required-packages.sh"
PATH="${PATH}:/usr/local/bin:/usr/local/sbin"
PUBLIC_CLOUD_URL="https://app.netdata.cloud"
-REPOCONFIG_URL_PREFIX="https://packagecloud.io/netdata/netdata-repoconfig/packages"
-REPOCONFIG_VERSION="1-2"
+REPOCONFIG_DEB_URL_PREFIX="https://repo.netdata.cloud/repos/repoconfig"
+REPOCONFIG_DEB_VERSION="2-1"
+REPOCONFIG_RPM_URL_PREFIX="https://repo.netdata.cloud/repos/repoconfig"
+REPOCONFIG_RPM_VERSION="2-1"
START_TIME="$(date +%s)"
STATIC_INSTALL_ARCHES="x86_64 armv7l aarch64 ppc64le"
TELEMETRY_URL="https://posthog.netdata.cloud/capture/"
@@ -43,9 +45,10 @@ INSTALL_TYPE="unknown"
INSTALL_PREFIX=""
NETDATA_AUTO_UPDATES="default"
NETDATA_CLAIM_ONLY=0
-NETDATA_CLAIM_URL="api.netdata.cloud"
+NETDATA_CLAIM_URL="https://api.netdata.cloud"
NETDATA_COMMAND="default"
NETDATA_DISABLE_CLOUD=0
+NETDATA_INSTALLER_OPTIONS=""
NETDATA_ONLY_BUILD=0
NETDATA_ONLY_NATIVE=0
NETDATA_ONLY_STATIC=0
@@ -62,8 +65,7 @@ else
NETDATA_DISABLE_TELEMETRY=0
fi
-NETDATA_TARBALL_BASEURL="${NETDATA_TARBALL_BASEURL:-https://storage.googleapis.com/netdata-nightlies}"
-NETDATA_INSTALLER_OPTIONS="${NETDATA_INSTALLER_OPTIONS:-""}"
+NETDATA_TARBALL_BASEURL="${NETDATA_TARBALL_BASEURL:-https://github.com/netdata/netdata-nightlies/releases}"
TELEMETRY_API_KEY="${NETDATA_POSTHOG_API_KEY:-mqkwGT0JNFqO-zX2t0mW6Tec9yooaVu7xCBlXtHnt5Y}"
if echo "${0}" | grep -q 'kickstart-static64'; then
@@ -181,7 +183,6 @@ USAGE: kickstart.sh [options]
--reinstall-even-if-unsafe Even try to reinstall if we don't think we can do so safely (implies --reinstall).
--disable-cloud Disable support for Netdata Cloud (default: detect)
--require-cloud Only install if Netdata Cloud can be enabled. Overrides --disable-cloud.
- --install <path> This option is deprecated and will be removed in a future version, use --install-prefix instead.
--install-prefix <path> Specify an installation prefix for local builds (default: autodetect based on system type).
--old-install-prefix <path> Specify an old local builds installation prefix for uninstall/reinstall (if it's not default).
--install-version <version> Specify the version of Netdata to install.
@@ -301,12 +302,21 @@ EOF
if command -v curl > /dev/null 2>&1; then
curl --silent -o /dev/null -X POST --max-time 2 --header "Content-Type: application/json" -d "${REQ_BODY}" "${TELEMETRY_URL}" > /dev/null
elif command -v wget > /dev/null 2>&1; then
- wget -q -O - --no-check-certificate \
- --method POST \
- --timeout=1 \
- --header 'Content-Type: application/json' \
- --body-data "${REQ_BODY}" \
- "${TELEMETRY_URL}" > /dev/null
+ if wget --help 2>&1 | grep BusyBox > /dev/null 2>&1; then
+ # BusyBox-compatible version of wget, there is no --no-check-certificate option
+ wget -q -O - \
+ -T 1 \
+ --header 'Content-Type: application/json' \
+ --post-data "${REQ_BODY}" \
+ "${TELEMETRY_URL}" > /dev/null
+ else
+ wget -q -O - --no-check-certificate \
+ --method POST \
+ --timeout=1 \
+ --header 'Content-Type: application/json' \
+ --body-data "${REQ_BODY}" \
+ "${TELEMETRY_URL}" > /dev/null
+ fi
fi
}
@@ -591,7 +601,7 @@ get_redirect() {
if command -v curl > /dev/null 2>&1; then
run sh -c "curl ${url} -s -L -I -o /dev/null -w '%{url_effective}' | grep -o '[^/]*$'" || return 1
elif command -v wget > /dev/null 2>&1; then
- run sh -c "wget --max-redirect=0 ${url} 2>&1 | grep Location | cut -d ' ' -f2 | grep -o '[^/]*$'" || return 1
+ run sh -c "wget -S -O /dev/null ${url} 2>&1 | grep -m 1 Location | grep -o '[^/]*$'" || return 1
else
fatal "${ERROR_F0003}" F0003
fi
@@ -1044,7 +1054,7 @@ EOF
confirm_install_prefix() {
if [ -n "${INSTALL_PREFIX}" ] && [ "${NETDATA_ONLY_BUILD}" -ne 1 ]; then
- fatal "The --install-prefix and --install options are only supported together with the --build-only option." F0204
+ fatal "The --install-prefix option is only supported together with the --build-only option." F0204
fi
if [ -n "${INSTALL_PREFIX}" ]; then
@@ -1282,7 +1292,7 @@ pkg_installed() {
netdata_avail_check() {
case "${DISTRO_COMPAT_NAME}" in
debian|ubuntu)
- env DEBIAN_FRONTEND=noninteractive apt-cache policy netdata | grep -q packagecloud.io/netdata/netdata;
+ env DEBIAN_FRONTEND=noninteractive apt-cache policy netdata | grep -q repo.netdata.cloud/repos/;
return $?
;;
centos|fedora|ol)
@@ -1361,7 +1371,7 @@ try_package_install() {
repo_subcmd="update"
repo_prefix="debian/${SYSCODENAME}"
pkg_type="deb"
- pkg_suffix="_all"
+ pkg_suffix="+debian${SYSVERSION}_all"
pkg_vsep="_"
pkg_install_opts="${interactive_opts}"
repo_update_opts="${interactive_opts}"
@@ -1375,7 +1385,7 @@ try_package_install() {
repo_subcmd="update"
repo_prefix="ubuntu/${SYSCODENAME}"
pkg_type="deb"
- pkg_suffix="_all"
+ pkg_suffix="+ubuntu${SYSVERSION}_all"
pkg_vsep="_"
pkg_install_opts="${interactive_opts}"
repo_update_opts="${interactive_opts}"
@@ -1468,8 +1478,17 @@ try_package_install() {
fi
repoconfig_name="netdata-repo${release}"
- repoconfig_file="${repoconfig_name}${pkg_vsep}${REPOCONFIG_VERSION}${pkg_suffix}.${pkg_type}"
- repoconfig_url="${REPOCONFIG_URL_PREFIX}/${repo_prefix}/${repoconfig_file}/download.${pkg_type}"
+
+ case "${pkg_type}" in
+ deb)
+ repoconfig_file="${repoconfig_name}${pkg_vsep}${REPOCONFIG_DEB_VERSION}${pkg_suffix}.${pkg_type}"
+ repoconfig_url="${REPOCONFIG_DEB_URL_PREFIX}/${repo_prefix}/${repoconfig_file}"
+ ;;
+ rpm)
+ repoconfig_file="${repoconfig_name}${pkg_vsep}${REPOCONFIG_RPM_VERSION}${pkg_suffix}.${pkg_type}"
+ repoconfig_url="${REPOCONFIG_RPM_URL_PREFIX}/${repo_prefix}/${SYSARCH}/${repoconfig_file}"
+ ;;
+ esac
if ! pkg_installed "${repoconfig_name}"; then
progress "Checking for availability of repository configuration package."
@@ -1507,7 +1526,7 @@ try_package_install() {
fi
if [ "${REPO_ACTION}" = "repositories-only" ]; then
- progress "Successfully installed repository configuraion package."
+ progress "Successfully installed repository configuration package."
deferred_warnings
cleanup
trap - EXIT
@@ -1564,25 +1583,33 @@ set_static_archive_urls() {
if [ -n "${NETDATA_OFFLINE_INSTALL_SOURCE}" ]; then
path="$(cd "${NETDATA_OFFLINE_INSTALL_SOURCE}" || exit 1; pwd)"
export NETDATA_STATIC_ARCHIVE_URL="file://${path}/netdata-${arch}-latest.gz.run"
+ export NETDATA_STATIC_ARCHIVE_NAME="netdata-${arch}-latest.gz.run"
export NETDATA_STATIC_ARCHIVE_CHECKSUM_URL="file://${path}/sha256sums.txt"
elif [ "${1}" = "stable" ]; then
if [ -n "${INSTALL_VERSION}" ]; then
- export NETDATA_STATIC_ARCHIVE_OLD_URL="https://github.com/netdata/netdata/releases/download/v${INSTALL_VERSION}/netdata-v${INSTALL_VERSION}.gz.run"
export NETDATA_STATIC_ARCHIVE_URL="https://github.com/netdata/netdata/releases/download/v${INSTALL_VERSION}/netdata-${arch}-v${INSTALL_VERSION}.gz.run"
+ export NETDATA_STATIC_ARCHIVE_OLD_URL="https://github.com/netdata/netdata/releases/download/v${INSTALL_VERSION}/netdata-v${INSTALL_VERSION}.gz.run"
+ export NETDATA_STATIC_ARCHIVE_NAME="netdata-${arch}-v${INSTALL_VERSION}.gz.run"
+ export NETDATA_STATIC_ARCHIVE_OLD_NAME="netdata-v${INSTALL_VERSION}.gz.run"
export NETDATA_STATIC_ARCHIVE_CHECKSUM_URL="https://github.com/netdata/netdata/releases/download/v${INSTALL_VERSION}/sha256sums.txt"
else
latest="$(get_redirect "https://github.com/netdata/netdata/releases/latest")"
export NETDATA_STATIC_ARCHIVE_URL="https://github.com/netdata/netdata/releases/download/${latest}/netdata-${arch}-latest.gz.run"
+ export NETDATA_STATIC_ARCHIVE_NAME="netdata-${arch}-latest.gz.run"
export NETDATA_STATIC_ARCHIVE_CHECKSUM_URL="https://github.com/netdata/netdata/releases/download/${latest}/sha256sums.txt"
fi
else
if [ -n "${INSTALL_VERSION}" ]; then
- export NETDATA_STATIC_ARCHIVE_URL="${NETDATA_TARBALL_BASEURL}/netdata-${arch}-v${INSTALL_VERSION}.gz.run"
- export NETDATA_STATIC_ARCHIVE_OLD_URL="${NETDATA_TARBALL_BASEURL}/netdata-v${INSTALL_VERSION}.gz.run"
- export NETDATA_STATIC_ARCHIVE_CHECKSUM_URL="${NETDATA_TARBALL_BASEURL}/sha256sums.txt"
+ export NETDATA_STATIC_ARCHIVE_URL="${NETDATA_TARBALL_BASEURL}/download/v${INSTALL_VERSION}/netdata-${arch}-v${INSTALL_VERSION}.gz.run"
+ export NETDATA_STATIC_ARCHIVE_OLD_URL="${NETDATA_TARBALL_BASEURL}/download/v${INSTALL_VERSION}/netdata-v${INSTALL_VERSION}.gz.run"
+ export NETDATA_STATIC_ARCHIVE_NAME="netdata-${arch}-v${INSTALL_VERSION}.gz.run"
+ export NETDATA_STATIC_ARCHIVE_OLD_NAME="netdata-v${INSTALL_VERSION}.gz.run"
+ export NETDATA_STATIC_ARCHIVE_CHECKSUM_URL="${NETDATA_TARBALL_BASEURL}/download/v${INSTALL_VERSION}/sha256sums.txt"
else
- export NETDATA_STATIC_ARCHIVE_URL="${NETDATA_TARBALL_BASEURL}/netdata-${arch}-latest.gz.run"
- export NETDATA_STATIC_ARCHIVE_CHECKSUM_URL="${NETDATA_TARBALL_BASEURL}/sha256sums.txt"
+ tag="$(get_redirect "${NETDATA_TARBALL_BASEURL}/latest")"
+ export NETDATA_STATIC_ARCHIVE_URL="${NETDATA_TARBALL_BASEURL}/download/${tag}/netdata-${arch}-latest.gz.run"
+ export NETDATA_STATIC_ARCHIVE_NAME="netdata-${arch}-latest.gz.run"
+ export NETDATA_STATIC_ARCHIVE_CHECKSUM_URL="${NETDATA_TARBALL_BASEURL}/download/${tag}/sha256sums.txt"
fi
fi
}
@@ -1597,23 +1624,9 @@ try_static_install() {
# Check status code first, so that we can provide nicer fallback for dry runs.
if check_for_remote_file "${NETDATA_STATIC_ARCHIVE_URL}"; then
- if [ -n "${NETDATA_OFFLINE_INSTALL_SOURCE}" ]; then
- netdata_agent="$(basename "${NETDATA_STATIC_ARCHIVE_URL#"file://"}")"
- elif [ -n "${INSTALL_VERSION}" ]; then
- if [ "${SELECTED_RELEASE_CHANNEL}" = "stable" ]; then
- netdata_agent="${NETDATA_STATIC_ARCHIVE_URL#"https://github.com/netdata/netdata/releases/download/v${INSTALL_VERSION}/"}"
- else
- netdata_agent="${NETDATA_STATIC_ARCHIVE_URL#"${NETDATA_TARBALL_BASEURL}/"}"
- fi
- else
- if [ "${SELECTED_RELEASE_CHANNEL}" = "stable" ]; then
- netdata_agent="${NETDATA_STATIC_ARCHIVE_URL#"https://github.com/netdata/netdata/releases/download/${latest}/"}"
- else
- netdata_agent="${NETDATA_STATIC_ARCHIVE_URL#"${NETDATA_TARBALL_BASEURL}/"}"
- fi
- fi
+ netdata_agent="${NETDATA_STATIC_ARCHIVE_NAME}"
elif [ "${SYSARCH}" = "x86_64" ] && check_for_remote_file "${NETDATA_STATIC_ARCHIVE_OLD_URL}"; then
- netdata_agent="${NETDATA_STATIC_ARCHIVE_OLD_URL#"https://github.com/netdata/netdata/releases/download/v${INSTALL_VERSION}/"}"
+ netdata_agent="${NETDATA_STATIC_ARCHIVE_OLD_NAME}"
export NETDATA_STATIC_ARCHIVE_URL="${NETDATA_STATIC_ARCHIVE_OLD_URL}"
else
warning "There is no static build available for ${SYSARCH} CPUs. This usually means we simply do not currently provide static builds for ${SYSARCH} CPUs."
@@ -1682,11 +1695,12 @@ set_source_archive_urls() {
fi
else
if [ -n "${INSTALL_VERSION}" ]; then
- export NETDATA_SOURCE_ARCHIVE_URL="${NETDATA_TARBALL_BASEURL}/netdata-v${INSTALL_VERSION}.tar.gz"
- export NETDATA_SOURCE_ARCHIVE_CHECKSUM_URL="${NETDATA_TARBALL_BASEURL}/sha256sums.txt"
+ export NETDATA_SOURCE_ARCHIVE_URL="${NETDATA_TARBALL_BASEURL}/download/v${INSTALL_VERSION}/netdata-latest.tar.gz"
+ export NETDATA_SOURCE_ARCHIVE_CHECKSUM_URL="${NETDATA_TARBALL_BASEURL}/download/v${INSTALL_VERSION}/sha256sums.txt"
else
- export NETDATA_SOURCE_ARCHIVE_URL="${NETDATA_TARBALL_BASEURL}/netdata-latest.tar.gz"
- export NETDATA_SOURCE_ARCHIVE_CHECKSUM_URL="${NETDATA_TARBALL_BASEURL}/sha256sums.txt"
+ tag="$(get_redirect "${NETDATA_TARBALL_BASEURL}/latest")"
+ export NETDATA_SOURCE_ARCHIVE_URL="${NETDATA_TARBALL_BASEURL}/download/${tag}/netdata-latest.tar.gz"
+ export NETDATA_SOURCE_ARCHIVE_CHECKSUM_URL="${NETDATA_TARBALL_BASEURL}/download/${tag}/sha256sums.txt"
fi
fi
}
@@ -1842,7 +1856,7 @@ prepare_offline_install_source() {
run mkdir -p "${1}" || fatal "Unable to create target directory for offline install preparation." F0504
fi
- run cd "${1}" || fatal "Failed to swtich to target directory for offline install preparation." F0505
+ run cd "${1}" || fatal "Failed to switch to target directory for offline install preparation." F0505
if [ "${NETDATA_ONLY_NATIVE}" -ne 1 ] && [ "${NETDATA_ONLY_BUILD}" -ne 1 ]; then
set_static_archive_urls "${SELECTED_RELEASE_CHANNEL}" "x86_64"
@@ -2088,10 +2102,6 @@ validate_args() {
}
parse_args() {
- if [ -n "${NETDATA_INSTALLER_OPTIONS}" ]; then
- warning "Explicitly specifying additional installer options with NETDATA_INSTALLER_OPTIONS is deprecated. Please instead pass the options to the script using either --local-build-options or --static-install-options as appropriate."
- fi
-
while [ -n "${1}" ]; do
case "${1}" in
"--help")
@@ -2147,11 +2157,6 @@ parse_args() {
NETDATA_DISABLE_TELEMETRY="1"
NETDATA_INSTALLER_OPTIONS="${NETDATA_INSTALLER_OPTIONS} --disable-telemetry"
;;
- "--install")
- warning "--install flag is deprecated and will be removed in a future version. Please use --install-prefix instead."
- INSTALL_PREFIX="${2}"
- shift 1
- ;;
"--install-prefix")
INSTALL_PREFIX="${2}"
shift 1
@@ -2223,11 +2228,11 @@ parse_args() {
esac
;;
"--local-build-options")
- LOCAL_BUILD_OPTIONS="${2}"
+ LOCAL_BUILD_OPTIONS="${LOCAL_BUILD_OPTIONS} ${2}"
shift 1
;;
"--static-install-options")
- STATIC_INSTALL_OPTIONS="${2}"
+ STATIC_INSTALL_OPTIONS="${STATIC_INSTALL_OPTIONS} ${2}"
shift 1
;;
"--prepare-offline-install-source")
@@ -2249,8 +2254,7 @@ parse_args() {
fi
;;
*)
- warning "Passing unrecognized option '${1}' to installer script. This behavior is deprecated and will be removed in the near future. If you intended to pass this option to the installer code, please use either --local-build-options or --static-install-options to specify it instead."
- NETDATA_INSTALLER_OPTIONS="${NETDATA_INSTALLER_OPTIONS} ${1}"
+ fatal "Unrecognized option '${1}'. If you intended to pass this option to the installer code, please use either --local-build-options or --static-install-options to specify it instead." F050E
;;
esac
shift 1
diff --git a/packaging/installer/methods/cloud-providers.md b/packaging/installer/methods/cloud-providers.md
index bc5c9aae..6b8fa6de 100644
--- a/packaging/installer/methods/cloud-providers.md
+++ b/packaging/installer/methods/cloud-providers.md
@@ -8,7 +8,7 @@ custom_edit_url: https://github.com/netdata/netdata/edit/master/packaging/instal
Netdata is fully compatible with popular cloud providers like Google Cloud Platform (GCP), Amazon Web Services (AWS),
Azure, and others. You can install Netdata on cloud instances to monitor the apps/services running there, or use
-multiple instances in a [parent-child streaming](/streaming/README.md) configuration.
+multiple instances in a [parent-child streaming](https://github.com/netdata/netdata/blob/master/streaming/README.md) configuration.
In some cases, using Netdata on these cloud providers requires unique installation or configuration steps. This page
aims to document some of those steps for popular cloud providers.
@@ -53,11 +53,11 @@ command from a remote system, and it fails, it's likely that a firewall is block
Another option is to put Netdata behind web server, which will proxy requests through standard HTTP/HTTPS ports
(80/443), which are likely already open on your instance. We have a number of guides available:
-- [Apache](/docs/Running-behind-apache.md)
-- [Nginx](/docs/Running-behind-nginx.md)
-- [Caddy](/docs/Running-behind-caddy.md)
-- [HAProxy](/docs/Running-behind-haproxy.md)
-- [lighttpd](/docs/Running-behind-lighttpd.md)
+- [Apache](https://github.com/netdata/netdata/blob/master/docs/Running-behind-apache.md)
+- [Nginx](https://github.com/netdata/netdata/blob/master/docs/Running-behind-nginx.md)
+- [Caddy](https://github.com/netdata/netdata/blob/master/docs/Running-behind-caddy.md)
+- [HAProxy](https://github.com/netdata/netdata/blob/master/docs/Running-behind-haproxy.md)
+- [lighttpd](https://github.com/netdata/netdata/blob/master/docs/Running-behind-lighttpd.md)
The next few sections outline how to add firewall rules to GCP, AWS, and Azure instances.
diff --git a/packaging/installer/methods/freebsd.md b/packaging/installer/methods/freebsd.md
index 3523157b..ea7099b3 100644
--- a/packaging/installer/methods/freebsd.md
+++ b/packaging/installer/methods/freebsd.md
@@ -45,7 +45,7 @@ gunzip netdata*.tar.gz && tar xf netdata*.tar && rm -rf netdata*.tar
Install Netdata in `/opt/netdata`. If you want to enable automatic updates, add `--auto-update` or `-u` to install `netdata-updater` in `cron` (**need root permission**):
```sh
-cd netdata-v* && ./netdata-installer.sh --install /opt && cp /opt/netdata/usr/sbin/netdata-claim.sh /usr/sbin/
+cd netdata-v* && ./netdata-installer.sh --install-prefix /opt && cp /opt/netdata/usr/sbin/netdata-claim.sh /usr/sbin/
```
You also need to enable the `netdata` service in `/etc/rc.conf`:
@@ -66,7 +66,7 @@ You can now access the Netdata dashboard by navigating to `http://NODE:19999`, r
Starting with v1.30, Netdata collects anonymous usage information by default and sends it to a self hosted PostHog instance within the Netdata infrastructure. To read
more about the information collected and how to opt-out, check the [anonymous statistics
-page](/docs/anonymous-statistics.md).
+page](https://github.com/netdata/netdata/blob/master/docs/anonymous-statistics.md).
## Updating the Agent on FreeBSD
If you have not passed the `--auto-update` or `-u` parameter for the installer to enable automatic updating, repeat the last step to update Netdata whenever a new version becomes available.
@@ -75,7 +75,7 @@ The `netdata-updater.sh` script will update your Agent.
## Optional parameters to alter your installation
| parameters | Description |
|:-----:|-----------|
-|`--install <path>`| Install netdata in `<path>.` Ex: `--install /opt` will put netdata in `/opt/netdata`|
+|`--install-prefix <path>`| Install netdata in `<path>.` Ex: `--install-prefix /opt` will put netdata in `/opt/netdata`|
| `--dont-start-it` | Do not (re)start netdata after installation|
| `--dont-wait` | Run installation in non-interactive mode|
| `--auto-update` or `-u` | Install netdata-updater in cron to update netdata automatically once per day|
diff --git a/packaging/installer/methods/kickstart.md b/packaging/installer/methods/kickstart.md
index 2555e4a8..7c1f60d1 100644
--- a/packaging/installer/methods/kickstart.md
+++ b/packaging/installer/methods/kickstart.md
@@ -1,21 +1,25 @@
<!--
title: "Install Netdata with kickstart.sh"
description: "The kickstart.sh script installs Netdata from source, including all dependencies required to connect to Netdata Cloud, with a single command."
-custom_edit_url: https://github.com/netdata/netdata/edit/master/packaging/installer/methods/kickstart.md
+custom_edit_url: "https://github.com/netdata/netdata/edit/master/packaging/installer/methods/kickstart.md"
+sidebar_label: "Install Netdata with kickstart.sh"
+learn_status: "Published"
+learn_topic_type: "Tasks"
+learn_rel_path: "Installation"
-->
import { OneLineInstallWget, OneLineInstallCurl } from '@site/src/components/OneLineInstall/'
# Install Netdata with kickstart.sh
-![](https://registry.my-netdata.io/api/v1/badge.svg?chart=web_log_nginx.requests_per_url&options=unaligned&dimensions=kickstart&group=sum&after=-3600&label=last+hour&units=kickstart%20downloads&value_color=orange&precision=0) ![](https://registry.my-netdata.io/api/v1/badge.svg?chart=web_log_nginx.requests_per_url&options=unaligned&dimensions=kickstart&group=sum&after=-86400&label=today&units=kickstart%20downloads&precision=0)
+![](https://registry.my-netdata.io/api/v1/badge.svg?chart=web_log_nginx.requests_by_url_pattern&options=unaligned&dimensions=kickstart&group=sum&after=-3600&label=last+hour&units=kickstart%20downloads&value_color=orange&precision=0) ![](https://registry.my-netdata.io/api/v1/badge.svg?chart=web_log_nginx.requests_by_url_pattern&options=unaligned&dimensions=kickstart&group=sum&after=-86400&label=today&units=kickstart%20downloads&precision=0)
This page covers detailed instructions on using and configuring the automatic one-line installation script named
`kickstart.sh`.
-The kickstart script works on all Linux distributions and macOS environments. By default, automatic nightly updates are enabled. If you are installing on macOS, make sure to check the [install documentation for macOS](macos.md) before continuing.
+The kickstart script works on all Linux distributions and macOS environments. By default, automatic nightly updates are enabled. If you are installing on macOS, make sure to check the [install documentation for macOS](https://github.com/netdata/netdata/blob/master/packaging/installer/methods/macos.md) before continuing.
-> If you are unsure whether you want nightly or stable releases, read the [installation guide](/packaging/installer/README.md#nightly-vs-stable-releases).
-> If you want to turn off [automatic updates](/packaging/installer/README.md#automatic-updates), use the `--no-updates` option. You can find more installation options below.
+> If you are unsure whether you want nightly or stable releases, read the [installation guide](https://github.com/netdata/netdata/blob/master/packaging/installer/README.md#nightly-vs-stable-releases).
+> If you want to turn off [automatic updates](https://github.com/netdata/netdata/blob/master/packaging/installer/README.md#automatic-updates), use the `--no-updates` option. You can find more installation options below.
To install Netdata, run the following as your normal user:
@@ -68,7 +72,6 @@ The `kickstart.sh` script accepts a number of optional parameters to control how
- `--disable-cloud`: For local builds, don’t build any of the cloud code at all. For native packages and static builds,
use runtime configuration to disable cloud support.
- `--require-cloud`: Only install if Netdata Cloud can be enabled. Overrides `--disable-cloud`.
-- `--install`: Specify an installation prefix for local builds (by default, we use a sane prefix based on the type of system), this option is deprecated and will be removed in a future version, please use `--install-prefix` instead.
- `--install-prefix`: Specify an installation prefix for local builds (by default, we use a sane prefix based on the type of system).
- `--install-version`: Specify the version of Netdata to install.
- `--old-install-prefix`: Specify the custom local build's installation prefix that should be removed.
@@ -76,7 +79,7 @@ The `kickstart.sh` script accepts a number of optional parameters to control how
- `--reinstall-clean`: Performs an uninstall of Netdata and clean installation.
- `--local-build-options`: Specify additional options to pass to the installer code when building locally. Only valid if `--build-only` is also specified.
- `--static-install-options`: Specify additional options to pass to the static installer code. Only valid if --static-only is also specified.
-- `--prepare-offline-install-source`: Instead of insallling the agent, prepare a directory that can be used to install on another system without needing to download anything. See our [offline installation documentation](/packaging/installer/methods/offline.md) for more info.
+- `--prepare-offline-install-source`: Instead of insallling the agent, prepare a directory that can be used to install on another system without needing to download anything. See our [offline installation documentation](https://github.com/netdata/netdata/blob/master/packaging/installer/methods/offline.md) for more info.
Additionally, the following environment variables may be used to further customize how the script runs (most users
should not need to use special values for any of these):
@@ -91,9 +94,9 @@ should not need to use special values for any of these):
### Connect node to Netdata Cloud during installation
-The `kickstart.sh` script accepts additional parameters to automatically [connect](/claim/README.md) your node to Netdata Cloud immediately after installation.
+The `kickstart.sh` script accepts additional parameters to automatically [connect](https://github.com/netdata/netdata/blob/master/claim/README.md) your node to Netdata Cloud immediately after installation.
-> Note: You either need to run the command with root privileges or run it with the user that is running the agent. More details: [Connect an agent without root privileges](/claim/README.md#connect-an-agent-without-root-privileges) section.
+> Note: You either need to run the command with root privileges or run it with the user that is running the agent. More details: [Connect an agent without root privileges](https://github.com/netdata/netdata/blob/master/claim/README.md#connect-an-agent-without-root-privileges) section.
To automatically claim nodes after installation:
@@ -106,7 +109,7 @@ To automatically claim nodes after installation:
after the install.
- `--claim-rooms`: Specify a comma-separated list of tokens for each War Room this node should appear in.
- `--claim-proxy`: Specify a proxy to use when connecting to the cloud in the form of `http://[user:pass@]host:ip` for an HTTP(S) proxy.
- See [connecting through a proxy](/claim/README.md#connect-through-a-proxy) for details.
+ See [connecting through a proxy](https://github.com/netdata/netdata/blob/master/claim/README.md#connect-through-a-proxy) for details.
- `--claim-url`: Specify a URL to use when connecting to the cloud. Defaults to `https://api.netdata.cloud`.
For example:
@@ -160,10 +163,10 @@ If the script is valid, this command will return `OK, VALID`.
## What's next?
-When you're finished with installation, check out our [single-node](/docs/quickstart/single-node.md) or
-[infrastructure](/docs/quickstart/infrastructure.md) monitoring quickstart guides based on your use case.
+When you're finished with installation, check out our [single-node](https://github.com/netdata/netdata/blob/master/docs/quickstart/single-node.md) or
+[infrastructure](https://github.com/netdata/netdata/blob/master/docs/quickstart/infrastructure.md) monitoring quickstart guides based on your use case.
-Or, skip straight to [configuring the Netdata Agent](/docs/configure/nodes.md).
+Or, skip straight to [configuring the Netdata Agent](https://github.com/netdata/netdata/blob/master/docs/configure/nodes.md).
Read through Netdata's [documentation](https://learn.netdata.cloud/docs), which is structured based on actions and
solutions, to enable features like health monitoring, alarm notifications, long-term metrics storage, exporting to
diff --git a/packaging/installer/methods/kubernetes.md b/packaging/installer/methods/kubernetes.md
index 216703a3..142c098b 100644
--- a/packaging/installer/methods/kubernetes.md
+++ b/packaging/installer/methods/kubernetes.md
@@ -1,7 +1,11 @@
<!--
title: "Deploy Kubernetes monitoring with Netdata"
description: "Deploy Netdata to monitor a Kubernetes cluster to monitor the health, performance, resource utilization, and application metrics of a Kubernetes cluster in real time."
-custom_edit_url: https://github.com/netdata/netdata/edit/master/packaging/installer/methods/kubernetes.md
+custom_edit_url: "https://github.com/netdata/netdata/edit/master/packaging/installer/methods/kubernetes.md"
+sidebar_label: "Deploy Kubernetes monitoring with Netdata"
+learn_status: "Published"
+learn_topic_type: "Tasks"
+learn_rel_path: "Installation"
-->
# Deploy Kubernetes monitoring with Netdata
@@ -41,8 +45,8 @@ dashboards available in Netdata Cloud.
## Connect your Kubernetes cluster to Netdata Cloud
-To start [Kubernetes monitoring](https://learn.netdata.cloud/docs/cloud/visualize/kubernetes/), you must first
-[connect](/claim/README.md) your Kubernetes cluster to [Netdata Cloud](https://app.netdata.cloud). The connection process securely
+To start [Kubernetes monitoring](https://github.com/netdata/netdata/blob/master/docs/cloud/visualize/kubernetes.md), you must first
+[connect](https://github.com/netdata/netdata/blob/master/claim/README.md) your Kubernetes cluster to [Netdata Cloud](https://app.netdata.cloud). The connection process securely
connects your Kubernetes cluster to stream metrics data to Netdata Cloud, enabling Kubernetes-specific visualizations
like the health map and time-series composite charts.
@@ -180,17 +184,17 @@ helm upgrade netdata netdata/netdata
## What's next?
-[Start Kubernetes monitoring](https://learn.netdata.cloud/docs/cloud/visualize/kubernetes/) in Netdata Cloud, which
+[Start Kubernetes monitoring](https://github.com/netdata/netdata/blob/master/docs/cloud/visualize/kubernetes.md) in Netdata Cloud, which
comes with meaningful visualizations out of the box.
Read our guide, [_Kubernetes monitoring with Netdata: Overview and
-visualizations_](/docs/guides/monitor/kubernetes-k8s-netdata.md), for a complete walkthrough of Netdata's Kubernetes
+visualizations_](https://github.com/netdata/netdata/blob/master/docs/guides/monitor/kubernetes-k8s-netdata.md), for a complete walkthrough of Netdata's Kubernetes
monitoring capabilities, including a health map of every container in your infrastructure, aggregated resource
utilization metrics, and application metrics.
### Related reference documentation
-- [Netdata Cloud · Kubernetes monitoring](https://learn.netdata.cloud/docs/cloud/visualize/kubernetes/)
+- [Netdata Cloud · Kubernetes monitoring](https://github.com/netdata/netdata/blob/master/docs/cloud/visualize/kubernetes.md)
- [Netdata Helm chart](https://github.com/netdata/helmchart)
- [Netdata service discovery](https://github.com/netdata/agent-service-discovery/)
diff --git a/packaging/installer/methods/macos.md b/packaging/installer/methods/macos.md
index a1b5f60c..f80f4c13 100644
--- a/packaging/installer/methods/macos.md
+++ b/packaging/installer/methods/macos.md
@@ -1,13 +1,17 @@
<!--
title: "Install Netdata on macOS"
-custom_edit_url: https://github.com/netdata/netdata/edit/master/packaging/installer/methods/macos.md
+custom_edit_url: "https://github.com/netdata/netdata/edit/master/packaging/installer/methods/macos.md"
+sidebar_label: "Install Netdata on macOS"
+learn_status: "Published"
+learn_topic_type: "Tasks"
+learn_rel_path: "Installation"
-->
# Install Netdata on macOS
Netdata works on macOS, albeit with some limitations.
-The number of charts displaying system metrics is limited, but you can use any of Netdata's [external plugins](/collectors/plugins.d/README.md) to monitor any services you might have installed on your macOS system.
-You could also use a macOS system as the parent node in a [streaming configuration](/streaming/README.md).
+The number of charts displaying system metrics is limited, but you can use any of Netdata's [external plugins](https://github.com/netdata/netdata/blob/master/collectors/plugins.d/README.md) to monitor any services you might have installed on your macOS system.
+You could also use a macOS system as the parent node in a [streaming configuration](https://github.com/netdata/netdata/blob/master/streaming/README.md).
You can install Netdata in one of the three following ways:
@@ -18,12 +22,12 @@ You can install Netdata in one of the three following ways:
Each of these installation option requires [Homebrew](https://brew.sh/) for handling dependencies.
> The Netdata Homebrew package is community-created and -maintained.
-> Community-maintained packages _may_ receive support from Netdata, but are only a best-effort affair. Learn more about [Netdata's platform support policy](/packaging/PLATFORM_SUPPORT.md).
+> Community-maintained packages _may_ receive support from Netdata, but are only a best-effort affair. Learn more about [Netdata's platform support policy](https://github.com/netdata/netdata/blob/master/packaging/PLATFORM_SUPPORT.md).
## Install Netdata with our automatic one-line installation script
**Local Netdata Agent installation**
-To install Netdata using our automatic [kickstart](/packaging/installer/README.md#automatic-one-line-installation-script) open a new terminal and run:
+To install Netdata using our automatic [kickstart](https://github.com/netdata/netdata/blob/master/packaging/installer/README.md#automatic-one-line-installation-script) open a new terminal and run:
```bash
curl https://my-netdata.io/kickstart.sh > /tmp/netdata-kickstart.sh && sh /tmp/netdata-kickstart.sh
@@ -34,25 +38,25 @@ The Netdata Agent is installed under `/usr/local/netdata`. Dependencies are hand
<!-- Potential reuse: https://learn.netdata.cloud/docs/agent/claim#connect-an-agent-running-in-macos-->
<!--Potential reuse https://learn.netdata.cloud/docs/agent/packaging/installer/methods/kickstart#connect-node-to-netdata-cloud-during-installation The following information is copied from this link.-->
-The `kickstart.sh` script accepts additional parameters to automatically [connect](/claim/README.md) your node to Netdata
+The `kickstart.sh` script accepts additional parameters to automatically [connect](https://github.com/netdata/netdata/blob/master/claim/README.md) your node to Netdata
Cloud immediately after installation. Find the `token` and `rooms` strings by [signing in to Netdata
Cloud](https://app.netdata.cloud/sign-in?cloudRoute=/spaces), then clicking on **Connect Nodes** in the [Spaces management
-area](https://learn.netdata.cloud/docs/cloud/spaces#manage-spaces).
+area](https://github.com/netdata/netdata/blob/master/docs/cloud/cloud.mdx#manage-spaces).
- `--claim-token`: Specify a unique claiming token associated with your Space in Netdata Cloud to be used to connect to the node
after the install.
- `--claim-rooms`: Specify a comma-separated list of tokens for each War Room this node should appear in.
- `--claim-proxy`: Specify a proxy to use when connecting to the cloud in the form of `http://[user:pass@]host:ip` for an HTTP(S) proxy.
- See [connecting through a proxy](/claim/README.md#connect-through-a-proxy) for details.
+ See [connecting through a proxy](https://github.com/netdata/netdata/blob/master/claim/README.md#connect-through-a-proxy) for details.
- `--claim-url`: Specify a URL to use when connecting to the cloud. Defaults to `https://api.netdata.cloud`.
For example:
```bash
-curl https://my-netdata.io/kickstart.sh > /tmp/netdata-kickstart.sh && sh /tmp/netdata-kickstart.sh --install /usr/local/ --claim-token TOKEN --claim-rooms ROOM1,ROOM2 --claim-url https://api.netdata.cloud
+curl https://my-netdata.io/kickstart.sh > /tmp/netdata-kickstart.sh && sh /tmp/netdata-kickstart.sh --install-prefix /usr/local/ --claim-token TOKEN --claim-rooms ROOM1,ROOM2 --claim-url https://api.netdata.cloud
```
The Netdata Agent is installed under `/usr/local/netdata` on your machine. Your machine will also show up as a node in your Netdata Cloud.
-If you experience issues while claiming your node, follow the steps in our [Troubleshooting](/claim/README.md#troubleshooting) documentation.
+If you experience issues while claiming your node, follow the steps in our [Troubleshooting](https://github.com/netdata/netdata/blob/master/claim/README.md#troubleshooting) documentation.
## Install Netdata via Homebrew
To install Netdata and all its dependencies, run Homebrew using the following command:
@@ -77,7 +81,7 @@ We don't recommend installing Netdata from source on macOS, as it can be difficu
```
2. Click **Install** on the Software Update popup window that appears.
-3. Use the same terminal session to install some of Netdata's prerequisites using Homebrew. If you don't want to use [Netdata Cloud](https://learn.netdata.cloud/docs/cloud/), you can omit `cmake`.
+3. Use the same terminal session to install some of Netdata's prerequisites using Homebrew. If you don't want to use [Netdata Cloud](https://github.com/netdata/netdata/blob/master/docs/cloud/cloud.mdx), you can omit `cmake`.
```bash
brew install ossp-uuid autoconf automake pkg-config libuv lz4 json-c openssl libtool cmake
@@ -93,7 +97,7 @@ We don't recommend installing Netdata from source on macOS, as it can be difficu
```bash
cd netdata/
- sudo ./netdata-installer.sh --install /usr/local
+ sudo ./netdata-installer.sh --install-prefix /usr/local
```
> Your Netdata configuration directory will be at `/usr/local/netdata/`.
@@ -102,10 +106,10 @@ We don't recommend installing Netdata from source on macOS, as it can be difficu
## What's next?
-When you're finished with installation, check out our [single-node](/docs/quickstart/single-node.md) or
-[infrastructure](/docs/quickstart/infrastructure.md) monitoring quickstart guides based on your use case.
+When you're finished with installation, check out our [single-node](https://github.com/netdata/netdata/blob/master/docs/quickstart/single-node.md) or
+[infrastructure](https://github.com/netdata/netdata/blob/master/docs/quickstart/infrastructure.md) monitoring quickstart guides based on your use case.
-Or, skip straight to [configuring the Netdata Agent](/docs/configure/nodes.md).
+Or, skip straight to [configuring the Netdata Agent](https://github.com/netdata/netdata/blob/master/docs/configure/nodes.md).
diff --git a/packaging/installer/methods/manual.md b/packaging/installer/methods/manual.md
index d3207539..46bc9a33 100644
--- a/packaging/installer/methods/manual.md
+++ b/packaging/installer/methods/manual.md
@@ -1,7 +1,11 @@
<!--
title: "Install Netdata on Linux from a Git checkout"
description: "Use the Netdata Agent source code from GitHub, plus helper scripts to set up your system, to install Netdata without packages or binaries."
-custom_edit_url: https://github.com/netdata/netdata/edit/master/packaging/installer/methods/manual.md
+custom_edit_url: "https://github.com/netdata/netdata/edit/master/packaging/installer/methods/manual.md"
+sidebar_label: "Install Netdata on Linux from a Git checkout"
+learn_status: "Published"
+learn_topic_type: "Tasks"
+learn_rel_path: "Installation"
-->
# Install Netdata on Linux from a Git checkout
@@ -96,10 +100,7 @@ Netdata plugins and various aspects of Netdata can be enabled or benefit when th
| `python`|for most of the external plugins|
| `python-yaml`|used for monitoring **beanstalkd**|
| `python-beanstalkc`|used for monitoring **beanstalkd**|
-| `python-dnspython`|used for monitoring DNS query time|
-| `python-ipaddress`|used for monitoring **DHCPd**<br/>this package is required only if the system has python v2. python v3 has this functionality embedded|
| `python-mysqldb`<br/>or<br/>`python-pymysql`|used for monitoring **mysql** or **mariadb** databases<br/>`python-mysqldb` is a lot faster and thus preferred|
-| `python-pymongo`|used for monitoring **mongodb** databases|
| `nodejs`|used for `node.js` plugins for monitoring **named** and **SNMP** devices|
| `lm-sensors`|for monitoring **hardware sensors**|
| `libelf`|for monitoring kernel-level metrics using eBPF|
@@ -123,7 +124,7 @@ Netdata Cloud support may require the following packages to be installed:
|:---------:|--------------------------------------------------------------------------------------------------------------------------------------|
| `cmake` | Needed at build time if you aren't using your distribution's version of libwebsockets or are building on a platform other than Linux |
| `openssl` | Needed to secure communications with the Netdata Cloud |
-| `protobuf`| Used for the new Cloud<->Agent binary protocol
+| `protobuf`| Used for the new Cloud<->Agent binary protocol |
*Netdata will greatly benefit if you have the above packages installed, but it will still work without them.*
@@ -189,7 +190,7 @@ cd netdata
- You can also append `--stable-channel` to fetch and install only the official releases from GitHub, instead of the nightly builds.
-- If you don't want to install it on the default directories, you can run the installer like this: `./netdata-installer.sh --install /opt`. This one will install Netdata in `/opt/netdata`.
+- If you don't want to install it on the default directories, you can run the installer like this: `./netdata-installer.sh --install-prefix /opt`. This one will install Netdata in `/opt/netdata`.
- If your server does not have access to the internet and you have manually put the installation directory on your server, you will need to pass the option `--disable-go` to the installer. The option will prevent the installer from attempting to download and install `go.d.plugin`.
@@ -201,22 +202,22 @@ cd netdata
- `--dont-start-it`: Prevent the installer from starting Netdata automatically.
- `--stable-channel`: Automatically update only on the release of new major versions.
- `--nightly-channel`: Automatically update on every new nightly build.
-- `--disable-telemetry`: Opt-out of [anonymous statistics](/docs/anonymous-statistics.md) we use to make
+- `--disable-telemetry`: Opt-out of [anonymous statistics](https://github.com/netdata/netdata/blob/master/docs/anonymous-statistics.md) we use to make
Netdata better.
- `--no-updates`: Prevent automatic updates of any kind.
- `--reinstall`: If an existing install is detected, reinstall instead of trying to update it. Note that this
cannot be used to change installation types.
-- `--local-files`: Used for [offline installations](offline.md). Pass four file paths: the Netdata
+- `--local-files`: Used for [offline installations](https://github.com/netdata/netdata/blob/master/packaging/installer/methods/offline.md). Pass four file paths: the Netdata
tarball, the checksum file, the go.d plugin tarball, and the go.d plugin config tarball, to force kickstart run the
process using those files. This option conflicts with the `--stable-channel` option. If you set this _and_
`--stable-channel`, Netdata will use the local files.
### Connect node to Netdata Cloud during installation
-Unlike the [`kickstart.sh`](/packaging/installer/methods/kickstart.md), the `netdata-installer.sh` script does
-not allow you to automatically [connect](/claim/README.md) your node to Netdata Cloud immediately after installation.
+Unlike the [`kickstart.sh`](https://github.com/netdata/netdata/blob/master/packaging/installer/methods/kickstart.md), the `netdata-installer.sh` script does
+not allow you to automatically [connect](https://github.com/netdata/netdata/blob/master/claim/README.md) your node to Netdata Cloud immediately after installation.
-See the [connect to cloud](/claim/README.md) doc for details on connecting a node with a manual installation of Netdata.
+See the [connect to cloud](https://github.com/netdata/netdata/blob/master/claim/README.md) doc for details on connecting a node with a manual installation of Netdata.
### 'nonrepresentable section on output' errors
@@ -228,10 +229,10 @@ In most cases, you can do this by running `CC=gcc ./netdata-installer.sh`.
## What's next?
-When you're finished with installation, check out our [single-node](/docs/quickstart/single-node.md) or
-[infrastructure](/docs/quickstart/infrastructure.md) monitoring quickstart guides based on your use case.
+When you're finished with installation, check out our [single-node](https://github.com/netdata/netdata/blob/master/docs/quickstart/single-node.md) or
+[infrastructure](https://github.com/netdata/netdata/blob/master/docs/quickstart/infrastructure.md) monitoring quickstart guides based on your use case.
-Or, skip straight to [configuring the Netdata Agent](/docs/configure/nodes.md).
+Or, skip straight to [configuring the Netdata Agent](https://github.com/netdata/netdata/blob/master/docs/configure/nodes.md).
Read through Netdata's [documentation](https://learn.netdata.cloud/docs), which is structured based on actions and
solutions, to enable features like health monitoring, alarm notifications, long-term metrics storage, exporting to
diff --git a/packaging/installer/methods/offline.md b/packaging/installer/methods/offline.md
index 5e92976e..e49f1d2e 100644
--- a/packaging/installer/methods/offline.md
+++ b/packaging/installer/methods/offline.md
@@ -1,7 +1,11 @@
<!--
title: "Install Netdata on offline systems"
description: "Install the Netdata Agent on offline/air gapped systems to benefit from real-time, per-second monitoring without connecting to the internet."
-custom_edit_url: https://github.com/netdata/netdata/edit/master/packaging/installer/methods/offline.md
+custom_edit_url: "https://github.com/netdata/netdata/edit/master/packaging/installer/methods/offline.md"
+sidebar_label: "Install Netdata on offline systems"
+learn_status: "Published"
+learn_topic_type: "Tasks"
+learn_rel_path: "Installation"
-->
# Install Netdata on offline systems
@@ -41,7 +45,7 @@ curl https://my-netdata.io/kickstart.sh > /tmp/netdata-kickstart.sh && sh /tmp/n
This will create a directory called `netdata-offline` in the current directory and place all the files required for an offline install in it.
If you want to use a specific release channel (nightly or stable), it _must_ be specified on this step using the
-apporpriate option for the kickstart script.
+appropriate option for the kickstart script.
## Installing on the target system
@@ -50,16 +54,16 @@ target system. This can be done in any manner you like, as long as filenames are
After copying the files, simply run the `install.sh` script located in the
offline install source directory. It accepts all the [same options as the kickstart
-script](/packaging/installer/methods/kickstart.md#optional-parameters-to-alter-your-installation) for further
+script](https://github.com/netdata/netdata/blob/master/packaging/installer/methods/kickstart.md#optional-parameters-to-alter-your-installation) for further
customization of the installation, though it will default to not enabling automatic updates (as they are not
supported on offline installs).
## What's next?
-When you're finished with installation, check out our [single-node](/docs/quickstart/single-node.md) or
-[infrastructure](/docs/quickstart/infrastructure.md) monitoring quickstart guides based on your use case.
+When you're finished with installation, check out our [single-node](https://github.com/netdata/netdata/blob/master/docs/quickstart/single-node.md) or
+[infrastructure](https://github.com/netdata/netdata/blob/master/docs/quickstart/infrastructure.md) monitoring quickstart guides based on your use case.
-Or, skip straight to [configuring the Netdata Agent](/docs/configure/nodes.md).
+Or, skip straight to [configuring the Netdata Agent](https://github.com/netdata/netdata/blob/master/docs/configure/nodes.md).
Read through Netdata's [documentation](https://learn.netdata.cloud/docs), which is structured based on actions and
solutions, to enable features like health monitoring, alarm notifications, long-term metrics storage, exporting to
diff --git a/packaging/installer/methods/packages.md b/packaging/installer/methods/packages.md
new file mode 100644
index 00000000..13551280
--- /dev/null
+++ b/packaging/installer/methods/packages.md
@@ -0,0 +1,89 @@
+<!--
+title: "Install Netdata using native DEB/RPM packages."
+description: "Instructions for how to install Netdata using native DEB or RPM packages."
+custom_edit_url: "https://github.com/netdata/netdata/edit/master/packaging/installer/methods/packages.md"
+sidebar_label: "Install Netdata using native DEB/RPM packages."
+learn_status: "Published"
+learn_topic_type: "Tasks"
+learn_rel_path: "Installation"
+-->
+
+# Installing Netdata using native DEB or RPM packages.
+
+For most common Linux distributions that use either DEB or RPM packages, Netdata provides pre-built native packages
+for current releases in-line with
+our [official platform support policy](https://github.com/netdata/netdata/blob/master/packaging/PLATFORM_SUPPORT.md).
+These packages will be used by default when attempting to install on a supported platform using our
+[kickstart.sh installer script](https://github.com/netdata/netdata/blob/master/packaging/installer/methods/kickstart.md).
+
+When using the kickstart script, you can force usage of native DEB or RPM packages by passing the option
+`--native-only` when invoking the script. This will cause it to only attempt to use native packages for the install,
+and fail if it cannot do so.
+
+## Manual setup of RPM packages.
+
+Netdata’s official RPM repositories are hosted at https://repo.netdata.cloud/repos. We provide four groups of
+repositories at that top level:
+
+- `stable`: Contains packages for stable releases of the Netdata Agent.
+- `edge`: Contains packages for nightly builds of the Netdata Agent.
+- `repoconfig`: Provides packages that set up configuration files for using the other repositories.
+- `devel`: Is used for one-off development builds of the Netdata Agent, and can simply be ignored by users.
+
+Within each top level group of repositories, there are directories for each supported group of distributions:
+
+- `el`: Is for Red Hat Enterprise Linux and binary compatible distros, such as CentOS, Alma Linux, and Rocky Linux.
+- `fedora`: Is for Fedora and binary compatible distros.
+- `ol`: Is for Oracle Linux and binary compatible distros.
+- `opensuse`: Is for openSUSE and binary compatible distros.
+
+Under each of those directories is a directory for each supported release of that distribution, and under that a
+directory for each supported CPU architecture which contains the actual repository.
+
+For example, for stable release packages for RHEL 9 on 64-bit x86, the full URL for the repository would be
+https://repo.netdata.cloud/repos/stable/el/9/x86\_64/
+
+Our RPM packages and repository metadata are signed using a GPG key with a user name of ‘Netdatabot’. The
+current key fingerprint is `6588FDD7B14721FE7C3115E6F9177B5265F56346`. The associated public key can be fetched from
+`https://repo.netdata.cloud/netdatabot.gpg.key`.
+
+If you are explicitly configuring a system to use our repositories, the recommended setup is to download the
+appropriate repository configuration package from https://repo.netdata.cloud/repos/repoconfig and install it
+directly on the target system using the system package manager. This will ensure any packages needed to use the
+repository are also installed, and will help enable a seamless transition if we ever need to change our infrastructure.
+
+## Manual setup of DEB packages.
+
+Netdata’s official DEB repositories are hosted at https://repo.netdata.cloud/repos. We provide four groups of
+repositories at that top level:
+
+- `stable`: Contains packages for stable releases of the Netdata Agent.
+- `edge`: Contains packages for nightly builds of the Netdata Agent.
+- `repoconfig`: Provides packages that set up configuration files for using the other repositories.
+- `devel`: Is used for one-off development builds of the Netdata Agent, and can simply be ignored by users.
+
+Within each top level group of repositories, there are directories for each supported group of distributions:
+
+- `debian`: Is for Debian Linux and binary compatible distros.
+- `ubuntu`: Is for Ubuntu Linux and binary compatible distros.
+
+Under each of these directories is a directory for each supported release, corresponding to the release codename.
+
+These repositories are set up as what Debian calls ‘flat repositories’, and are available via both HTTP and HTTPS.
+
+As a result of this structure, the required APT sources entry for stable packages for Debian 11 (Bullseye) is:
+
+```
+deb http://repo.netdata.cloud/repos/stable/debian/ bullseye/
+```
+
+Note the `/` at the end of the codename, this is required for the repository to be processed correctly.
+
+Our DEB packages and repository metadata are signed using a GPG key with a user name of ‘Netdatabot’. The
+current key fingerprint is `6588FDD7B14721FE7C3115E6F9177B5265F56346`. The associated public key can be fetched from
+`https://repo.netdata.cloud/netdatabot.gpg.key`.
+
+If you are explicitly configuring a system to use our repositories, the recommended setup is to download the
+appropriate repository configuration package from https://repo.netdata.cloud/repos/repoconfig and install it
+directly on the target system using the system package manager. This will ensure any packages needed to use the
+repository are also installed, and will help enable a seamless transition if we ever need to change our infrastructure.
diff --git a/packaging/installer/methods/source.md b/packaging/installer/methods/source.md
index d8f4f0bd..ecf35382 100644
--- a/packaging/installer/methods/source.md
+++ b/packaging/installer/methods/source.md
@@ -1,7 +1,11 @@
<!--
title: "Manually build Netdata from source"
description: "Package maintainers and power users may be interested in manually building Netdata from source without using any of our installation scripts."
-custom_edit_url: https://github.com/netdata/netdata/edit/master/packaging/installer/methods/source.md
+custom_edit_url: "https://github.com/netdata/netdata/edit/master/packaging/installer/methods/source.md"
+sidebar_label: "Manually build Netdata from source"
+learn_status: "Published"
+learn_topic_type: "Tasks"
+learn_rel_path: "Installation"
-->
# Manually build Netdata from source
@@ -9,7 +13,7 @@ custom_edit_url: https://github.com/netdata/netdata/edit/master/packaging/instal
These instructions are for advanced users and distribution package
maintainers. Unless this describes you, you almost certainly want
to follow [our guide for manually installing Netdata from a git
-checkout](/packaging/installer/methods/manual.md) instead.
+checkout](https://github.com/netdata/netdata/blob/master/packaging/installer/methods/manual.md) instead.
## Required dependencies
diff --git a/packaging/installer/methods/synology.md b/packaging/installer/methods/synology.md
index 30ec3035..e3602df5 100644
--- a/packaging/installer/methods/synology.md
+++ b/packaging/installer/methods/synology.md
@@ -26,7 +26,7 @@ installations run it as the `netdata` user, you might wish to do the same. This
2. Create a user `netdata` via the Synology user interface. Give it no access to anything and a random password. Assign
the user to the `netdata` group. Netdata will chuid to this user when running.
3. Change ownership of the following directories, as defined in [Netdata
- Security](/docs/netdata-security.md#security-design):
+ Security](https://github.com/netdata/netdata/blob/master/docs/netdata-security.md#security-design):
```sh
chown -R root:netdata /opt/netdata/usr/share/netdata
diff --git a/packaging/installer/netdata-uninstaller.sh b/packaging/installer/netdata-uninstaller.sh
index 45ec73fc..2f2e89ff 100755
--- a/packaging/installer/netdata-uninstaller.sh
+++ b/packaging/installer/netdata-uninstaller.sh
@@ -426,7 +426,7 @@ portable_del_group() {
# Linux
if command -v groupdel 1> /dev/null 2>&1; then
- if grep -q "${groupname}" /etc/group; then
+ if get_group "${groupname}" > /dev/null 2>&1; then
run groupdel "${groupname}" && return 0
else
info "Group ${groupname} already removed in a previous step."
diff --git a/packaging/installer/netdata-updater.sh b/packaging/installer/netdata-updater.sh
index d018d67d..130507c1 100755
--- a/packaging/installer/netdata-updater.sh
+++ b/packaging/installer/netdata-updater.sh
@@ -34,6 +34,9 @@ set -e
PACKAGES_SCRIPT="https://raw.githubusercontent.com/netdata/netdata/master/packaging/installer/install-required-packages.sh"
+NETDATA_STABLE_BASE_URL="${NETDATA_BASE_URL:-https://github.com/netdata/netdata/releases}"
+NETDATA_NIGHTLY_BASE_URL="${NETDATA_BASE_URL:-https://github.com/netdata/netdata-nightlies/releases}"
+
script_dir="$(CDPATH='' cd -- "$(dirname -- "$0")" && pwd -P)"
if [ -x "${script_dir}/netdata-updater" ]; then
@@ -369,13 +372,13 @@ download() {
}
get_netdata_latest_tag() {
- dest="${1}"
- url="https://github.com/netdata/netdata/releases/latest"
+ url="${1}/latest"
+ dest="${2}"
if command -v curl >/dev/null 2>&1; then
tag=$(curl "${url}" -s -L -I -o /dev/null -w '%{url_effective}' | grep -m 1 -o '[^/]*$')
elif command -v wget >/dev/null 2>&1; then
- tag=$(wget --max-redirect=0 "${url}" 2>&1 | grep Location | cut -d ' ' -f2 | grep -m 1 -o '[^/]*$')
+ tag=$(wget -S -O /dev/null "${url}" 2>&1 | grep -m 1 Location | grep -o '[^/]*$')
else
fatal "I need curl or wget to proceed, but neither of them are available on this system." U0006
fi
@@ -439,7 +442,12 @@ self_update() {
parse_version() {
r="${1}"
- if echo "${r}" | grep -q '^v.*'; then
+ if [ "${r}" = "latest" ]; then
+ # If we get ‘latest’ as a version, return the largest possible
+ # version value.
+ printf "99999999999999"
+ return 0
+ elif echo "${r}" | grep -q '^v.*'; then
# shellcheck disable=SC2001
# XXX: Need a regex group substitution here.
r="$(echo "${r}" | sed -e 's/^v\(.*\)/\1/')"
@@ -463,9 +471,9 @@ parse_version() {
get_latest_version() {
if [ "${RELEASE_CHANNEL}" = "stable" ]; then
- get_netdata_latest_tag /dev/stdout
+ get_netdata_latest_tag "${NETDATA_STABLE_BASE_URL}" /dev/stdout
else
- download "$NETDATA_NIGHTLIES_BASEURL/latest-version.txt" /dev/stdout
+ get_netdata_latest_tag "${NETDATA_NIGHTLY_BASE_URL}" /dev/stdout
fi
}
@@ -529,12 +537,13 @@ set_tarball_urls() {
fi
if [ "$1" = "stable" ]; then
- latest="$(get_netdata_latest_tag /dev/stdout)"
- export NETDATA_TARBALL_URL="https://github.com/netdata/netdata/releases/download/$latest/${filename}"
- export NETDATA_TARBALL_CHECKSUM_URL="https://github.com/netdata/netdata/releases/download/$latest/sha256sums.txt"
+ latest="$(get_netdata_latest_tag "${NETDATA_STABLE_BASE_URL}" /dev/stdout)"
+ export NETDATA_TARBALL_URL="${NETDATA_STABLE_BASE_URL}/download/$latest/${filename}"
+ export NETDATA_TARBALL_CHECKSUM_URL="${NETDATA_STABLE_BASE_URL}/download/$latest/sha256sums.txt"
else
- export NETDATA_TARBALL_URL="$NETDATA_NIGHTLIES_BASEURL/${filename}"
- export NETDATA_TARBALL_CHECKSUM_URL="$NETDATA_NIGHTLIES_BASEURL/sha256sums.txt"
+ tag="$(get_netdata_latest_tag "${NETDATA_NIGHTLY_BASE_URL}" /dev/stdout)"
+ export NETDATA_TARBALL_URL="${NETDATA_NIGHTLY_BASE_URL}/download/${tag}/${filename}"
+ export NETDATA_TARBALL_CHECKSUM_URL="${NETDATA_NIGHTLY_BASE_URL}/download/${tag}/sha256sums.txt"
fi
}
@@ -769,8 +778,8 @@ update_binpkg() {
opensuse)
pm_cmd="zypper"
repo_subcmd="--gpg-auto-import-keys refresh"
- upgrade_cmd="upgrade"
- pkg_install_opts="${interactive_opts} --allow-unsigned-rpm"
+ upgrade_cmd="update"
+ pkg_install_opts="${interactive_opts}"
repo_update_opts=""
pkg_installed_check="rpm -q"
INSTALL_TYPE="binpkg-rpm"
@@ -907,9 +916,6 @@ export NETDATA_LIB_DIR="${NETDATA_LIB_DIR:-${NETDATA_PREFIX}/var/lib/netdata}"
# Source the tarball checksum, if not already available from environment (for existing installations with the old logic)
[ -z "${NETDATA_TARBALL_CHECKSUM}" ] && [ -f "${NETDATA_LIB_DIR}/netdata.tarball.checksum" ] && NETDATA_TARBALL_CHECKSUM="$(cat "${NETDATA_LIB_DIR}/netdata.tarball.checksum")"
-# Grab the nightlies baseurl (defaulting to our Google Storage bucket)
-export NETDATA_NIGHTLIES_BASEURL="${NETDATA_NIGHTLIES_BASEURL:-https://storage.googleapis.com/netdata-nightlies}"
-
if echo "$INSTALL_TYPE" | grep -qv ^binpkg && [ "${INSTALL_UID}" != "$(id -u)" ]; then
fatal "You are running this script as user with uid $(id -u). We recommend to run this script as root (user with uid 0)" U0011
fi
diff --git a/packaging/libbpf.checksums b/packaging/libbpf.checksums
index 9a8b8f8c..e0b91c0c 100644
--- a/packaging/libbpf.checksums
+++ b/packaging/libbpf.checksums
@@ -1 +1 @@
-63fe4ac3f6807e8ff4cd3af2ffae0091eb177fb7f6aca2f03d3f201a609b988c v1.0.1_netdata.tar.gz
+f2a8214c967153fcbb7a8f2af59c23a38f6e175384878dd37648649c5d8182c4 v1.1_netdata.tar.gz
diff --git a/packaging/libbpf.version b/packaging/libbpf.version
index bb58ffc4..b0797d5a 100644
--- a/packaging/libbpf.version
+++ b/packaging/libbpf.version
@@ -1 +1 @@
-1.0.1_netdata
+1.1_netdata
diff --git a/packaging/makeself/install-or-update.sh b/packaging/makeself/install-or-update.sh
index be2b2f75..52a23fc7 100755
--- a/packaging/makeself/install-or-update.sh
+++ b/packaging/makeself/install-or-update.sh
@@ -27,7 +27,7 @@ fi
STARTIT=1
REINSTALL_OPTIONS=""
-RELEASE_CHANNEL="nightly" # check .travis/create_artifacts.sh before modifying
+RELEASE_CHANNEL="nightly"
while [ "${1}" ]; do
case "${1}" in
@@ -121,6 +121,11 @@ if portable_add_group netdata; then
run_failed "Failed to add netdata user to secondary groups"
fi
done
+ # Netdata must be able to read /etc/pve/qemu-server/* and /etc/pve/lxc/*
+ # for reading VMs/containers names, CPU and memory limits on Proxmox.
+ if [ -d "/etc/pve" ]; then
+ portable_add_user_to_group "www-data" netdata && NETDATA_ADDED_TO_GROUPS="${NETDATA_ADDED_TO_GROUPS} www-data"
+ fi
NETDATA_USER="netdata"
NETDATA_GROUP="netdata"
else
@@ -218,12 +223,6 @@ if [ -f "usr/libexec/netdata/plugins.d/go.d.plugin" ] && command -v setcap 1>/de
run setcap "cap_net_admin+epi cap_net_raw=eip" "usr/libexec/netdata/plugins.d/go.d.plugin"
fi
-# fix the fping binary
-if [ -f bin/fping ]; then
- run chown root:${NETDATA_GROUP} bin/fping
- run chmod 4750 bin/fping
-fi
-
# -----------------------------------------------------------------------------
echo "Configure TLS certificate paths"
diff --git a/packaging/makeself/jobs/50-fping-5.1.install.sh b/packaging/makeself/jobs/50-fping-5.1.install.sh
deleted file mode 100755
index 644b5524..00000000
--- a/packaging/makeself/jobs/50-fping-5.1.install.sh
+++ /dev/null
@@ -1,45 +0,0 @@
-#!/usr/bin/env bash
-# SPDX-License-Identifier: GPL-3.0-or-later
-
-# shellcheck source=packaging/makeself/functions.sh
-. "$(dirname "${0}")/../functions.sh" "${@}" || exit 1
-
-version="5.1"
-
-# shellcheck disable=SC2015
-[ "${GITHUB_ACTIONS}" = "true" ] && echo "::group::Building fping" || true
-
-fetch "fping-${version}" "https://fping.org/dist/fping-${version}.tar.gz" \
- 1ee5268c063d76646af2b4426052e7d81a42b657e6a77d8e7d3d2e60fd7409fe fping
-
-export CFLAGS="-static -I/openssl-static/include -pipe"
-export LDFLAGS="-static -L/openssl-static/lib"
-export PKG_CONFIG_PATH="/openssl-static/lib/pkgconfig"
-
-if [ "${CACHE_HIT:-0}" -eq 0 ]; then
- run ./configure \
- --prefix="${NETDATA_INSTALL_PATH}" \
- --enable-ipv4 \
- --enable-ipv6 \
- --disable-dependency-tracking
-
- cat > doc/Makefile <<-EOF
- all:
- clean:
- install:
- EOF
-
- run make clean
- run make -j "$(nproc)"
-fi
-
-run make install
-
-store_cache fping "${NETDATA_MAKESELF_PATH}/tmp/fping-${version}"
-
-if [ "${NETDATA_BUILD_WITH_DEBUG}" -eq 0 ]; then
- run strip "${NETDATA_INSTALL_PATH}"/bin/fping
-fi
-
-# shellcheck disable=SC2015
-[ "${GITHUB_ACTIONS}" = "true" ] && echo "::endgroup::" || true
diff --git a/packaging/makeself/jobs/70-netdata-git.install.sh b/packaging/makeself/jobs/70-netdata-git.install.sh
index ea6902a4..2c4fb300 100755
--- a/packaging/makeself/jobs/70-netdata-git.install.sh
+++ b/packaging/makeself/jobs/70-netdata-git.install.sh
@@ -27,7 +27,7 @@ export PKG_CONFIG_PATH="/openssl-static/lib/pkgconfig"
export CMAKE_FLAGS="-DOPENSSL_ROOT_DIR=/openssl-static -DOPENSSL_LIBRARIES=/openssl-static/lib -DCMAKE_INCLUDE_DIRECTORIES_PROJECT_BEFORE=/openssl-static -DLWS_OPENSSL_INCLUDE_DIRS=/openssl-static/include -DLWS_OPENSSL_LIBRARIES=/openssl-static/lib/libssl.a;/openssl-static/lib/libcrypto.a"
run ./netdata-installer.sh \
- --install "${NETDATA_INSTALL_PARENT}" \
+ --install-prefix "${NETDATA_INSTALL_PARENT}" \
--dont-wait \
--dont-start-it \
--require-cloud \
diff --git a/packaging/repoconfig/Makefile b/packaging/repoconfig/Makefile
index d0f246ac..18b9887f 100644
--- a/packaging/repoconfig/Makefile
+++ b/packaging/repoconfig/Makefile
@@ -4,23 +4,26 @@ all: $(FILES)
netdata.list: netdata.list.in
cp netdata.list.in netdata.list
- set -a && . /etc/os-release && sed -i -e "s/__DISTRO__/$${ID}/" -e "s/__SUITE__/$${VERSION_CODENAME}/" -e "s/__VARIANT__//" netdata.list
+ set -a && . /etc/os-release && sed -i -e "s/__DISTRO__/$${ID}/" -e "s/__SUITE__/$${VERSION_CODENAME}/" -e "s/__VARIANT__/stable/" netdata.list
netdata-edge.list: netdata.list.in
cp netdata.list.in netdata-edge.list
- set -a && . /etc/os-release && sed -i -e "s/__DISTRO__/$${ID}/" -e "s/__SUITE__/$${VERSION_CODENAME}/" -e "s/__VARIANT__/-edge/" netdata-edge.list
+ set -a && . /etc/os-release && sed -i -e "s/__DISTRO__/$${ID}/" -e "s/__SUITE__/$${VERSION_CODENAME}/" -e "s/__VARIANT__/edge/" netdata-edge.list
-netdata-archive-keyring.gpg:
- curl -L https://packagecloud.io/netdata/netdata/gpgkey | gpg --dearmor > netdata-archive-keyring.gpg
+netdata.gpg.key:
+ curl -L https://repo.netdata.cloud/netdatabot.gpg.key > $@
-netdata-edge-archive-keyring.gpg:
- curl -L https://packagecloud.io/netdata/netdata-edge/gpgkey | gpg --dearmor > netdata-edge-archive-keyring.gpg
+netdata-archive-keyring.gpg: netdata.gpg.key
+ gpg --dearmor > $@ < $<
-netdata-repoconfig-archive-keyring.gpg:
- curl -L https://packagecloud.io/netdata/netdata-repoconfig/gpgkey | gpg --dearmor > netdata-repoconfig-archive-keyring.gpg
+netdata-edge-archive-keyring.gpg: netdata.gpg.key
+ gpg --dearmor > $@ < $<
+
+netdata-repoconfig-archive-keyring.gpg: netdata.gpg.key
+ gpg --dearmor > $@ < $<
debian/tmp:
- mkdir -p debian/tmp
+ mkdir -p $@
install: $(FILES) debian/tmp
cp $(FILES) debian/tmp/
@@ -29,3 +32,4 @@ clean:
rm -f $(FILES)
.PHONY: clean
+.INTERMEDIATE: netdatabot.gpg.key
diff --git a/packaging/repoconfig/debian/changelog b/packaging/repoconfig/debian/changelog
index 57c12d71..02eedfc3 100644
--- a/packaging/repoconfig/debian/changelog
+++ b/packaging/repoconfig/debian/changelog
@@ -1,3 +1,10 @@
+netdata-repo (2-1) unstable; urgency=medium
+
+ * Switched to new package hosting infrastructure
+ * Removed apt-transport-https requirement
+
+ -- Netdata Builder <bot@netdata.cloud> Wed, 18 Jan 2023 08:30:00 -0500
+
netdata-repo (1-2) unstable; urgency=medium
* Fixed package file naming for repo layout compliance
diff --git a/packaging/repoconfig/debian/control b/packaging/repoconfig/debian/control
index 5fdcf140..fdea6a82 100644
--- a/packaging/repoconfig/debian/control
+++ b/packaging/repoconfig/debian/control
@@ -8,12 +8,12 @@ Homepage: https://netdata.cloud
Package: netdata-repo
Architecture: all
-Depends: apt-transport-https, debian-archive-keyring, gnupg
+Depends: debian-archive-keyring, gnupg
Conflicts: netdata-repo-edge
Description: Configuration for the official Netdata Stable package repository.
Package: netdata-repo-edge
Architecture:all
-Depends: apt-transport-https, debian-archive-keyring, gnupg
+Depends: debian-archive-keyring, gnupg
Conflicts: netdata-repo
Description: Configuration for the official Netdata Edge package repository.
diff --git a/packaging/repoconfig/debian/copyright b/packaging/repoconfig/debian/copyright
index 193b45e6..44b59693 100644
--- a/packaging/repoconfig/debian/copyright
+++ b/packaging/repoconfig/debian/copyright
@@ -4,7 +4,7 @@ Upstream-Contact: Costa Tsaousis <costa@netdata.cloud>
Source: https://github.com/netdata/netdata
Files: *
-Copyright: 2021 Netdata Inc.
+Copyright: 2021-2023 Netdata Inc.
License: GPL-3+
On Debian systems, the complete text of the GNU General Public
License version 3 can be found in /usr/share/common-licenses/GPL-3.
diff --git a/packaging/repoconfig/netdata-edge.repo.centos b/packaging/repoconfig/netdata-edge.repo.centos
index af56384f..fd96f0d7 100644
--- a/packaging/repoconfig/netdata-edge.repo.centos
+++ b/packaging/repoconfig/netdata-edge.repo.centos
@@ -1,9 +1,9 @@
[netdata-edge]
name=Netdata Edge
-baseurl=https://packagecloud.io/netdata/netdata-edge/el/$releasever/$basearch
+baseurl=https://repo.netdata.cloud/repos/edge/el/$releasever/$basearch
repo_gpgcheck=1
-gpgcheck=0
-gpgkey=https://packagecloud.io/netdata/netdata-edge/gpgkey
+gpgcheck=1
+gpgkey=https://repo.netdata.cloud/netdatabot.gpg.key
enabled=1
sslverify=1
sslcacert=/etc/pki/tls/certs/ca-bundle.crt
@@ -11,10 +11,10 @@ priority=50
[netdata-repoconfig]
name=Netdata Repository Config
-baseurl=https://packagecloud.io/netdata/netdata-repoconfig/el/$releasever/$basearch
+baseurl=https://repo.netdata.cloud/repos/repoconfig/el/$releasever/$basearch
repo_gpgcheck=1
-gpgcheck=0
-gpgkey=https://packagecloud.io/netdata/netdata-repoconfig/gpgkey
+gpgcheck=1
+gpgkey=https://repo.netdata.cloud/netdatabot.gpg.key
enabled=1
sslverify=1
sslcacert=/etc/pki/tls/certs/ca-bundle.crt
diff --git a/packaging/repoconfig/netdata-edge.repo.fedora b/packaging/repoconfig/netdata-edge.repo.fedora
index 902e00c1..03b0e9c7 100644
--- a/packaging/repoconfig/netdata-edge.repo.fedora
+++ b/packaging/repoconfig/netdata-edge.repo.fedora
@@ -1,9 +1,9 @@
[netdata-edge]
name=Netdata Edge
-baseurl=https://packagecloud.io/netdata/netdata-edge/fedora/$releasever/$basearch
+baseurl=https://repo.netdata.cloud/repos/edge/fedora/$releasever/$basearch
repo_gpgcheck=1
-gpgcheck=0
-gpgkey=https://packagecloud.io/netdata/netdata-edge/gpgkey
+gpgcheck=1
+gpgkey=https://repo.netdata.cloud/netdatabot.gpg.key
enabled=1
sslverify=1
sslcacert=/etc/pki/tls/certs/ca-bundle.crt
@@ -11,10 +11,10 @@ priority=50
[netdata-repoconfig]
name=Netdata Repository Config
-baseurl=https://packagecloud.io/netdata/netdata-repoconfig/fedora/$releasever/$basearch
+baseurl=https://repo.netdata.cloud/repos/repoconfig/fedora/$releasever/$basearch
repo_gpgcheck=1
-gpgcheck=0
-gpgkey=https://packagecloud.io/netdata/netdata-repoconfig/gpgkey
+gpgcheck=1
+gpgkey=https://repo.netdata.cloud/netdatabot.gpg.key
enabled=1
sslverify=1
sslcacert=/etc/pki/tls/certs/ca-bundle.crt
diff --git a/packaging/repoconfig/netdata-edge.repo.ol b/packaging/repoconfig/netdata-edge.repo.ol
index 1d1ea407..89f74e71 100644
--- a/packaging/repoconfig/netdata-edge.repo.ol
+++ b/packaging/repoconfig/netdata-edge.repo.ol
@@ -1,9 +1,9 @@
[netdata-edge]
name=Netdata Edge
-baseurl=https://packagecloud.io/netdata/netdata-edge/ol/$releasever/$basearch
+baseurl=https://repo.netdata.cloud/repos/edge/ol/$releasever/$basearch
repo_gpgcheck=1
-gpgcheck=0
-gpgkey=https://packagecloud.io/netdata/netdata-edge/gpgkey
+gpgcheck=1
+gpgkey=https://repo.netdata.cloud/netdatabot.gpg.key
enabled=1
sslverify=1
sslcacert=/etc/pki/tls/certs/ca-bundle.crt
@@ -11,10 +11,10 @@ priority=50
[netdata-repoconfig]
name=Netdata Repository Config
-baseurl=https://packagecloud.io/netdata/netdata-repoconfig/ol/$releasever/$basearch
+baseurl=https://repo.netdata.cloud/repos/repoconfig/ol/$releasever/$basearch
repo_gpgcheck=1
-gpgcheck=0
-gpgkey=https://packagecloud.io/netdata/netdata-repoconfig/gpgkey
+gpgcheck=1
+gpgkey=https://repo.netdata.cloud/netdatabot.gpg.key
enabled=1
sslverify=1
sslcacert=/etc/pki/tls/certs/ca-bundle.crt
diff --git a/packaging/repoconfig/netdata-edge.repo.suse b/packaging/repoconfig/netdata-edge.repo.suse
index 94db12a5..f65bd08d 100644
--- a/packaging/repoconfig/netdata-edge.repo.suse
+++ b/packaging/repoconfig/netdata-edge.repo.suse
@@ -1,19 +1,19 @@
[netdata-edge]
name=Netdata Edge
-baseurl=https://packagecloud.io/netdata/netdata-edge/opensuse/$releasever/$basearch
+baseurl=https://repo.netdata.cloud/repos/edge/opensuse/$releasever/$basearch
repo_gpgcheck=1
-pkg_gpgcheck=0
-gpgkey=https://packagecloud.io/netdata/netdata-edge/gpgkey
+pkg_gpgcheck=1
+gpgkey=https://repo.netdata.cloud/netdatabot.gpg.key
enabled=1
type=rpm-md
autorefresh=1
[netdata-repoconfig]
name=Netdata Repoconfig
-baseurl=https://packagecloud.io/netdata/netdata-repoconfig/opensuse/$releasever/$basearch
+baseurl=https://repo.netdata.cloud/repos/repoconfig/opensuse/$releasever/$basearch
repo_gpgcheck=1
-pkg_gpgcheck=0
-gpgkey=https://packagecloud.io/netdata/netdata-repoconfig/gpgkey
+pkg_gpgcheck=1
+gpgkey=https://repo.netdata.cloud/netdatabot.gpg.key
enabled=1
type=rpm-md
autorefresh=1
diff --git a/packaging/repoconfig/netdata-repo.spec b/packaging/repoconfig/netdata-repo.spec
index 3e5f66b8..cc53fd8c 100644
--- a/packaging/repoconfig/netdata-repo.spec
+++ b/packaging/repoconfig/netdata-repo.spec
@@ -1,8 +1,8 @@
%{?rhel:%global centos_ver %rhel}
Name: netdata-repo
-Version: 1
-Release: 2
+Version: 2
+Release: 1
Summary: Netdata stable repositories configuration.
Group: System Environment/Base
@@ -96,6 +96,8 @@ This package contains the official Netdata package repository configuration for
%endif
%changelog
+* Wed Dec 7 2022 Austin Hemmelgarn <austin@netdata.cloud> 2-1
+- Switch to new hosting at repo.netdata.cloud.
* Mon Jun 6 2022 Austin Hemmelgarn <austin@netdata.cloud> 1-2
- Bump release to keep in sync with DEB package.
* Mon Jun 14 2021 Austin Hemmelgarn <austin@netdata.cloud> 1-1
diff --git a/packaging/repoconfig/netdata.list.in b/packaging/repoconfig/netdata.list.in
index 9c3ddba0..a49dbd91 100644
--- a/packaging/repoconfig/netdata.list.in
+++ b/packaging/repoconfig/netdata.list.in
@@ -1,2 +1,2 @@
-deb https://packagecloud.io/netdata/netdata__VARIANT__/__DISTRO__/ __SUITE__ main
-deb https://packagecloud.io/netdata/netdata-repoconfig/__DISTRO__/ __SUITE__ main
+deb http://repo.netdata.cloud/repos/__VARIANT__/__DISTRO__/ __SUITE__/
+deb http://repo.netdata.cloud/repos/repoconfig/__DISTRO__/ __SUITE__/
diff --git a/packaging/repoconfig/netdata.repo.centos b/packaging/repoconfig/netdata.repo.centos
index d9cb2bca..221e6451 100644
--- a/packaging/repoconfig/netdata.repo.centos
+++ b/packaging/repoconfig/netdata.repo.centos
@@ -1,9 +1,9 @@
[netdata]
name=Netdata
-baseurl=https://packagecloud.io/netdata/netdata/el/$releasever/$basearch
+baseurl=https://repo.netdata.cloud/repos/stable/el/$releasever/$basearch
repo_gpgcheck=1
-gpgcheck=0
-gpgkey=https://packagecloud.io/netdata/netdata/gpgkey
+gpgcheck=1
+gpgkey=https://repo.netdata.cloud/netdatabot.gpg.key
enabled=1
sslverify=1
sslcacert=/etc/pki/tls/certs/ca-bundle.crt
@@ -11,10 +11,10 @@ priority=50
[netdata-repoconfig]
name=Netdata Repository Config
-baseurl=https://packagecloud.io/netdata/netdata-repoconfig/el/$releasever/$basearch
+baseurl=https://repo.netdata.cloud/repos/repoconfig/el/$releasever/$basearch
repo_gpgcheck=1
-gpgcheck=0
-gpgkey=https://packagecloud.io/netdata/netdata-repoconfig/gpgkey
+gpgcheck=1
+gpgkey=https://repo.netdata.cloud/netdatabot.gpg.key
enabled=1
sslverify=1
sslcacert=/etc/pki/tls/certs/ca-bundle.crt
diff --git a/packaging/repoconfig/netdata.repo.fedora b/packaging/repoconfig/netdata.repo.fedora
index 82ec6ea5..e13262ac 100644
--- a/packaging/repoconfig/netdata.repo.fedora
+++ b/packaging/repoconfig/netdata.repo.fedora
@@ -1,9 +1,9 @@
[netdata]
name=Netdata
-baseurl=https://packagecloud.io/netdata/netdata/fedora/$releasever/$basearch
+baseurl=https://repo.netdata.cloud/repos/stable/fedora/$releasever/$basearch
repo_gpgcheck=1
-gpgcheck=0
-gpgkey=https://packagecloud.io/netdata/netdata/gpgkey
+gpgcheck=1
+gpgkey=https://repo.netdata.cloud/netdatabot.gpg.key
enabled=1
sslverify=1
sslcacert=/etc/pki/tls/certs/ca-bundle.crt
@@ -11,10 +11,10 @@ priority=50
[netdata-repoconfig]
name=Netdata Repository Config
-baseurl=https://packagecloud.io/netdata/netdata-repoconfig/fedora/$releasever/$basearch
+baseurl=https://repo.netdata.cloud/repos/repoconfig/fedora/$releasever/$basearch
repo_gpgcheck=1
-gpgcheck=0
-gpgkey=https://packagecloud.io/netdata/netdata-repoconfig/gpgkey
+gpgcheck=1
+gpgkey=https://repo.netdata.cloud/netdatabot.gpg.key
enabled=1
sslverify=1
sslcacert=/etc/pki/tls/certs/ca-bundle.crt
diff --git a/packaging/repoconfig/netdata.repo.ol b/packaging/repoconfig/netdata.repo.ol
index 763504b2..0488670d 100644
--- a/packaging/repoconfig/netdata.repo.ol
+++ b/packaging/repoconfig/netdata.repo.ol
@@ -1,9 +1,9 @@
[netdata]
name=Netdata
-baseurl=https://packagecloud.io/netdata/netdata/ol/$releasever/$basearch
+baseurl=https://repo.netdata.cloud/repos/stable/ol/$releasever/$basearch
repo_gpgcheck=1
-gpgcheck=0
-gpgkey=https://packagecloud.io/netdata/netdata/gpgkey
+gpgcheck=1
+gpgkey=https://repo.netdata.cloud/netdatabot.gpg.key
enabled=1
sslverify=1
sslcacert=/etc/pki/tls/certs/ca-bundle.crt
@@ -11,10 +11,10 @@ priority=50
[netdata-repoconfig]
name=Netdata Repository Config
-baseurl=https://packagecloud.io/netdata/netdata-repoconfig/ol/$releasever/$basearch
+baseurl=https://repo.netdata.cloud/repos/repoconfig/ol/$releasever/$basearch
repo_gpgcheck=1
-gpgcheck=0
-gpgkey=https://packagecloud.io/netdata/netdata-repoconfig/gpgkey
+gpgcheck=1
+gpgkey=https://repo.netdata.cloud/netdatabot.gpg.key
enabled=1
sslverify=1
sslcacert=/etc/pki/tls/certs/ca-bundle.crt
diff --git a/packaging/repoconfig/netdata.repo.suse b/packaging/repoconfig/netdata.repo.suse
index 55ad73e3..8204d8d4 100644
--- a/packaging/repoconfig/netdata.repo.suse
+++ b/packaging/repoconfig/netdata.repo.suse
@@ -1,19 +1,19 @@
[netdata]
name=Netdata
-baseurl=https://packagecloud.io/netdata/netdata/opensuse/$releasever/$basearch
+baseurl=https://repo.netdata.cloud/repos/stable/opensuse/$releasever/$basearch
repo_gpgcheck=1
-pkg_gpgcheck=0
-gpgkey=https://packagecloud.io/netdata/netdata/gpgkey
+pkg_gpgcheck=1
+gpgkey=https://repo.netdata.cloud/netdatabot.gpg.key
enabled=1
type=rpm-md
autorefresh=1
[netdata-repoconfig]
name=Netdata Repoconfig
-baseurl=https://packagecloud.io/netdata/netdata-repoconfig/opensuse/$releasever/$basearch
+baseurl=https://repo.netdata.cloud/repos/repoconfig/opensuse/$releasever/$basearch
repo_gpgcheck=1
-pkg_gpgcheck=0
-gpgkey=https://packagecloud.io/netdata/netdata-repoconfig/gpgkey
+pkg_gpgcheck=1
+gpgkey=https://repo.netdata.cloud/netdatabot.gpg.key
enabled=1
type=rpm-md
autorefresh=1
diff --git a/packaging/version b/packaging/version
index 094f172c..553ed720 100644
--- a/packaging/version
+++ b/packaging/version
@@ -1 +1 @@
-v1.37.1
+v1.38.0
diff --git a/parser/README.md b/parser/README.md
index c01972df..b7951864 100644
--- a/parser/README.md
+++ b/parser/README.md
@@ -1,3 +1,9 @@
+<!--
+title: "Parser"
+custom_edit_url: https://github.com/netdata/netdata/blob/master/parser/README.md
+-->
+
+
#### Introduction
The parser will be used to process streaming and plugins input as well as metadata
@@ -18,7 +24,8 @@ Usage
#### Functions
-----
+TODO:
+
##### parse_init(RRDHOST *host, void *user, void *input, int flags)
Initialize an internal parser with the specified user defined data structure that will be shared across calls.
@@ -38,7 +45,6 @@ Output
-----
##### parse_push(PARSER *parser, char *line)
Push a new line for processing
@@ -58,7 +64,7 @@ Returns
- 0 line added
- 1 error detected
-----
+
##### parse_add_keyword(PARSER *parser, char *keyword, keyword_function callback_function)
The function will add callbacks for keywords. The callback function is defined as
@@ -74,9 +80,9 @@ Input
- keyword_function
- The callback that will handle the keyword processing
* The callback function should return one of the following
- * PARSER_RC_OK -- Callback was successful (continue with other callbacks)
- * PARSER_RC_STOP -- Stop processing callbacks (return OK)
- * PARSER_RC_ERROR -- Callback failed, exit
+ * PARSER_RC_OK - Callback was successful (continue with other callbacks)
+ * PARSER_RC_STOP - Stop processing callbacks (return OK)
+ * PARSER_RC_ERROR - Callback failed, exit
Output
- The corresponding keyword and callback will be registered
@@ -86,7 +92,6 @@ Returns
- > 0 which is the number of callbacks associated with this keyword.
-----
##### parser_next(PARSER *parser)
Return the next item to parse
@@ -101,7 +106,7 @@ Returns
- 0 Next item fetched successfully
- 1 No more items to parse
-----
+
##### parser_action(PARSER *parser, char *input)
Return the next item to parse
@@ -118,7 +123,7 @@ Returns
- 0 Callbacks called successfully
- 1 Failed
-----
+
##### parser_destroy(PARSER *parser)
Cleanup a previously allocated parser
@@ -132,7 +137,7 @@ Output
Returns
- none
-----
+
##### parser_recover_input(PARSER *parser)
Cleanup a previously allocated parser
diff --git a/parser/parser.c b/parser/parser.c
index 5b4c528d..c687c7af 100644
--- a/parser/parser.c
+++ b/parser/parser.c
@@ -357,7 +357,7 @@ inline int parser_action(PARSER *parser, char *input)
#ifdef NETDATA_INTERNAL_CHECKS
if(rc == PARSER_RC_ERROR) {
- BUFFER *wb = buffer_create(PLUGINSD_LINE_MAX);
+ BUFFER *wb = buffer_create(PLUGINSD_LINE_MAX, NULL);
for(size_t i = 0; i < num_words ;i++) {
if(i) buffer_fast_strcat(wb, " ", 1);
diff --git a/registry/README.md b/registry/README.md
index df618ffc..827eea13 100644
--- a/registry/README.md
+++ b/registry/README.md
@@ -1,7 +1,11 @@
<!--
title: "Registry"
description: "Netdata utilizes a central registry of machines/person GUIDs, URLs, and opt-in account information to provide unified cross-server dashboards."
-custom_edit_url: https://github.com/netdata/netdata/edit/master/registry/README.md
+custom_edit_url: "https://github.com/netdata/netdata/edit/master/registry/README.md"
+sidebar_label: "Registry"
+learn_status: "Published"
+learn_topic_type: "References"
+learn_rel_path: "References/Configuration"
-->
# Registry
@@ -67,8 +71,8 @@ in the Netdata registry regardless of whether you sign in or not.
## Who talks to the registry?
-Your web browser **only**! If sending this information is against your policies, you can [run your own
-registry](#run-your-own-registry)
+Your web browser **only**! If sending this information is against your policies, you
+can [run your own registry](#run-your-own-registry)
Your Netdata servers do not talk to the registry. This is a UML diagram of its operation:
@@ -133,7 +137,7 @@ Netdata v1.9+ support limiting access to the registry from given IPs, like this:
allow from = *
```
-`allow from` settings are [Netdata simple patterns](/libnetdata/simple_pattern/README.md): string matches that use `*`
+`allow from` settings are [Netdata simple patterns](https://github.com/netdata/netdata/blob/master/libnetdata/simple_pattern/README.md): string matches that use `*`
as wildcard (any number of times) and a `!` prefix for a negative match. So: `allow from = !10.1.2.3 10.*` will allow
all IPs in `10.*` except `10.1.2.3`. The order is important: left to right, the first positive or negative match is
used.
@@ -180,7 +184,7 @@ Both files are machine readable text files.
Beginning with `v1.30.0`, when the Netdata Agent's web server processes a request, it delivers the `SameSite=none`
and `Secure` cookies. If you have problems accessing the local Agent dashboard or Netdata Cloud, disable these
-cookies by [editing `netdata.conf`](/docs/configure/nodes.md#use-edit-config-to-edit-configuration-files):
+cookies by [editing `netdata.conf`](https://github.com/netdata/netdata/blob/master/docs/configure/nodes.md#use-edit-config-to-edit-configuration-files):
```conf
[registry]
diff --git a/spawn/spawn_server.c b/spawn/spawn_server.c
index 53b143de..1d79ef15 100644
--- a/spawn/spawn_server.c
+++ b/spawn/spawn_server.c
@@ -317,10 +317,7 @@ void spawn_server(void)
// close all open file descriptors, except the standard ones
// the caller may have left open files (lxc-attach has this issue)
- int fd;
- for(fd = (int)(sysconf(_SC_OPEN_MAX) - 1) ; fd > 2 ; --fd)
- if(fd_is_valid(fd))
- close(fd);
+ for_each_open_fd(OPEN_FD_ACTION_CLOSE, OPEN_FD_EXCLUDE_STDIN | OPEN_FD_EXCLUDE_STDOUT | OPEN_FD_EXCLUDE_STDERR);
// Have the libuv IPC pipe be closed when forking child processes
(void) fcntl(0, F_SETFD, FD_CLOEXEC);
diff --git a/streaming/README.md b/streaming/README.md
index 58eb2cc1..37d2c261 100644
--- a/streaming/README.md
+++ b/streaming/README.md
@@ -7,7 +7,7 @@ custom_edit_url: https://github.com/netdata/netdata/edit/master/streaming/README
Each Netdata node is able to replicate/mirror its database to another Netdata node, by streaming the collected
metrics in real-time. This is quite different to [data archiving to third party time-series
-databases](/exporting/README.md).
+databases](https://github.com/netdata/netdata/blob/master/exporting/README.md).
The nodes that send metrics are called **child** nodes, and the nodes that receive metrics are called **parent** nodes.
There are also **proxy** nodes, which collect metrics from a child and sends it to a parent.
@@ -38,7 +38,7 @@ In a headless setup, the child acts as a plain data collector. It spawns all ext
local database and accepting dashboard requests, it streams all metrics to the parent.
This setup works great to reduce the memory footprint. Depending on the enabled plugins, memory usage is between 6 MiB and 40 MiB. To reduce the memory usage as much as
-possible, refer to the [performance optimization guide](/docs/guides/configure/performance.md).
+possible, refer to the [performance optimization guide](https://github.com/netdata/netdata/blob/master/docs/guides/configure/performance.md).
### Database Replication
@@ -107,7 +107,7 @@ This also disables the registry (there cannot be a registry without an API).
requests from its child nodes. 0 sets no limit, 1 means maximum once every second. If this is set, you may see error log
entries "... too busy to accept new streaming request. Will be allowed in X secs".
-You can [use](/exporting/README.md#configuration) the exporting engine to configure data archiving to an external database (it archives all databases maintained on
+You can [use](https://github.com/netdata/netdata/blob/master/exporting/README.md#configuration) the exporting engine to configure data archiving to an external database (it archives all databases maintained on
this host).
### Streaming configuration
@@ -198,7 +198,7 @@ You can also use `default memory mode = dbengine` for an API key or `memory mode
##### Allow from
-`allow from` settings are [Netdata simple patterns](/libnetdata/simple_pattern/README.md): string matches
+`allow from` settings are [Netdata simple patterns](https://github.com/netdata/netdata/blob/master/libnetdata/simple_pattern/README.md): string matches
that use `*` as wildcard (any number of times) and a `!` prefix for a negative match.
So: `allow from = !10.1.2.3 10.*` will allow all IPs in `10.*` except `10.1.2.3`. The order is
important: left to right, the first positive or negative match is used.
@@ -233,7 +233,7 @@ For Netdata v1.9+, streaming can also be monitored via `access.log`.
### Securing streaming communications
Netdata does not activate TLS encryption by default. To encrypt streaming connections:
-1. On the parent node (receiving node), [enable TLS support](/web/server/README.md#enabling-tls-support).
+1. On the parent node (receiving node), [enable TLS support](https://github.com/netdata/netdata/blob/master/web/server/README.md#enabling-tls-support).
2. On the child's `stream.conf`, configure the destination as follows:
```
@@ -602,7 +602,7 @@ this writing, Netdata supports:
- json document DBs
- all the compatibles to the above (e.g. kairosdb, influxdb, etc)
-Check the Netdata [exporting documentation](/docs/export/external-databases.md) for configuring this.
+Check the Netdata [exporting documentation](https://github.com/netdata/netdata/blob/master/docs/export/external-databases.md) for configuring this.
This is how such a solution will work:
@@ -696,7 +696,7 @@ ERROR : STREAM_SENDER[CHILD HOSTNAME] : STREAM child HOSTNAME [send to PARENT HO
Chart data needs to be consistent between child and parent nodes. If there are differences between chart data on
a parent and a child, such as gaps in metrics collection, it most often means your child's `memory mode`
does not match the parent's. To learn more about the different ways Netdata can store metrics, and thus keep chart
-data consistent, read our [memory mode documentation](/database/README.md).
+data consistent, read our [memory mode documentation](https://github.com/netdata/netdata/blob/master/database/README.md).
### Forbidding access
diff --git a/streaming/compression.c b/streaming/compression.c
index 7ba9dbf1..8f2517a8 100644
--- a/streaming/compression.c
+++ b/streaming/compression.c
@@ -244,7 +244,7 @@ static size_t lz4_decompressor_decompress(struct decompressor_state *state, cons
, state->stream->size
, state->stream->write_at
, decompressed_size
- , state->stream->write_at + decompressed_size - state->stream->size
+ , (size_t)(state->stream->write_at + decompressed_size - state->stream->size)
);
state->stream->write_at += decompressed_size;
diff --git a/streaming/receiver.c b/streaming/receiver.c
index 61ee33bc..95652942 100644
--- a/streaming/receiver.c
+++ b/streaming/receiver.c
@@ -16,7 +16,8 @@
extern struct config stream_config;
-void destroy_receiver_state(struct receiver_state *rpt) {
+void receiver_state_free(struct receiver_state *rpt) {
+
freez(rpt->key);
freez(rpt->hostname);
freez(rpt->registry_hostname);
@@ -29,43 +30,23 @@ void destroy_receiver_state(struct receiver_state *rpt) {
freez(rpt->client_port);
freez(rpt->program_name);
freez(rpt->program_version);
+
#ifdef ENABLE_HTTPS
- if(rpt->ssl.conn){
+ if(rpt->ssl.conn)
SSL_free(rpt->ssl.conn);
- }
#endif
+
#ifdef ENABLE_COMPRESSION
if (rpt->decompressor)
rpt->decompressor->destroy(&rpt->decompressor);
#endif
- freez(rpt);
-}
-
-static void rrdpush_receiver_thread_cleanup(void *ptr) {
- worker_unregister();
- static __thread int executed = 0;
- if(!executed) {
- executed = 1;
- struct receiver_state *rpt = (struct receiver_state *) ptr;
- // If the shutdown sequence has started, and this receiver is still attached to the host then we cannot touch
- // the host pointer as it is unpredictable when the RRDHOST is deleted. Do the cleanup from rrdhost_free().
- if (netdata_exit && rpt->host) {
- rpt->exited = 1;
- return;
- }
+ if(rpt->system_info)
+ rrdhost_system_info_free(rpt->system_info);
- // Make sure that we detach this thread and don't kill a freshly arriving receiver
- if (!netdata_exit && rpt->host) {
- netdata_mutex_lock(&rpt->host->receiver_lock);
- if (rpt->host->receiver == rpt)
- rpt->host->receiver = NULL;
- netdata_mutex_unlock(&rpt->host->receiver_lock);
- }
+ __atomic_sub_fetch(&netdata_buffers_statistics.rrdhost_receivers, sizeof(*rpt), __ATOMIC_RELAXED);
- info("STREAM %s [receive from [%s]:%s]: receive thread ended (task id %d)", rpt->hostname, rpt->client_ip, rpt->client_port, gettid());
- destroy_receiver_state(rpt);
- }
+ freez(rpt);
}
#include "collectors/plugins.d/pluginsd_parser.h"
@@ -105,11 +86,10 @@ PARSER_RC streaming_claimed_id(char **words, size_t num_words, void *user)
if (host->aclk_state.claimed_id)
freez(host->aclk_state.claimed_id);
host->aclk_state.claimed_id = strcmp(claim_id_str, "NULL") ? strdupz(claim_id_str) : NULL;
-
- metaqueue_store_claim_id(&host->host_uuid, host->aclk_state.claimed_id ? &uuid : NULL);
-
rrdhost_aclk_state_unlock(host);
+ rrdhost_flag_set(host, RRDHOST_FLAG_METADATA_CLAIMID |RRDHOST_FLAG_METADATA_UPDATE);
+
rrdpush_claimed_id(host);
return PARSER_RC_OK;
@@ -350,11 +330,13 @@ static void streaming_parser_thread_cleanup(void *ptr) {
parser_destroy(parser);
}
+bool plugin_is_enabled(struct plugind *cd);
+
static size_t streaming_parser(struct receiver_state *rpt, struct plugind *cd, int fd, void *ssl) {
size_t result;
PARSER_USER_OBJECT user = {
- .enabled = cd->enabled,
+ .enabled = plugin_is_enabled(cd),
.host = rpt->host,
.opaque = rpt,
.cd = cd,
@@ -390,39 +372,50 @@ static size_t streaming_parser(struct receiver_state *rpt, struct plugind *cd, i
size_t read_buffer_start = 0;
char buffer[PLUGINSD_LINE_MAX + 2] = "";
- while(!netdata_exit) {
+ while(service_running(SERVICE_STREAMING)) {
+ netdata_thread_testcancel();
+
if(!receiver_next_line(rpt, buffer, PLUGINSD_LINE_MAX + 2, &read_buffer_start)) {
bool have_new_data;
- if(compressed_connection)
+ if(likely(compressed_connection))
have_new_data = receiver_read_compressed(rpt);
else
have_new_data = receiver_read_uncompressed(rpt);
- if(!have_new_data)
+ if(unlikely(!have_new_data)) {
+ if(!rpt->exit.reason)
+ rpt->exit.reason = "SOCKET READ ERROR";
+
break;
+ }
rpt->last_msg_t = now_realtime_sec();
continue;
}
- if(unlikely(netdata_exit)) {
- internal_error(true, "exiting...");
+ if(unlikely(!service_running(SERVICE_STREAMING))) {
+ if(!rpt->exit.reason)
+ rpt->exit.reason = "NETDATA EXIT";
goto done;
}
- if(unlikely(rpt->shutdown)) {
- internal_error(true, "parser shutdown...");
+ if(unlikely(rpt->exit.shutdown)) {
+ if(!rpt->exit.reason)
+ rpt->exit.reason = "SHUTDOWN REQUESTED";
+
goto done;
}
if (unlikely(parser_action(parser, buffer))) {
internal_error(true, "parser_action() failed on keyword '%s'.", buffer);
+
+ if(!rpt->exit.reason)
+ rpt->exit.reason = "PARSER FAILED";
+
break;
}
}
done:
- internal_error(true, "Streaming receiver thread stopping...");
-
result = user.count;
// free parser with the pop function
@@ -431,103 +424,240 @@ done:
return result;
}
-static void rrdpush_receiver_replication_reset(struct receiver_state *rpt) {
+static void rrdpush_receiver_replication_reset(RRDHOST *host) {
RRDSET *st;
- rrdset_foreach_read(st, rpt->host) {
+ rrdset_foreach_read(st, host) {
rrdset_flag_clear(st, RRDSET_FLAG_RECEIVER_REPLICATION_IN_PROGRESS);
rrdset_flag_set(st, RRDSET_FLAG_RECEIVER_REPLICATION_FINISHED);
}
rrdset_foreach_done(st);
- rrdhost_receiver_replicating_charts_zero(rpt->host);
+ rrdhost_receiver_replicating_charts_zero(host);
+}
+
+bool rrdhost_set_receiver(RRDHOST *host, struct receiver_state *rpt) {
+ bool signal_rrdcontext = false;
+ bool set_this = false;
+
+ netdata_mutex_lock(&host->receiver_lock);
+
+ if (!host->receiver || host->receiver == rpt) {
+ rrdhost_flag_clear(host, RRDHOST_FLAG_ORPHAN);
+
+ host->receiver = rpt;
+ rpt->host = host;
+
+ host->child_connect_time = now_realtime_sec();
+ host->child_disconnected_time = 0;
+ host->child_last_chart_command = 0;
+ host->trigger_chart_obsoletion_check = 1;
+
+ if (rpt->config.health_enabled != CONFIG_BOOLEAN_NO) {
+ if (rpt->config.alarms_delay > 0) {
+ host->health.health_delay_up_to = now_realtime_sec() + rpt->config.alarms_delay;
+ log_health(
+ "[%s]: Postponing health checks for %" PRId64 " seconds, because it was just connected.",
+ rrdhost_hostname(host),
+ (int64_t) rpt->config.alarms_delay);
+ }
+ }
+
+// this is a test
+// if(rpt->hops <= host->sender->hops)
+// rrdpush_sender_thread_stop(host, "HOPS MISMATCH", false);
+
+ signal_rrdcontext = true;
+ rrdpush_receiver_replication_reset(host);
+
+ rrdhost_flag_clear(rpt->host, RRDHOST_FLAG_RRDPUSH_RECEIVER_DISCONNECTED);
+
+ set_this = true;
+ }
+
+ netdata_mutex_unlock(&host->receiver_lock);
+
+ if(signal_rrdcontext)
+ rrdcontext_host_child_connected(host);
+
+ return set_this;
+}
+
+static void rrdhost_clear_receiver(struct receiver_state *rpt) {
+ bool signal_rrdcontext = false;
+
+ RRDHOST *host = rpt->host;
+ if(host) {
+ netdata_mutex_lock(&host->receiver_lock);
+
+ // Make sure that we detach this thread and don't kill a freshly arriving receiver
+ if(host->receiver == rpt) {
+ host->trigger_chart_obsoletion_check = 0;
+ host->child_connect_time = 0;
+ host->child_disconnected_time = now_realtime_sec();
+
+ if (rpt->config.health_enabled == CONFIG_BOOLEAN_AUTO)
+ host->health.health_enabled = 0;
+
+ rrdpush_sender_thread_stop(host, "RECEIVER LEFT", false);
+
+ signal_rrdcontext = true;
+ rrdpush_receiver_replication_reset(host);
+
+ if (host->receiver == rpt)
+ host->receiver = NULL;
+
+ rrdhost_flag_set(host, RRDHOST_FLAG_ORPHAN);
+ }
+
+ netdata_mutex_unlock(&host->receiver_lock);
+
+ if(signal_rrdcontext)
+ rrdcontext_host_child_disconnected(host);
+ }
+}
+
+bool stop_streaming_receiver(RRDHOST *host, const char *reason) {
+ bool ret = false;
+
+ netdata_mutex_lock(&host->receiver_lock);
+
+ if(host->receiver) {
+ if(!host->receiver->exit.shutdown) {
+ host->receiver->exit.shutdown = true;
+ host->receiver->exit.reason = reason;
+ shutdown(host->receiver->fd, SHUT_RDWR);
+ }
+
+ netdata_thread_cancel(host->receiver->thread);
+ }
+
+ int count = 2000;
+ while (host->receiver && count-- > 0) {
+ netdata_mutex_unlock(&host->receiver_lock);
+
+ // let the lock for the receiver thread to exit
+ sleep_usec(1 * USEC_PER_MS);
+
+ netdata_mutex_lock(&host->receiver_lock);
+ }
+
+ if(host->receiver)
+ error("STREAM '%s' [receive from [%s]:%s]: "
+ "thread %d takes too long to stop, giving up..."
+ , rrdhost_hostname(host)
+ , host->receiver->client_ip, host->receiver->client_port
+ , gettid());
+ else
+ ret = true;
+
+ netdata_mutex_unlock(&host->receiver_lock);
+
+ return ret;
+}
+
+void rrdpush_receive_log_status(struct receiver_state *rpt, const char *msg, const char *status) {
+
+ log_stream_connection(rpt->client_ip, rpt->client_port,
+ (rpt->key && *rpt->key)? rpt->key : "-",
+ (rpt->machine_guid && *rpt->machine_guid) ? rpt->machine_guid : "-",
+ (rpt->hostname && *rpt->hostname) ? rpt->hostname : "-",
+ status);
+
+ info("STREAM '%s' [receive from [%s]:%s]: "
+ "%s. "
+ "STATUS: %s%s%s%s"
+ , rpt->hostname
+ , rpt->client_ip, rpt->client_port
+ , msg
+ , status
+ , rpt->exit.reason?" (":""
+ , rpt->exit.reason?rpt->exit.reason:""
+ , rpt->exit.reason?")":""
+ );
+
+}
+
+static void rrdhost_reset_destinations(RRDHOST *host) {
+ for (struct rrdpush_destinations *d = host->destinations; d; d = d->next)
+ d->postpone_reconnection_until = 0;
}
static int rrdpush_receive(struct receiver_state *rpt)
{
- int history = default_rrd_history_entries;
- RRD_MEMORY_MODE mode = default_rrd_memory_mode;
- int health_enabled = default_health_enabled;
- int rrdpush_enabled = default_rrdpush_enabled;
- char *rrdpush_destination = default_rrdpush_destination;
- char *rrdpush_api_key = default_rrdpush_api_key;
- char *rrdpush_send_charts_matching = default_rrdpush_send_charts_matching;
- bool rrdpush_enable_replication = default_rrdpush_enable_replication;
- time_t rrdpush_seconds_to_replicate = default_rrdpush_seconds_to_replicate;
- time_t rrdpush_replication_step = default_rrdpush_replication_step;
- time_t alarms_delay = 60;
-
- rpt->update_every = (int)appconfig_get_number(&stream_config, rpt->machine_guid, "update every", rpt->update_every);
- if(rpt->update_every < 0) rpt->update_every = 1;
-
- history = (int)appconfig_get_number(&stream_config, rpt->key, "default history", history);
- history = (int)appconfig_get_number(&stream_config, rpt->machine_guid, "history", history);
- if(history < 5) history = 5;
-
- mode = rrd_memory_mode_id(appconfig_get(&stream_config, rpt->key, "default memory mode", rrd_memory_mode_name(mode)));
- mode = rrd_memory_mode_id(appconfig_get(&stream_config, rpt->machine_guid, "memory mode", rrd_memory_mode_name(mode)));
-
- if (unlikely(mode == RRD_MEMORY_MODE_DBENGINE && !dbengine_enabled)) {
- error("STREAM %s [receive from %s:%s]: dbengine is not enabled, falling back to default.", rpt->hostname, rpt->client_ip, rpt->client_port);
- mode = default_rrd_memory_mode;
+ rpt->config.mode = default_rrd_memory_mode;
+ rpt->config.history = default_rrd_history_entries;
+
+ rpt->config.health_enabled = (int)default_health_enabled;
+ rpt->config.alarms_delay = 60;
+
+ rpt->config.rrdpush_enabled = (int)default_rrdpush_enabled;
+ rpt->config.rrdpush_destination = default_rrdpush_destination;
+ rpt->config.rrdpush_api_key = default_rrdpush_api_key;
+ rpt->config.rrdpush_send_charts_matching = default_rrdpush_send_charts_matching;
+
+ rpt->config.rrdpush_enable_replication = default_rrdpush_enable_replication;
+ rpt->config.rrdpush_seconds_to_replicate = default_rrdpush_seconds_to_replicate;
+ rpt->config.rrdpush_replication_step = default_rrdpush_replication_step;
+
+ rpt->config.update_every = (int)appconfig_get_number(&stream_config, rpt->machine_guid, "update every", rpt->config.update_every);
+ if(rpt->config.update_every < 0) rpt->config.update_every = 1;
+
+ rpt->config.history = (int)appconfig_get_number(&stream_config, rpt->key, "default history", rpt->config.history);
+ rpt->config.history = (int)appconfig_get_number(&stream_config, rpt->machine_guid, "history", rpt->config.history);
+ if(rpt->config.history < 5) rpt->config.history = 5;
+
+ rpt->config.mode = rrd_memory_mode_id(appconfig_get(&stream_config, rpt->key, "default memory mode", rrd_memory_mode_name(rpt->config.mode)));
+ rpt->config.mode = rrd_memory_mode_id(appconfig_get(&stream_config, rpt->machine_guid, "memory mode", rrd_memory_mode_name(rpt->config.mode)));
+
+ if (unlikely(rpt->config.mode == RRD_MEMORY_MODE_DBENGINE && !dbengine_enabled)) {
+ error("STREAM '%s' [receive from %s:%s]: "
+ "dbengine is not enabled, falling back to default."
+ , rpt->hostname
+ , rpt->client_ip, rpt->client_port
+ );
+
+ rpt->config.mode = default_rrd_memory_mode;
}
- health_enabled = appconfig_get_boolean_ondemand(&stream_config, rpt->key, "health enabled by default", health_enabled);
- health_enabled = appconfig_get_boolean_ondemand(&stream_config, rpt->machine_guid, "health enabled", health_enabled);
+ rpt->config.health_enabled = appconfig_get_boolean_ondemand(&stream_config, rpt->key, "health enabled by default", rpt->config.health_enabled);
+ rpt->config.health_enabled = appconfig_get_boolean_ondemand(&stream_config, rpt->machine_guid, "health enabled", rpt->config.health_enabled);
- alarms_delay = appconfig_get_number(&stream_config, rpt->key, "default postpone alarms on connect seconds", alarms_delay);
- alarms_delay = appconfig_get_number(&stream_config, rpt->machine_guid, "postpone alarms on connect seconds", alarms_delay);
+ rpt->config.alarms_delay = appconfig_get_number(&stream_config, rpt->key, "default postpone alarms on connect seconds", rpt->config.alarms_delay);
+ rpt->config.alarms_delay = appconfig_get_number(&stream_config, rpt->machine_guid, "postpone alarms on connect seconds", rpt->config.alarms_delay);
- rrdpush_enabled = appconfig_get_boolean(&stream_config, rpt->key, "default proxy enabled", rrdpush_enabled);
- rrdpush_enabled = appconfig_get_boolean(&stream_config, rpt->machine_guid, "proxy enabled", rrdpush_enabled);
+ rpt->config.rrdpush_enabled = appconfig_get_boolean(&stream_config, rpt->key, "default proxy enabled", rpt->config.rrdpush_enabled);
+ rpt->config.rrdpush_enabled = appconfig_get_boolean(&stream_config, rpt->machine_guid, "proxy enabled", rpt->config.rrdpush_enabled);
- rrdpush_destination = appconfig_get(&stream_config, rpt->key, "default proxy destination", rrdpush_destination);
- rrdpush_destination = appconfig_get(&stream_config, rpt->machine_guid, "proxy destination", rrdpush_destination);
+ rpt->config.rrdpush_destination = appconfig_get(&stream_config, rpt->key, "default proxy destination", rpt->config.rrdpush_destination);
+ rpt->config.rrdpush_destination = appconfig_get(&stream_config, rpt->machine_guid, "proxy destination", rpt->config.rrdpush_destination);
- rrdpush_api_key = appconfig_get(&stream_config, rpt->key, "default proxy api key", rrdpush_api_key);
- rrdpush_api_key = appconfig_get(&stream_config, rpt->machine_guid, "proxy api key", rrdpush_api_key);
+ rpt->config.rrdpush_api_key = appconfig_get(&stream_config, rpt->key, "default proxy api key", rpt->config.rrdpush_api_key);
+ rpt->config.rrdpush_api_key = appconfig_get(&stream_config, rpt->machine_guid, "proxy api key", rpt->config.rrdpush_api_key);
- rrdpush_send_charts_matching = appconfig_get(&stream_config, rpt->key, "default proxy send charts matching", rrdpush_send_charts_matching);
- rrdpush_send_charts_matching = appconfig_get(&stream_config, rpt->machine_guid, "proxy send charts matching", rrdpush_send_charts_matching);
+ rpt->config.rrdpush_send_charts_matching = appconfig_get(&stream_config, rpt->key, "default proxy send charts matching", rpt->config.rrdpush_send_charts_matching);
+ rpt->config.rrdpush_send_charts_matching = appconfig_get(&stream_config, rpt->machine_guid, "proxy send charts matching", rpt->config.rrdpush_send_charts_matching);
- rrdpush_enable_replication = appconfig_get_boolean(&stream_config, rpt->key, "enable replication", rrdpush_enable_replication);
- rrdpush_enable_replication = appconfig_get_boolean(&stream_config, rpt->machine_guid, "enable replication", rrdpush_enable_replication);
+ rpt->config.rrdpush_enable_replication = appconfig_get_boolean(&stream_config, rpt->key, "enable replication", rpt->config.rrdpush_enable_replication);
+ rpt->config.rrdpush_enable_replication = appconfig_get_boolean(&stream_config, rpt->machine_guid, "enable replication", rpt->config.rrdpush_enable_replication);
- rrdpush_seconds_to_replicate = appconfig_get_number(&stream_config, rpt->key, "seconds to replicate", rrdpush_seconds_to_replicate);
- rrdpush_seconds_to_replicate = appconfig_get_number(&stream_config, rpt->machine_guid, "seconds to replicate", rrdpush_seconds_to_replicate);
+ rpt->config.rrdpush_seconds_to_replicate = appconfig_get_number(&stream_config, rpt->key, "seconds to replicate", rpt->config.rrdpush_seconds_to_replicate);
+ rpt->config.rrdpush_seconds_to_replicate = appconfig_get_number(&stream_config, rpt->machine_guid, "seconds to replicate", rpt->config.rrdpush_seconds_to_replicate);
- rrdpush_replication_step = appconfig_get_number(&stream_config, rpt->key, "seconds per replication step", rrdpush_replication_step);
- rrdpush_replication_step = appconfig_get_number(&stream_config, rpt->machine_guid, "seconds per replication step", rrdpush_replication_step);
+ rpt->config.rrdpush_replication_step = appconfig_get_number(&stream_config, rpt->key, "seconds per replication step", rpt->config.rrdpush_replication_step);
+ rpt->config.rrdpush_replication_step = appconfig_get_number(&stream_config, rpt->machine_guid, "seconds per replication step", rpt->config.rrdpush_replication_step);
#ifdef ENABLE_COMPRESSION
- unsigned int rrdpush_compression = default_compression_enabled;
- rrdpush_compression = appconfig_get_boolean(&stream_config, rpt->key, "enable compression", rrdpush_compression);
- rrdpush_compression = appconfig_get_boolean(&stream_config, rpt->machine_guid, "enable compression", rrdpush_compression);
- rpt->rrdpush_compression = (rrdpush_compression && default_compression_enabled);
+ rpt->config.rrdpush_compression = default_compression_enabled;
+ rpt->config.rrdpush_compression = appconfig_get_boolean(&stream_config, rpt->key, "enable compression", rpt->config.rrdpush_compression);
+ rpt->config.rrdpush_compression = appconfig_get_boolean(&stream_config, rpt->machine_guid, "enable compression", rpt->config.rrdpush_compression);
+ rpt->rrdpush_compression = (rpt->config.rrdpush_compression && default_compression_enabled);
#endif //ENABLE_COMPRESSION
(void)appconfig_set_default(&stream_config, rpt->machine_guid, "host tags", (rpt->tags)?rpt->tags:"");
- if (strcmp(rpt->machine_guid, localhost->machine_guid) == 0) {
- log_stream_connection(rpt->client_ip, rpt->client_port, rpt->key, rpt->machine_guid, rpt->hostname, "DENIED - ATTEMPT TO RECEIVE METRICS FROM MACHINE_GUID IDENTICAL TO PARENT");
- error("STREAM %s [receive from %s:%s]: denied to receive metrics, machine GUID [%s] is my own. Did you copy the parent/proxy machine GUID to a child, or is this an inter-agent loop?", rpt->hostname, rpt->client_ip, rpt->client_port, rpt->machine_guid);
- char initial_response[HTTP_HEADER_SIZE + 1];
- snprintfz(initial_response, HTTP_HEADER_SIZE, "%s", START_STREAMING_ERROR_SAME_LOCALHOST);
-#ifdef ENABLE_HTTPS
- if(send_timeout(&rpt->ssl, rpt->fd, initial_response, strlen(initial_response), 0, 60) != (ssize_t)strlen(initial_response)) {
-#else
- if(send_timeout(rpt->fd, initial_response, strlen(initial_response), 0, 60) != strlen(initial_response)) {
-#endif
- log_stream_connection(rpt->client_ip, rpt->client_port, rpt->key, rpt->host->machine_guid, rrdhost_hostname(rpt->host), "FAILED - CANNOT REPLY");
- error("STREAM %s [receive from [%s]:%s]: cannot send command.", rrdhost_hostname(rpt->host), rpt->client_ip, rpt->client_port);
- close(rpt->fd);
- return 0;
- }
- close(rpt->fd);
- return 0;
- }
-
- if (rpt->host==NULL) {
-
- rpt->host = rrdhost_find_or_create(
+ // find the host for this receiver
+ {
+ // this will also update the host with our system_info
+ RRDHOST *host = rrdhost_find_or_create(
rpt->hostname
, rpt->registry_hostname
, rpt->machine_guid
@@ -538,76 +668,41 @@ static int rrdpush_receive(struct receiver_state *rpt)
, rpt->tags
, rpt->program_name
, rpt->program_version
- , rpt->update_every
- , history
- , mode
- , (unsigned int)(health_enabled != CONFIG_BOOLEAN_NO)
- , (unsigned int)(rrdpush_enabled && rrdpush_destination && *rrdpush_destination && rrdpush_api_key && *rrdpush_api_key)
- , rrdpush_destination
- , rrdpush_api_key
- , rrdpush_send_charts_matching
- , rrdpush_enable_replication
- , rrdpush_seconds_to_replicate
- , rrdpush_replication_step
+ , rpt->config.update_every
+ , rpt->config.history
+ , rpt->config.mode
+ , (unsigned int)(rpt->config.health_enabled != CONFIG_BOOLEAN_NO)
+ , (unsigned int)(rpt->config.rrdpush_enabled && rpt->config.rrdpush_destination && *rpt->config.rrdpush_destination && rpt->config.rrdpush_api_key && *rpt->config.rrdpush_api_key)
+ , rpt->config.rrdpush_destination
+ , rpt->config.rrdpush_api_key
+ , rpt->config.rrdpush_send_charts_matching
+ , rpt->config.rrdpush_enable_replication
+ , rpt->config.rrdpush_seconds_to_replicate
+ , rpt->config.rrdpush_replication_step
, rpt->system_info
, 0
);
- if(!rpt->host) {
+ if(!host) {
+ rrdpush_receive_log_status(rpt, "failed to find/create host structure", "INTERNAL ERROR DROPPING CONNECTION");
close(rpt->fd);
- log_stream_connection(rpt->client_ip, rpt->client_port, rpt->key, rpt->machine_guid, rpt->hostname, "FAILED - CANNOT ACQUIRE HOST");
- error("STREAM %s [receive from [%s]:%s]: failed to find/create host structure.", rpt->hostname, rpt->client_ip, rpt->client_port);
return 1;
}
- netdata_mutex_lock(&rpt->host->receiver_lock);
- if (rpt->host->receiver == NULL)
- rpt->host->receiver = rpt;
- else {
- error("Multiple receivers connected for %s concurrently, cancelling this one...", rpt->machine_guid);
- netdata_mutex_unlock(&rpt->host->receiver_lock);
+ // system_info has been consumed by the host structure
+ rpt->system_info = NULL;
+
+ if(!rrdhost_set_receiver(host, rpt)) {
+ rrdpush_receive_log_status(rpt, "host is already served by another receiver", "DUPLICATE RECEIVER DROPPING CONNECTION");
close(rpt->fd);
- log_stream_connection(rpt->client_ip, rpt->client_port, rpt->key, rpt->machine_guid, rpt->hostname, "FAILED - BEATEN TO HOST CREATION");
return 1;
}
- netdata_mutex_unlock(&rpt->host->receiver_lock);
- }
- else {
- rrd_wrlock();
- rrdhost_update(
- rpt->host,
- rpt->hostname,
- rpt->registry_hostname,
- rpt->machine_guid,
- rpt->os,
- rpt->timezone,
- rpt->abbrev_timezone,
- rpt->utc_offset,
- rpt->tags,
- rpt->program_name,
- rpt->program_version,
- rpt->update_every,
- history,
- mode,
- (unsigned int)(health_enabled != CONFIG_BOOLEAN_NO),
- (unsigned int)(rrdpush_enabled && rrdpush_destination && *rrdpush_destination && rrdpush_api_key && *rrdpush_api_key),
- rrdpush_destination,
- rrdpush_api_key,
- rrdpush_send_charts_matching,
- rrdpush_enable_replication,
- rrdpush_seconds_to_replicate,
- rrdpush_replication_step,
- rpt->system_info);
- rrd_unlock();
}
#ifdef NETDATA_INTERNAL_CHECKS
- int ssl = 0;
-#ifdef ENABLE_HTTPS
- if (rpt->ssl.conn != NULL)
- ssl = 1;
-#endif
- info("STREAM %s [receive from [%s]:%s]: client willing to stream metrics for host '%s' with machine_guid '%s': update every = %d, history = %ld, memory mode = %s, health %s,%s tags '%s'"
+ info("STREAM '%s' [receive from [%s]:%s]: "
+ "client willing to stream metrics for host '%s' with machine_guid '%s': "
+ "update every = %d, history = %ld, memory mode = %s, health %s,%s tags '%s'"
, rpt->hostname
, rpt->client_ip
, rpt->client_port
@@ -616,20 +711,26 @@ static int rrdpush_receive(struct receiver_state *rpt)
, rpt->host->rrd_update_every
, rpt->host->rrd_history_entries
, rrd_memory_mode_name(rpt->host->rrd_memory_mode)
- , (health_enabled == CONFIG_BOOLEAN_NO)?"disabled":((health_enabled == CONFIG_BOOLEAN_YES)?"enabled":"auto")
- , ssl ? " SSL," : ""
+ , (rpt->config.health_enabled == CONFIG_BOOLEAN_NO)?"disabled":((rpt->config.health_enabled == CONFIG_BOOLEAN_YES)?"enabled":"auto")
+#ifdef ENABLE_HTTPS
+ , (rpt->ssl.conn != NULL) ? " SSL," : ""
+#else
+ , ""
+#endif
, rrdhost_tags(rpt->host)
);
#endif // NETDATA_INTERNAL_CHECKS
struct plugind cd = {
- .enabled = 1,
.update_every = default_rrd_update_every,
- .pid = 0,
.serial_failures = 0,
.successful_collections = 0,
- .obsolete = 0,
+ .unsafe = {
+ .spinlock = NETDATA_SPINLOCK_INITIALIZER,
+ .running = true,
+ .enabled = true,
+ },
.started_t = now_realtime_sec(),
.next = NULL,
.capabilities = 0,
@@ -648,76 +749,60 @@ static int rrdpush_receive(struct receiver_state *rpt)
}
#endif
- info("STREAM %s [receive from [%s]:%s]: initializing communication...", rrdhost_hostname(rpt->host), rpt->client_ip, rpt->client_port);
- char initial_response[HTTP_HEADER_SIZE];
- if (stream_has_capability(rpt, STREAM_CAP_VCAPS)) {
- log_receiver_capabilities(rpt);
- sprintf(initial_response, "%s%u", START_STREAMING_PROMPT_VN, rpt->capabilities);
- }
- else if (stream_has_capability(rpt, STREAM_CAP_VN)) {
- log_receiver_capabilities(rpt);
- sprintf(initial_response, "%s%d", START_STREAMING_PROMPT_VN, stream_capabilities_to_vn(rpt->capabilities));
- } else if (stream_has_capability(rpt, STREAM_CAP_V2)) {
- log_receiver_capabilities(rpt);
- sprintf(initial_response, "%s", START_STREAMING_PROMPT_V2);
- } else { // stream_has_capability(rpt, STREAM_CAP_V1)
- log_receiver_capabilities(rpt);
- sprintf(initial_response, "%s", START_STREAMING_PROMPT_V1);
- }
- debug(D_STREAM, "Initial response to %s: %s", rpt->client_ip, initial_response);
+ {
+ // info("STREAM %s [receive from [%s]:%s]: initializing communication...", rrdhost_hostname(rpt->host), rpt->client_ip, rpt->client_port);
+ char initial_response[HTTP_HEADER_SIZE];
+ if (stream_has_capability(rpt, STREAM_CAP_VCAPS)) {
+ log_receiver_capabilities(rpt);
+ sprintf(initial_response, "%s%u", START_STREAMING_PROMPT_VN, rpt->capabilities);
+ }
+ else if (stream_has_capability(rpt, STREAM_CAP_VN)) {
+ log_receiver_capabilities(rpt);
+ sprintf(initial_response, "%s%d", START_STREAMING_PROMPT_VN, stream_capabilities_to_vn(rpt->capabilities));
+ }
+ else if (stream_has_capability(rpt, STREAM_CAP_V2)) {
+ log_receiver_capabilities(rpt);
+ sprintf(initial_response, "%s", START_STREAMING_PROMPT_V2);
+ }
+ else { // stream_has_capability(rpt, STREAM_CAP_V1)
+ log_receiver_capabilities(rpt);
+ sprintf(initial_response, "%s", START_STREAMING_PROMPT_V1);
+ }
+
+ debug(D_STREAM, "Initial response to %s: %s", rpt->client_ip, initial_response);
+ if(send_timeout(
#ifdef ENABLE_HTTPS
- if(send_timeout(&rpt->ssl, rpt->fd, initial_response, strlen(initial_response), 0, 60) != (ssize_t)strlen(initial_response)) {
-#else
- if(send_timeout(rpt->fd, initial_response, strlen(initial_response), 0, 60) != strlen(initial_response)) {
+ &rpt->ssl,
#endif
- log_stream_connection(rpt->client_ip, rpt->client_port, rpt->key, rpt->host->machine_guid, rrdhost_hostname(rpt->host), "FAILED - CANNOT REPLY");
- error("STREAM %s [receive from [%s]:%s]: cannot send ready command.", rrdhost_hostname(rpt->host), rpt->client_ip, rpt->client_port);
- close(rpt->fd);
- return 0;
- }
+ rpt->fd, initial_response, strlen(initial_response), 0, 60) != (ssize_t)strlen(initial_response)) {
- // remove the non-blocking flag from the socket
- if(sock_delnonblock(rpt->fd) < 0)
- error("STREAM %s [receive from [%s]:%s]: cannot remove the non-blocking flag from socket %d", rrdhost_hostname(rpt->host), rpt->client_ip, rpt->client_port, rpt->fd);
-
- struct timeval timeout;
- timeout.tv_sec = 600;
- timeout.tv_usec = 0;
- if (unlikely(setsockopt(rpt->fd, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof timeout) != 0))
- error("STREAM %s [receive from [%s]:%s]: cannot set timeout for socket %d", rrdhost_hostname(rpt->host), rpt->client_ip, rpt->client_port, rpt->fd);
-
- rrdhost_wrlock(rpt->host);
-/* if(rpt->host->connected_senders > 0) {
- rrdhost_unlock(rpt->host);
- log_stream_connection(rpt->client_ip, rpt->client_port, rpt->key, rpt->host->machine_guid, rpt->host->hostname, "REJECTED - ALREADY CONNECTED");
- info("STREAM %s [receive from [%s]:%s]: multiple streaming connections for the same host detected. Rejecting new connection.", rpt->host->hostname, rpt->client_ip, rpt->client_port);
- fclose(fp);
- return 0;
- }
-*/
-
-// rpt->host->connected_senders++;
- if(health_enabled != CONFIG_BOOLEAN_NO) {
- if(alarms_delay > 0) {
- rpt->host->health_delay_up_to = now_realtime_sec() + alarms_delay;
- log_health(
- "[%s]: Postponing health checks for %" PRId64 " seconds, because it was just connected.",
- rrdhost_hostname(rpt->host),
- (int64_t)alarms_delay);
+ rrdpush_receive_log_status(rpt, "cannot reply back", "CANT REPLY DROPPING CONNECTION");
+ close(rpt->fd);
+ return 0;
}
}
- rpt->host->senders_connect_time = now_realtime_sec();
- rpt->host->senders_last_chart_command = 0;
- rpt->host->trigger_chart_obsoletion_check = 1;
- rrdhost_unlock(rpt->host);
+ {
+ // remove the non-blocking flag from the socket
+ if(sock_delnonblock(rpt->fd) < 0)
+ error("STREAM '%s' [receive from [%s]:%s]: "
+ "cannot remove the non-blocking flag from socket %d"
+ , rrdhost_hostname(rpt->host)
+ , rpt->client_ip, rpt->client_port
+ , rpt->fd);
- // call the plugins.d processor to receive the metrics
- info("STREAM %s [receive from [%s]:%s]: receiving metrics...",
- rrdhost_hostname(rpt->host), rpt->client_ip, rpt->client_port);
+ struct timeval timeout;
+ timeout.tv_sec = 600;
+ timeout.tv_usec = 0;
+ if (unlikely(setsockopt(rpt->fd, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof timeout) != 0))
+ error("STREAM '%s' [receive from [%s]:%s]: "
+ "cannot set timeout for socket %d"
+ , rrdhost_hostname(rpt->host)
+ , rpt->client_ip, rpt->client_port
+ , rpt->fd);
+ }
- log_stream_connection(rpt->client_ip, rpt->client_port,
- rpt->key, rpt->host->machine_guid, rrdhost_hostname(rpt->host), "CONNECTED");
+ rrdpush_receive_log_status(rpt, "ready to receive data", "CONNECTED");
cd.capabilities = rpt->capabilities;
@@ -728,12 +813,10 @@ static int rrdpush_receive(struct receiver_state *rpt)
aclk_host_state_update(rpt->host, 1);
#endif
- rrdhost_set_is_parent_label(++localhost->senders_count);
+ rrdhost_set_is_parent_label(++localhost->connected_children_count);
- rrdpush_receiver_replication_reset(rpt);
- rrdcontext_host_child_connected(rpt->host);
-
- rrdhost_flag_clear(rpt->host, RRDHOST_FLAG_RRDPUSH_RECEIVER_DISCONNECTED);
+ // let it reconnect to parent immediately
+ rrdhost_reset_destinations(rpt->host);
size_t count = streaming_parser(rpt, &cd, rpt->fd,
#ifdef ENABLE_HTTPS
@@ -745,15 +828,14 @@ static int rrdpush_receive(struct receiver_state *rpt)
rrdhost_flag_set(rpt->host, RRDHOST_FLAG_RRDPUSH_RECEIVER_DISCONNECTED);
- log_stream_connection(rpt->client_ip, rpt->client_port,
- rpt->key, rpt->host->machine_guid, rpt->hostname,
- "DISCONNECTED");
+ if(!rpt->exit.reason)
+ rpt->exit.reason = "PARSER EXIT";
- error("STREAM %s [receive from [%s]:%s]: disconnected (completed %zu updates).",
- rpt->hostname, rpt->client_ip, rpt->client_port, count);
-
- rrdcontext_host_child_disconnected(rpt->host);
- rrdpush_receiver_replication_reset(rpt);
+ {
+ char msg[100 + 1];
+ snprintfz(msg, 100, "disconnected (completed %zu updates)", count);
+ rrdpush_receive_log_status(rpt, msg, "DISCONNECTED");
+ }
#ifdef ENABLE_ACLK
// in case we have cloud connection we inform cloud
@@ -762,48 +844,41 @@ static int rrdpush_receive(struct receiver_state *rpt)
aclk_host_state_update(rpt->host, 0);
#endif
- rrdhost_set_is_parent_label(--localhost->senders_count);
-
- // During a shutdown there is cleanup code in rrdhost that will cancel the sender thread
- if (!netdata_exit && rpt->host) {
- rrd_rdlock();
- rrdhost_wrlock(rpt->host);
- netdata_mutex_lock(&rpt->host->receiver_lock);
- if (rpt->host->receiver == rpt) {
- rpt->host->senders_connect_time = 0;
- rpt->host->trigger_chart_obsoletion_check = 0;
- rpt->host->senders_disconnected_time = now_realtime_sec();
- rrdhost_flag_set(rpt->host, RRDHOST_FLAG_ORPHAN);
- if(health_enabled == CONFIG_BOOLEAN_AUTO)
- rpt->host->health_enabled = 0;
- }
- rrdhost_unlock(rpt->host);
- if (rpt->host->receiver == rpt) {
- rrdpush_sender_thread_stop(rpt->host);
- }
- netdata_mutex_unlock(&rpt->host->receiver_lock);
- rrd_unlock();
- }
+ rrdhost_set_is_parent_label(--localhost->connected_children_count);
// cleanup
close(rpt->fd);
return (int)count;
}
+static void rrdpush_receiver_thread_cleanup(void *ptr) {
+ struct receiver_state *rpt = (struct receiver_state *) ptr;
+ worker_unregister();
+
+ rrdhost_clear_receiver(rpt);
+
+ info("STREAM '%s' [receive from [%s]:%s]: "
+ "receive thread ended (task id %d)"
+ , rpt->hostname ? rpt->hostname : "-"
+ , rpt->client_ip ? rpt->client_ip : "-", rpt->client_port ? rpt->client_port : "-"
+ , gettid());
+
+ receiver_state_free(rpt);
+}
+
void *rrdpush_receiver_thread(void *ptr) {
netdata_thread_cleanup_push(rrdpush_receiver_thread_cleanup, ptr);
- struct receiver_state *rpt = (struct receiver_state *)ptr;
- info("STREAM %s [%s]:%s: receive thread created (task id %d)", rpt->hostname, rpt->client_ip, rpt->client_port, gettid());
-
worker_register("STREAMRCV");
worker_register_job_custom_metric(WORKER_RECEIVER_JOB_BYTES_READ, "received bytes", "bytes/s", WORKER_METRIC_INCREMENT);
worker_register_job_custom_metric(WORKER_RECEIVER_JOB_BYTES_UNCOMPRESSED, "uncompressed bytes", "bytes/s", WORKER_METRIC_INCREMENT);
worker_register_job_custom_metric(WORKER_RECEIVER_JOB_REPLICATION_COMPLETION, "replication completion", "%", WORKER_METRIC_ABSOLUTE);
+
+ struct receiver_state *rpt = (struct receiver_state *)ptr;
+ info("STREAM %s [%s]:%s: receive thread created (task id %d)", rpt->hostname, rpt->client_ip, rpt->client_port, gettid());
+
rrdpush_receive(rpt);
- worker_unregister();
netdata_thread_cleanup_pop(1);
return NULL;
}
-
diff --git a/streaming/replication.c b/streaming/replication.c
index d659d701..7c1f16b4 100644
--- a/streaming/replication.c
+++ b/streaming/replication.c
@@ -3,28 +3,30 @@
#include "replication.h"
#include "Judy.h"
-#define STREAMING_START_MAX_SENDER_BUFFER_PERCENTAGE_ALLOWED 50
-#define MAX_SENDER_BUFFER_PERCENTAGE_ALLOWED 50
-#define MIN_SENDER_BUFFER_PERCENTAGE_ALLOWED 10
+#define STREAMING_START_MAX_SENDER_BUFFER_PERCENTAGE_ALLOWED 50ULL
+#define MAX_REPLICATION_MESSAGE_PERCENT_SENDER_BUFFER 25ULL
+#define MAX_SENDER_BUFFER_PERCENTAGE_ALLOWED 50ULL
+#define MIN_SENDER_BUFFER_PERCENTAGE_ALLOWED 10ULL
#define WORKER_JOB_FIND_NEXT 1
#define WORKER_JOB_QUERYING 2
#define WORKER_JOB_DELETE_ENTRY 3
#define WORKER_JOB_FIND_CHART 4
-#define WORKER_JOB_CHECK_CONSISTENCY 5
-#define WORKER_JOB_BUFFER_COMMIT 6
-#define WORKER_JOB_CLEANUP 7
-#define WORKER_JOB_WAIT 8
+#define WORKER_JOB_PREPARE_QUERY 5
+#define WORKER_JOB_CHECK_CONSISTENCY 6
+#define WORKER_JOB_BUFFER_COMMIT 7
+#define WORKER_JOB_CLEANUP 8
+#define WORKER_JOB_WAIT 9
// master thread worker jobs
-#define WORKER_JOB_STATISTICS 9
-#define WORKER_JOB_CUSTOM_METRIC_PENDING_REQUESTS 10
-#define WORKER_JOB_CUSTOM_METRIC_SKIPPED_NO_ROOM 11
-#define WORKER_JOB_CUSTOM_METRIC_COMPLETION 12
-#define WORKER_JOB_CUSTOM_METRIC_ADDED 13
-#define WORKER_JOB_CUSTOM_METRIC_DONE 14
-#define WORKER_JOB_CUSTOM_METRIC_SENDER_RESETS 15
-#define WORKER_JOB_CUSTOM_METRIC_SENDER_FULL 16
+#define WORKER_JOB_STATISTICS 10
+#define WORKER_JOB_CUSTOM_METRIC_PENDING_REQUESTS 11
+#define WORKER_JOB_CUSTOM_METRIC_SKIPPED_NO_ROOM 12
+#define WORKER_JOB_CUSTOM_METRIC_COMPLETION 13
+#define WORKER_JOB_CUSTOM_METRIC_ADDED 14
+#define WORKER_JOB_CUSTOM_METRIC_DONE 15
+#define WORKER_JOB_CUSTOM_METRIC_SENDER_RESETS 16
+#define WORKER_JOB_CUSTOM_METRIC_SENDER_FULL 17
#define ITERATIONS_IDLE_WITHOUT_PENDING_TO_RUN_SENDER_VERIFICATION 30
#define SECONDS_TO_RESET_POINT_IN_TIME 10
@@ -44,6 +46,12 @@ struct replication_query_statistics replication_get_query_statistics(void) {
return ret;
}
+size_t replication_buffers_allocated = 0;
+
+size_t replication_allocated_buffers(void) {
+ return __atomic_load_n(&replication_buffers_allocated, __ATOMIC_RELAXED);
+}
+
// ----------------------------------------------------------------------------
// sending replication replies
@@ -51,137 +59,400 @@ struct replication_dimension {
STORAGE_POINT sp;
struct storage_engine_query_handle handle;
bool enabled;
+ bool skip;
DICTIONARY *dict;
const DICTIONARY_ITEM *rda;
RRDDIM *rd;
};
-static time_t replicate_chart_timeframe(BUFFER *wb, RRDSET *st, time_t after, time_t before, bool enable_streaming, time_t wall_clock_time) {
+struct replication_query {
+ RRDSET *st;
+
+ struct {
+ time_t first_entry_t;
+ time_t last_entry_t;
+ } db;
+
+ struct { // what the parent requested
+ time_t after;
+ time_t before;
+ bool enable_streaming;
+ } request;
+
+ struct { // what the child will do
+ time_t after;
+ time_t before;
+ bool enable_streaming;
+
+ bool locked_data_collection;
+ bool execute;
+ bool interrupted;
+ } query;
+
+ time_t wall_clock_time;
+
+ size_t points_read;
+ size_t points_generated;
+
+ struct storage_engine_query_ops *ops;
+ struct replication_request *rq;
+
+ size_t dimensions;
+ struct replication_dimension data[];
+};
+
+static struct replication_query *replication_query_prepare(
+ RRDSET *st,
+ time_t db_first_entry,
+ time_t db_last_entry,
+ time_t requested_after,
+ time_t requested_before,
+ bool requested_enable_streaming,
+ time_t query_after,
+ time_t query_before,
+ bool query_enable_streaming,
+ time_t wall_clock_time
+) {
size_t dimensions = rrdset_number_of_dimensions(st);
- size_t points_read = 0, points_generated = 0;
+ struct replication_query *q = callocz(1, sizeof(struct replication_query) + dimensions * sizeof(struct replication_dimension));
+ __atomic_add_fetch(&replication_buffers_allocated, sizeof(struct replication_query) + dimensions * sizeof(struct replication_dimension), __ATOMIC_RELAXED);
- struct storage_engine_query_ops *ops = &st->rrdhost->db[0].eng->api.query_ops;
- struct replication_dimension data[dimensions];
- memset(data, 0, sizeof(data));
+ q->dimensions = dimensions;
+ q->st = st;
- if(enable_streaming && st->last_updated.tv_sec > before) {
- internal_error(true, "STREAM_SENDER REPLAY: 'host:%s/chart:%s' has start_streaming = true, adjusting replication before timestamp from %llu to %llu",
- rrdhost_hostname(st->rrdhost), rrdset_id(st),
- (unsigned long long)before,
- (unsigned long long)st->last_updated.tv_sec
- );
- before = st->last_updated.tv_sec;
+ q->db.first_entry_t = db_first_entry;
+ q->db.last_entry_t = db_last_entry;
+
+ q->request.after = requested_after,
+ q->request.before = requested_before,
+ q->request.enable_streaming = requested_enable_streaming,
+
+ q->query.after = query_after;
+ q->query.before = query_before;
+ q->query.enable_streaming = query_enable_streaming;
+
+ q->wall_clock_time = wall_clock_time;
+
+ if (!q->dimensions || !q->query.after || !q->query.before) {
+ q->query.execute = false;
+ q->dimensions = 0;
+ return q;
+ }
+
+ if(q->query.enable_streaming) {
+ netdata_spinlock_lock(&st->data_collection_lock);
+ q->query.locked_data_collection = true;
+
+ if (st->last_updated.tv_sec > q->query.before) {
+#ifdef NETDATA_LOG_REPLICATION_REQUESTS
+ internal_error(true,
+ "STREAM_SENDER REPLAY: 'host:%s/chart:%s' "
+ "has start_streaming = true, "
+ "adjusting replication before timestamp from %llu to %llu",
+ rrdhost_hostname(st->rrdhost), rrdset_id(st),
+ (unsigned long long) q->query.before,
+ (unsigned long long) st->last_updated.tv_sec
+ );
+#endif
+ q->query.before = MIN(st->last_updated.tv_sec, wall_clock_time);
+ }
}
+ q->ops = &st->rrdhost->db[0].eng->api.query_ops;
+
// prepare our array of dimensions
- {
- RRDDIM *rd;
- rrddim_foreach_read(rd, st) {
- if(unlikely(!rd || !rd_dfe.item || !rd->exposed))
- continue;
+ size_t count = 0;
+ RRDDIM *rd;
+ rrddim_foreach_read(rd, st) {
+ if (unlikely(!rd || !rd_dfe.item || !rd->exposed))
+ continue;
- if (unlikely(rd_dfe.counter >= dimensions)) {
- internal_error(true, "STREAM_SENDER REPLAY ERROR: 'host:%s/chart:%s' has more dimensions than the replicated ones",
- rrdhost_hostname(st->rrdhost), rrdset_id(st));
- break;
- }
+ if (unlikely(rd_dfe.counter >= q->dimensions)) {
+ internal_error(true,
+ "STREAM_SENDER REPLAY ERROR: 'host:%s/chart:%s' has more dimensions than the replicated ones",
+ rrdhost_hostname(st->rrdhost), rrdset_id(st));
+ break;
+ }
+
+ struct replication_dimension *d = &q->data[rd_dfe.counter];
+
+ d->dict = rd_dfe.dict;
+ d->rda = dictionary_acquired_item_dup(rd_dfe.dict, rd_dfe.item);
+ d->rd = rd;
- struct replication_dimension *d = &data[rd_dfe.counter];
+ q->ops->init(rd->tiers[0].db_metric_handle, &d->handle, q->query.after, q->query.before,
+ q->query.locked_data_collection ? STORAGE_PRIORITY_HIGH : STORAGE_PRIORITY_LOW);
+ d->enabled = true;
+ d->skip = false;
+ count++;
+ }
+ rrddim_foreach_done(rd);
+
+ if(!count) {
+ // no data for this chart
- d->dict = rd_dfe.dict;
- d->rda = dictionary_acquired_item_dup(rd_dfe.dict, rd_dfe.item);
- d->rd = rd;
+ q->query.execute = false;
- ops->init(rd->tiers[0]->db_metric_handle, &d->handle, after, before);
- d->enabled = true;
+ if(q->query.locked_data_collection) {
+ netdata_spinlock_unlock(&st->data_collection_lock);
+ q->query.locked_data_collection = false;
}
- rrddim_foreach_done(rd);
+
+ }
+ else {
+ // we have data for this chart
+
+ q->query.execute = true;
+ }
+
+ return q;
+}
+
+static void replication_send_chart_collection_state(BUFFER *wb, RRDSET *st) {
+ RRDDIM *rd;
+ rrddim_foreach_read(rd, st) {
+ if(!rd->exposed) continue;
+
+ buffer_sprintf(wb, PLUGINSD_KEYWORD_REPLAY_RRDDIM_STATE " \"%s\" %llu %lld " NETDATA_DOUBLE_FORMAT " " NETDATA_DOUBLE_FORMAT "\n",
+ rrddim_id(rd),
+ (usec_t)rd->last_collected_time.tv_sec * USEC_PER_SEC + (usec_t)rd->last_collected_time.tv_usec,
+ rd->last_collected_value,
+ rd->last_calculated_value,
+ rd->last_stored_value
+ );
+ }
+ rrddim_foreach_done(rd);
+
+ buffer_sprintf(wb, PLUGINSD_KEYWORD_REPLAY_RRDSET_STATE " %llu %llu\n",
+ (usec_t)st->last_collected_time.tv_sec * USEC_PER_SEC + (usec_t)st->last_collected_time.tv_usec,
+ (usec_t)st->last_updated.tv_sec * USEC_PER_SEC + (usec_t)st->last_updated.tv_usec
+ );
+}
+
+static void replication_query_finalize(BUFFER *wb, struct replication_query *q, bool executed) {
+ size_t dimensions = q->dimensions;
+
+ if(wb && q->query.enable_streaming)
+ replication_send_chart_collection_state(wb, q->st);
+
+ if(q->query.locked_data_collection) {
+ netdata_spinlock_unlock(&q->st->data_collection_lock);
+ q->query.locked_data_collection = false;
+ }
+
+ // release all the dictionary items acquired
+ // finalize the queries
+ size_t queries = 0;
+
+ for (size_t i = 0; i < dimensions; i++) {
+ struct replication_dimension *d = &q->data[i];
+ if (unlikely(!d->enabled)) continue;
+
+ q->ops->finalize(&d->handle);
+
+ dictionary_acquired_item_release(d->dict, d->rda);
+
+ // update global statistics
+ queries++;
+ }
+
+ if(executed) {
+ netdata_spinlock_lock(&replication_queries.spinlock);
+ replication_queries.queries_started += queries;
+ replication_queries.queries_finished += queries;
+ replication_queries.points_read += q->points_read;
+ replication_queries.points_generated += q->points_generated;
+ netdata_spinlock_unlock(&replication_queries.spinlock);
}
- time_t now = after + 1, actual_after = 0, actual_before = 0; (void)actual_before;
+ __atomic_sub_fetch(&replication_buffers_allocated, sizeof(struct replication_query) + dimensions * sizeof(struct replication_dimension), __ATOMIC_RELAXED);
+ freez(q);
+}
+
+static void replication_query_align_to_optimal_before(struct replication_query *q) {
+ if(!q->query.execute || q->query.enable_streaming)
+ return;
+
+ size_t dimensions = q->dimensions;
+ time_t expanded_before = 0;
+
+ for (size_t i = 0; i < dimensions; i++) {
+ struct replication_dimension *d = &q->data[i];
+ if(unlikely(!d->enabled)) continue;
+
+ time_t new_before = q->ops->align_to_optimal_before(&d->handle);
+ if (!expanded_before || new_before < expanded_before)
+ expanded_before = new_before;
+ }
+
+ if(expanded_before > q->query.before && // it is later than the original
+ (expanded_before - q->query.before) / q->st->update_every < 1024 && // it is reasonable (up to a page)
+ expanded_before < q->st->last_updated.tv_sec && // it is not the chart's last updated time
+ expanded_before < q->wall_clock_time) // it is not later than the wall clock time
+ q->query.before = expanded_before;
+}
+
+static bool replication_query_execute(BUFFER *wb, struct replication_query *q, size_t max_msg_size) {
+ replication_query_align_to_optimal_before(q);
+
+ time_t after = q->query.after;
+ time_t before = q->query.before;
+ size_t dimensions = q->dimensions;
+ struct storage_engine_query_ops *ops = q->ops;
+ time_t wall_clock_time = q->wall_clock_time;
+
+ size_t points_read = q->points_read, points_generated = q->points_generated;
+
+#ifdef NETDATA_LOG_REPLICATION_REQUESTS
+ time_t actual_after = 0, actual_before = 0;
+#endif
+
+ time_t now = after + 1;
+ time_t last_end_time_in_buffer = 0;
while(now <= before) {
- time_t min_start_time = 0, min_end_time = 0;
+ time_t min_start_time = 0, max_start_time = 0, min_end_time = 0, max_end_time = 0, min_update_every = 0, max_update_every = 0;
for (size_t i = 0; i < dimensions ;i++) {
- struct replication_dimension *d = &data[i];
- if(unlikely(!d->enabled)) continue;
+ struct replication_dimension *d = &q->data[i];
+ if(unlikely(!d->enabled || d->skip)) continue;
// fetch the first valid point for the dimension
- int max_skip = 100;
- while(d->sp.end_time < now && !ops->is_finished(&d->handle) && max_skip-- > 0) {
+ int max_skip = 1000;
+ while(d->sp.end_time_s < now && !ops->is_finished(&d->handle) && max_skip-- >= 0) {
d->sp = ops->next_metric(&d->handle);
points_read++;
}
- internal_error(max_skip <= 0,
- "STREAM_SENDER REPLAY ERROR: 'host:%s/chart:%s/dim:%s': db does not advance the query beyond time %llu",
- rrdhost_hostname(st->rrdhost), rrdset_id(st), rrddim_id(d->rd), (unsigned long long) now);
+ if(max_skip <= 0) {
+ d->skip = true;
- if(unlikely(d->sp.end_time < now || storage_point_is_unset(d->sp) || storage_point_is_empty(d->sp)))
- continue;
+ error_limit_static_global_var(erl, 1, 0);
+ error_limit(&erl,
+ "STREAM_SENDER REPLAY ERROR: 'host:%s/chart:%s/dim:%s': db does not advance the query beyond time %llu (tried 1000 times to get the next point and always got back a point in the past)",
+ rrdhost_hostname(q->st->rrdhost), rrdset_id(q->st), rrddim_id(d->rd),
+ (unsigned long long) now);
- if(unlikely(!min_start_time)) {
- min_start_time = d->sp.start_time;
- min_end_time = d->sp.end_time;
- }
- else {
- min_start_time = MIN(min_start_time, d->sp.start_time);
- min_end_time = MIN(min_end_time, d->sp.end_time);
+ continue;
}
- }
- if(unlikely(min_start_time > wall_clock_time + 1 || min_end_time > wall_clock_time + st->update_every + 1)) {
- internal_error(true,
- "STREAM_SENDER REPLAY ERROR: 'host:%s/chart:%s': db provided future start time %llu or end time %llu (now is %llu)",
- rrdhost_hostname(st->rrdhost), rrdset_id(st),
- (unsigned long long)min_start_time,
- (unsigned long long)min_end_time,
- (unsigned long long)wall_clock_time);
- break;
- }
+ if(unlikely(d->sp.end_time_s < now || d->sp.end_time_s < d->sp.start_time_s))
+ // this dimension does not provide any data
+ continue;
- if(unlikely(min_end_time < now)) {
-#ifdef NETDATA_LOG_REPLICATION_REQUESTS
- internal_error(true,
- "STREAM_SENDER REPLAY: 'host:%s/chart:%s': no data on any dimension beyond time %llu",
- rrdhost_hostname(st->rrdhost), rrdset_id(st), (unsigned long long)now);
-#endif // NETDATA_LOG_REPLICATION_REQUESTS
- break;
+ time_t update_every = d->sp.end_time_s - d->sp.start_time_s;
+ if(unlikely(!update_every))
+ update_every = q->st->update_every;
+
+ if(unlikely(!min_update_every))
+ min_update_every = update_every;
+
+ if(unlikely(!min_start_time))
+ min_start_time = d->sp.start_time_s;
+
+ if(unlikely(!min_end_time))
+ min_end_time = d->sp.end_time_s;
+
+ min_update_every = MIN(min_update_every, update_every);
+ max_update_every = MAX(max_update_every, update_every);
+
+ min_start_time = MIN(min_start_time, d->sp.start_time_s);
+ max_start_time = MAX(max_start_time, d->sp.start_time_s);
+
+ min_end_time = MIN(min_end_time, d->sp.end_time_s);
+ max_end_time = MAX(max_end_time, d->sp.end_time_s);
}
- if(unlikely(min_end_time <= min_start_time))
- min_start_time = min_end_time - st->update_every;
+ if (unlikely(min_update_every != max_update_every ||
+ min_start_time != max_start_time)) {
- if(unlikely(!actual_after)) {
- actual_after = min_end_time;
- actual_before = min_end_time;
+ time_t fix_min_start_time;
+ if(last_end_time_in_buffer &&
+ last_end_time_in_buffer >= min_start_time &&
+ last_end_time_in_buffer <= max_start_time) {
+ fix_min_start_time = last_end_time_in_buffer;
+ }
+ else
+ fix_min_start_time = min_end_time - min_update_every;
+
+ error_limit_static_global_var(erl, 1, 0);
+ error_limit(&erl, "REPLAY WARNING: 'host:%s/chart:%s' "
+ "misaligned dimensions "
+ "update every (min: %ld, max: %ld), "
+ "start time (min: %ld, max: %ld), "
+ "end time (min %ld, max %ld), "
+ "now %ld, last end time sent %ld, "
+ "min start time is fixed to %ld",
+ rrdhost_hostname(q->st->rrdhost), rrdset_id(q->st),
+ min_update_every, max_update_every,
+ min_start_time, max_start_time,
+ min_end_time, max_end_time,
+ now, last_end_time_in_buffer,
+ fix_min_start_time
+ );
+
+ min_start_time = fix_min_start_time;
}
- else
+
+ if(likely(min_start_time <= now && min_end_time >= now)) {
+ // we have a valid point
+
+ if (unlikely(min_end_time == min_start_time))
+ min_start_time = min_end_time - q->st->update_every;
+
+#ifdef NETDATA_LOG_REPLICATION_REQUESTS
+ if (unlikely(!actual_after))
+ actual_after = min_end_time;
+
actual_before = min_end_time;
+#endif
- buffer_sprintf(wb, PLUGINSD_KEYWORD_REPLAY_BEGIN " '' %llu %llu %llu\n"
- , (unsigned long long)min_start_time
- , (unsigned long long)min_end_time
- , (unsigned long long)wall_clock_time
- );
+ if(buffer_strlen(wb) > max_msg_size && last_end_time_in_buffer) {
+ q->query.before = last_end_time_in_buffer;
+ q->query.enable_streaming = false;
- // output the replay values for this time
- for (size_t i = 0; i < dimensions ;i++) {
- struct replication_dimension *d = &data[i];
- if(unlikely(!d->enabled)) continue;
+ internal_error(true, "REPLICATION: buffer size %zu is more than the max message size %zu for chart '%s' of host '%s'. "
+ "Interrupting replication request (%ld to %ld, %s) at %ld to %ld, %s.",
+ buffer_strlen(wb), max_msg_size, rrdset_id(q->st), rrdhost_hostname(q->st->rrdhost),
+ q->request.after, q->request.before, q->request.enable_streaming?"true":"false",
+ q->query.after, q->query.before, q->query.enable_streaming?"true":"false");
- if(likely(d->sp.start_time <= min_end_time && d->sp.end_time >= min_end_time))
- buffer_sprintf(wb, PLUGINSD_KEYWORD_REPLAY_SET " \"%s\" " NETDATA_DOUBLE_FORMAT " \"%s\"\n",
- rrddim_id(d->rd), d->sp.sum, d->sp.flags & SN_FLAG_RESET ? "R" : "");
+ q->query.interrupted = true;
- else
- buffer_sprintf(wb, PLUGINSD_KEYWORD_REPLAY_SET " \"%s\" NAN \"E\"\n",
- rrddim_id(d->rd));
+ break;
+ }
+ last_end_time_in_buffer = min_end_time;
- points_generated++;
- }
+ buffer_sprintf(wb, PLUGINSD_KEYWORD_REPLAY_BEGIN " '' %llu %llu %llu\n",
+ (unsigned long long) min_start_time,
+ (unsigned long long) min_end_time,
+ (unsigned long long) wall_clock_time
+ );
+
+ // output the replay values for this time
+ for (size_t i = 0; i < dimensions; i++) {
+ struct replication_dimension *d = &q->data[i];
+ if (unlikely(!d->enabled)) continue;
+
+ if (likely( d->sp.start_time_s <= min_end_time &&
+ d->sp.end_time_s >= min_end_time &&
+ !storage_point_is_unset(d->sp) &&
+ !storage_point_is_gap(d->sp))) {
- now = min_end_time + 1;
+ buffer_sprintf(wb, PLUGINSD_KEYWORD_REPLAY_SET " \"%s\" " NETDATA_DOUBLE_FORMAT " \"%s\"\n",
+ rrddim_id(d->rd), d->sp.sum, d->sp.flags & SN_FLAG_RESET ? "R" : "");
+
+ points_generated++;
+ }
+ }
+
+ now = min_end_time + 1;
+ }
+ else if(unlikely(min_end_time < now))
+ // the query does not progress
+ break;
+ else
+ // we have gap - all points are in the future
+ now = min_start_time;
}
#ifdef NETDATA_LOG_REPLICATION_REQUESTS
@@ -202,110 +473,89 @@ static time_t replicate_chart_timeframe(BUFFER *wb, RRDSET *st, time_t after, ti
(unsigned long long)after, (unsigned long long)before);
#endif // NETDATA_LOG_REPLICATION_REQUESTS
- // release all the dictionary items acquired
- // finalize the queries
- size_t queries = 0;
- for(size_t i = 0; i < dimensions ;i++) {
- struct replication_dimension *d = &data[i];
- if(unlikely(!d->enabled)) continue;
+ q->points_read = points_read;
+ q->points_generated = points_generated;
- ops->finalize(&d->handle);
+ bool finished_with_gap = false;
+ if(last_end_time_in_buffer < before - q->st->update_every)
+ finished_with_gap = true;
- dictionary_acquired_item_release(d->dict, d->rda);
+ return finished_with_gap;
+}
- // update global statistics
- queries++;
- }
+static struct replication_query *replication_response_prepare(RRDSET *st, bool requested_enable_streaming, time_t requested_after, time_t requested_before) {
+ time_t wall_clock_time = now_realtime_sec();
- netdata_spinlock_lock(&replication_queries.spinlock);
- replication_queries.queries_started += queries;
- replication_queries.queries_finished += queries;
- replication_queries.points_read += points_read;
- replication_queries.points_generated += points_generated;
- netdata_spinlock_unlock(&replication_queries.spinlock);
+ if(requested_after > requested_before) {
+ // flip them
+ time_t t = requested_before;
+ requested_before = requested_after;
+ requested_after = t;
+ }
- return before;
-}
+ if(requested_after > wall_clock_time) {
+ requested_after = 0;
+ requested_before = 0;
+ requested_enable_streaming = true;
+ }
-static void replicate_chart_collection_state(BUFFER *wb, RRDSET *st) {
- RRDDIM *rd;
- rrddim_foreach_read(rd, st) {
- if(!rd->exposed) continue;
-
- buffer_sprintf(wb, PLUGINSD_KEYWORD_REPLAY_RRDDIM_STATE " \"%s\" %llu %lld " NETDATA_DOUBLE_FORMAT " " NETDATA_DOUBLE_FORMAT "\n",
- rrddim_id(rd),
- (usec_t)rd->last_collected_time.tv_sec * USEC_PER_SEC + (usec_t)rd->last_collected_time.tv_usec,
- rd->last_collected_value,
- rd->last_calculated_value,
- rd->last_stored_value
- );
+ if(requested_before > wall_clock_time) {
+ requested_before = wall_clock_time;
+ requested_enable_streaming = true;
}
- rrddim_foreach_done(rd);
- buffer_sprintf(wb, PLUGINSD_KEYWORD_REPLAY_RRDSET_STATE " %llu %llu\n",
- (usec_t)st->last_collected_time.tv_sec * USEC_PER_SEC + (usec_t)st->last_collected_time.tv_usec,
- (usec_t)st->last_updated.tv_sec * USEC_PER_SEC + (usec_t)st->last_updated.tv_usec
- );
-}
+ time_t query_after = requested_after;
+ time_t query_before = requested_before;
+ bool query_enable_streaming = requested_enable_streaming;
-bool replicate_chart_response(RRDHOST *host, RRDSET *st, bool start_streaming, time_t after, time_t before) {
- time_t query_after = after;
- time_t query_before = before;
- time_t now = now_realtime_sec();
- time_t tolerance = 2; // sometimes from the time we get this value, to the time we check,
- // a data collection has been made
- // so, we give this tolerance to detect invalid timestamps
-
- // find the first entry we have
- time_t first_entry_local = rrdset_first_entry_t(st);
- if(first_entry_local > now + tolerance) {
- internal_error(true,
- "STREAM_SENDER REPLAY ERROR: 'host:%s/chart:%s' db first time %llu is in the future (now is %llu)",
- rrdhost_hostname(st->rrdhost), rrdset_id(st),
- (unsigned long long)first_entry_local, (unsigned long long)now);
- first_entry_local = now;
+ time_t db_first_entry = 0, db_last_entry = 0;
+ rrdset_get_retention_of_tier_for_collected_chart(st, &db_first_entry, &db_last_entry, wall_clock_time, 0);
+
+ if(requested_after == 0 && requested_before == 0 && requested_enable_streaming == true) {
+ // no data requested - just enable streaming
+ ;
}
+ else {
+ if (query_after < db_first_entry)
+ query_after = db_first_entry;
- if (query_after < first_entry_local)
- query_after = first_entry_local;
+ if (query_before > db_last_entry)
+ query_before = db_last_entry;
- // find the latest entry we have
- time_t last_entry_local = st->last_updated.tv_sec;
- if(!last_entry_local) {
- internal_error(true,
- "STREAM_SENDER REPLAY ERROR: 'host:%s/chart:%s' RRDSET reports last updated time zero.",
- rrdhost_hostname(st->rrdhost), rrdset_id(st));
- last_entry_local = rrdset_last_entry_t(st);
- if(!last_entry_local) {
- internal_error(true,
- "STREAM_SENDER REPLAY ERROR: 'host:%s/chart:%s' db reports last time zero.",
- rrdhost_hostname(st->rrdhost), rrdset_id(st));
- last_entry_local = now;
+ // if the parent asked us to start streaming, then fill the rest with the data that we have
+ if (requested_enable_streaming)
+ query_before = db_last_entry;
+
+ if (query_after > query_before) {
+ time_t tmp = query_before;
+ query_before = query_after;
+ query_after = tmp;
}
- }
- if(last_entry_local > now + tolerance) {
- internal_error(true,
- "STREAM_SENDER REPLAY ERROR: 'host:%s/chart:%s' last updated time %llu is in the future (now is %llu)",
- rrdhost_hostname(st->rrdhost), rrdset_id(st),
- (unsigned long long)last_entry_local, (unsigned long long)now);
- last_entry_local = now;
+ query_enable_streaming = (requested_enable_streaming ||
+ query_before == db_last_entry ||
+ !requested_after ||
+ !requested_before) ? true : false;
}
- if (query_before > last_entry_local)
- query_before = last_entry_local;
+ return replication_query_prepare(
+ st,
+ db_first_entry, db_last_entry,
+ requested_after, requested_before, requested_enable_streaming,
+ query_after, query_before, query_enable_streaming,
+ wall_clock_time);
+}
- // if the parent asked us to start streaming, then fill the rest with the data that we have
- if (start_streaming)
- query_before = last_entry_local;
+void replication_response_cancel_and_finalize(struct replication_query *q) {
+ replication_query_finalize(NULL, q, false);
+}
- if (query_after > query_before) {
- time_t tmp = query_before;
- query_before = query_after;
- query_after = tmp;
- }
+static bool sender_is_still_connected_for_this_request(struct replication_request *rq);
- bool enable_streaming = (start_streaming || query_before == last_entry_local || !after || !before) ? true : false;
+bool replication_response_execute_and_finalize(struct replication_query *q, size_t max_msg_size) {
+ struct replication_request *rq = q->rq;
+ RRDSET *st = q->st;
+ RRDHOST *host = st->rrdhost;
// we might want to optimize this by filling a temporary buffer
// and copying the result to the host's buffer in order to avoid
@@ -314,25 +564,24 @@ bool replicate_chart_response(RRDHOST *host, RRDSET *st, bool start_streaming, t
buffer_sprintf(wb, PLUGINSD_KEYWORD_REPLAY_BEGIN " \"%s\"\n", rrdset_id(st));
- if(after != 0 && before != 0)
- before = replicate_chart_timeframe(wb, st, query_after, query_before, enable_streaming, now);
- else {
- after = 0;
- before = 0;
- enable_streaming = true;
- }
+ bool locked_data_collection = q->query.locked_data_collection;
+ q->query.locked_data_collection = false;
- // get again the world clock time
- time_t world_clock_time = now_realtime_sec();
- if(enable_streaming) {
- if(now < world_clock_time) {
- // we needed time to execute this request
- // so, the parent will need to replicate more data
- enable_streaming = false;
- }
- else
- replicate_chart_collection_state(wb, st);
- }
+ bool finished_with_gap = false;
+ if(q->query.execute)
+ finished_with_gap = replication_query_execute(wb, q, max_msg_size);
+
+ time_t after = q->request.after;
+ time_t before = q->query.before;
+ bool enable_streaming = q->query.enable_streaming;
+
+ replication_query_finalize(wb, q, q->query.execute);
+ q = NULL; // IMPORTANT: q is invalid now
+
+ // get a fresh retention to send to the parent
+ time_t wall_clock_time = now_realtime_sec();
+ time_t db_first_entry, db_last_entry;
+ rrdset_get_retention_of_tier_for_collected_chart(st, &db_first_entry, &db_last_entry, wall_clock_time, 0);
// end with first/last entries we have, and the first start time and
// last end time of the data we sent
@@ -342,7 +591,7 @@ bool replicate_chart_response(RRDHOST *host, RRDSET *st, bool start_streaming, t
(int)st->update_every
// child first db time, child end db time
- , (unsigned long long)first_entry_local, (unsigned long long)last_entry_local
+ , (unsigned long long)db_first_entry, (unsigned long long)db_last_entry
// start streaming boolean
, enable_streaming ? "true" : "false"
@@ -351,13 +600,40 @@ bool replicate_chart_response(RRDHOST *host, RRDSET *st, bool start_streaming, t
, (unsigned long long)after, (unsigned long long)before
// child world clock time
- , (unsigned long long)world_clock_time
+ , (unsigned long long)wall_clock_time
);
worker_is_busy(WORKER_JOB_BUFFER_COMMIT);
sender_commit(host->sender, wb);
worker_is_busy(WORKER_JOB_CLEANUP);
+ if(enable_streaming) {
+ if(sender_is_still_connected_for_this_request(rq)) {
+ // enable normal streaming if we have to
+ // but only if the sender buffer has not been flushed since we started
+
+ if(rrdset_flag_check(st, RRDSET_FLAG_SENDER_REPLICATION_IN_PROGRESS)) {
+ rrdset_flag_clear(st, RRDSET_FLAG_SENDER_REPLICATION_IN_PROGRESS);
+ rrdset_flag_set(st, RRDSET_FLAG_SENDER_REPLICATION_FINISHED);
+ rrdhost_sender_replicating_charts_minus_one(st->rrdhost);
+
+ if(!finished_with_gap)
+ st->upstream_resync_time_s = 0;
+
+#ifdef NETDATA_LOG_REPLICATION_REQUESTS
+ internal_error(true, "STREAM_SENDER REPLAY: 'host:%s/chart:%s' streaming starts",
+ rrdhost_hostname(st->rrdhost), rrdset_id(st));
+#endif
+ }
+ else
+ internal_error(true, "REPLAY ERROR: 'host:%s/chart:%s' received start streaming command, but the chart is not in progress replicating",
+ rrdhost_hostname(st->rrdhost), rrdset_id(st));
+ }
+ }
+
+ if(locked_data_collection)
+ netdata_spinlock_unlock(&st->data_collection_lock);
+
return enable_streaming;
}
@@ -376,14 +652,14 @@ struct replication_request_details {
struct {
time_t first_entry_t; // the first entry time the child has
time_t last_entry_t; // the last entry time the child has
- time_t world_time_t; // the current time of the child
+ time_t wall_clock_time; // the current time of the child
+ bool fixed_last_entry; // when set we set the last entry to wall clock time
} child_db;
struct {
time_t first_entry_t; // the first entry time we have
time_t last_entry_t; // the last entry time we have
- bool last_entry_t_adjusted_to_now; // true, if the last entry time was in the future, and we fixed
- time_t now; // the current local world clock time
+ time_t wall_clock_time; // the current local world clock time
} local_db;
struct {
@@ -403,9 +679,36 @@ struct replication_request_details {
} wanted;
};
-static bool send_replay_chart_cmd(struct replication_request_details *r, const char *msg __maybe_unused) {
+static void replicate_log_request(struct replication_request_details *r, const char *msg) {
+#ifdef NETDATA_INTERNAL_CHECKS
+ internal_error(true,
+#else
+ error_limit_static_global_var(erl, 1, 0);
+ error_limit(&erl,
+#endif
+ "REPLAY ERROR: 'host:%s/chart:%s' child sent: "
+ "db from %ld to %ld%s, wall clock time %ld, "
+ "last request from %ld to %ld, "
+ "issue: %s - "
+ "sending replication request from %ld to %ld, start streaming %s",
+ rrdhost_hostname(r->st->rrdhost), rrdset_id(r->st),
+ r->child_db.first_entry_t,
+ r->child_db.last_entry_t, r->child_db.fixed_last_entry ? " (fixed)" : "",
+ r->child_db.wall_clock_time,
+ r->last_request.after,
+ r->last_request.before,
+ msg,
+ r->wanted.after,
+ r->wanted.before,
+ r->wanted.start_streaming ? "true" : "false");
+}
+
+static bool send_replay_chart_cmd(struct replication_request_details *r, const char *msg, bool log) {
RRDSET *st = r->st;
+ if(log)
+ replicate_log_request(r, msg);
+
if(st->rrdhost->receiver && (!st->rrdhost->receiver->replication_first_time_t || r->wanted.after < st->rrdhost->receiver->replication_first_time_t))
st->rrdhost->receiver->replication_first_time_t = r->wanted.after;
@@ -422,7 +725,7 @@ static bool send_replay_chart_cmd(struct replication_request_details *r, const c
internal_error(true,
"REPLAY: 'host:%s/chart:%s' sending replication request %ld [%s] to %ld [%s], start streaming '%s': %s: "
- "last[%ld - %ld] child[%ld - %ld, now %ld %s] local[%ld - %ld %s, now %ld] gap[%ld - %ld %s] %s"
+ "last[%ld - %ld] child[%ld - %ld, now %ld %s] local[%ld - %ld, now %ld] gap[%ld - %ld %s] %s"
, rrdhost_hostname(r->host), rrdset_id(r->st)
, r->wanted.after, wanted_after_buf
, r->wanted.before, wanted_before_buf
@@ -432,7 +735,7 @@ static bool send_replay_chart_cmd(struct replication_request_details *r, const c
, r->child_db.first_entry_t, r->child_db.last_entry_t
, r->child_db.world_time_t, (r->child_db.world_time_t == r->local_db.now) ? "SAME" : (r->child_db.world_time_t < r->local_db.now) ? "BEHIND" : "AHEAD"
, r->local_db.first_entry_t, r->local_db.last_entry_t
- , r->local_db.last_entry_t_adjusted_to_now?"FIXED":"RAW", r->local_db.now
+ , r->local_db.now
, r->gap.from, r->gap.to
, (r->gap.from == r->wanted.after) ? "FULL" : "PARTIAL"
, (st->replay.after != 0 || st->replay.before != 0) ? "OVERLAPPING" : ""
@@ -459,7 +762,7 @@ static bool send_replay_chart_cmd(struct replication_request_details *r, const c
}
bool replicate_chart_request(send_command callback, void *callback_data, RRDHOST *host, RRDSET *st,
- time_t first_entry_child, time_t last_entry_child, time_t child_world_time,
+ time_t child_first_entry, time_t child_last_entry, time_t child_wall_clock_time,
time_t prev_first_entry_wanted, time_t prev_last_entry_wanted)
{
struct replication_request_details r = {
@@ -472,16 +775,16 @@ bool replicate_chart_request(send_command callback, void *callback_data, RRDHOST
.st = st,
.child_db = {
- .first_entry_t = first_entry_child,
- .last_entry_t = last_entry_child,
- .world_time_t = child_world_time,
+ .first_entry_t = child_first_entry,
+ .last_entry_t = child_last_entry,
+ .wall_clock_time = child_wall_clock_time,
+ .fixed_last_entry = false,
},
.local_db = {
- .first_entry_t = rrdset_first_entry_t(st),
- .last_entry_t = rrdset_last_entry_t(st),
- .last_entry_t_adjusted_to_now = false,
- .now = now_realtime_sec(),
+ .first_entry_t = 0,
+ .last_entry_t = 0,
+ .wall_clock_time = now_realtime_sec(),
},
.last_request = {
@@ -496,12 +799,14 @@ bool replicate_chart_request(send_command callback, void *callback_data, RRDHOST
},
};
- // check our local database retention
- if(r.local_db.last_entry_t > r.local_db.now) {
- r.local_db.last_entry_t = r.local_db.now;
- r.local_db.last_entry_t_adjusted_to_now = true;
+ if(r.child_db.last_entry_t > r.child_db.wall_clock_time) {
+ replicate_log_request(&r, "child's db last entry > child's wall clock time");
+ r.child_db.last_entry_t = r.child_db.wall_clock_time;
+ r.child_db.fixed_last_entry = true;
}
+ rrdset_get_retention_of_tier_for_collected_chart(r.st, &r.local_db.first_entry_t, &r.local_db.last_entry_t, r.local_db.wall_clock_time, 0);
+
// let's find the GAP we have
if(!r.last_request.after || !r.last_request.before) {
// there is no previous request
@@ -511,7 +816,7 @@ bool replicate_chart_request(send_command callback, void *callback_data, RRDHOST
r.gap.from = r.local_db.last_entry_t;
else
// we don't have any data, the gap is the max timeframe we are allowed to replicate
- r.gap.from = r.local_db.now - r.host->rrdpush_seconds_to_replicate;
+ r.gap.from = r.local_db.wall_clock_time - r.host->rrdpush_seconds_to_replicate;
}
else {
@@ -522,27 +827,30 @@ bool replicate_chart_request(send_command callback, void *callback_data, RRDHOST
}
// we want all the data up to now
- r.gap.to = r.local_db.now;
+ r.gap.to = r.local_db.wall_clock_time;
// The gap is now r.gap.from -> r.gap.to
if (unlikely(!rrdhost_option_check(host, RRDHOST_OPTION_REPLICATION)))
- return send_replay_chart_cmd(&r, "empty replication request, replication is disabled");
-
- if (unlikely(!r.child_db.last_entry_t))
- return send_replay_chart_cmd(&r, "empty replication request, child has no stored data");
+ return send_replay_chart_cmd(&r, "empty replication request, replication is disabled", false);
if (unlikely(!rrdset_number_of_dimensions(st)))
- return send_replay_chart_cmd(&r, "empty replication request, chart has no dimensions");
+ return send_replay_chart_cmd(&r, "empty replication request, chart has no dimensions", false);
+
+ if (unlikely(!r.child_db.first_entry_t || !r.child_db.last_entry_t))
+ return send_replay_chart_cmd(&r, "empty replication request, child has no stored data", false);
- if (r.child_db.first_entry_t <= 0)
- return send_replay_chart_cmd(&r, "empty replication request, first entry of the child db first entry is invalid");
+ if (unlikely(r.child_db.first_entry_t < 0 || r.child_db.last_entry_t < 0))
+ return send_replay_chart_cmd(&r, "empty replication request, child db timestamps are invalid", true);
- if (r.child_db.first_entry_t > r.child_db.last_entry_t)
- return send_replay_chart_cmd(&r, "empty replication request, child timings are invalid (first entry > last entry)");
+ if (unlikely(r.child_db.first_entry_t > r.child_db.wall_clock_time))
+ return send_replay_chart_cmd(&r, "empty replication request, child db first entry is after its wall clock time", true);
- if (r.local_db.last_entry_t > r.child_db.last_entry_t)
- return send_replay_chart_cmd(&r, "empty replication request, local last entry is later than the child one");
+ if (unlikely(r.child_db.first_entry_t > r.child_db.last_entry_t))
+ return send_replay_chart_cmd(&r, "empty replication request, child timings are invalid (first entry > last entry)", true);
+
+ if (unlikely(r.local_db.last_entry_t > r.child_db.last_entry_t))
+ return send_replay_chart_cmd(&r, "empty replication request, local last entry is later than the child one", false);
// let's find what the child can provide to fill that gap
@@ -564,15 +872,22 @@ bool replicate_chart_request(send_command callback, void *callback_data, RRDHOST
if(r.wanted.before > r.child_db.last_entry_t)
r.wanted.before = r.child_db.last_entry_t;
- if(r.wanted.after > r.wanted.before)
- r.wanted.after = r.wanted.before;
+ if(r.wanted.after > r.wanted.before) {
+ r.wanted.after = 0;
+ r.wanted.before = 0;
+ r.wanted.start_streaming = true;
+ return send_replay_chart_cmd(&r, "empty replication request, wanted after computed bigger than wanted before", true);
+ }
// the child should start streaming immediately if the wanted duration is small, or we reached the last entry of the child
- r.wanted.start_streaming = (r.local_db.now - r.wanted.after <= host->rrdpush_replication_step || r.wanted.before == r.child_db.last_entry_t);
+ r.wanted.start_streaming = (r.local_db.wall_clock_time - r.wanted.after <= host->rrdpush_replication_step ||
+ r.wanted.before >= r.child_db.last_entry_t ||
+ r.wanted.before >= r.child_db.wall_clock_time ||
+ r.wanted.before >= r.local_db.wall_clock_time);
// the wanted timeframe is now r.wanted.after -> r.wanted.before
// send it
- return send_replay_chart_cmd(&r, "OK");
+ return send_replay_chart_cmd(&r, "OK", false);
}
// ----------------------------------------------------------------------------
@@ -585,13 +900,20 @@ struct replication_request {
STRING *chart_id; // the chart of the request
time_t after; // the start time of the query (maybe zero) key for sorting (JudyL)
time_t before; // the end time of the query (maybe zero)
- bool start_streaming; // true, when the parent wants to send the rest of the data (before is overwritten) and enable normal streaming
usec_t sender_last_flush_ut; // the timestamp of the sender, at the time we indexed this request
Word_t unique_id; // auto-increment, later requests have bigger
- bool found; // used as a result boolean for the find call
+
+ bool start_streaming; // true, when the parent wants to send the rest of the data (before is overwritten) and enable normal streaming
bool indexed_in_judy; // true when the request is indexed in judy
bool not_indexed_buffer_full; // true when the request is not indexed because the sender is full
+ bool not_indexed_preprocessing; // true when the request is not indexed, but it is pending in preprocessing
+
+ // prepare ahead members - preprocessing
+ bool found; // used as a result boolean for the find call
+ bool executed; // used to detect if we have skipped requests while preprocessing
+ RRDSET *st; // caching of the chart during preprocessing
+ struct replication_query *q; // the preprocessing query initialization
};
// replication sort entry in JudyL array
@@ -631,6 +953,7 @@ static struct replication_thread {
struct {
size_t executed; // the number of replication requests executed
size_t latest_first_time; // the 'after' timestamp of the last request we executed
+ size_t memory; // the total memory allocated by replication
} atomic; // access should be with atomic operations
struct {
@@ -663,6 +986,7 @@ static struct replication_thread {
.atomic = {
.executed = 0,
.latest_first_time = 0,
+ .memory = 0,
},
.main_thread = {
.last_executed = 0,
@@ -671,6 +995,10 @@ static struct replication_thread {
},
};
+size_t replication_allocated_memory(void) {
+ return __atomic_load_n(&replication_globals.atomic.memory, __ATOMIC_RELAXED);
+}
+
#define replication_set_latest_first_time(t) __atomic_store_n(&replication_globals.atomic.latest_first_time, t, __ATOMIC_RELAXED)
#define replication_get_latest_first_time() __atomic_load_n(&replication_globals.atomic.latest_first_time, __ATOMIC_RELAXED)
@@ -723,6 +1051,7 @@ static struct replication_sort_entry *replication_sort_entry_create_unsafe(struc
fatal_when_replication_is_not_locked_for_me();
struct replication_sort_entry *rse = mallocz(sizeof(struct replication_sort_entry));
+ __atomic_add_fetch(&replication_globals.atomic.memory, sizeof(struct replication_sort_entry), __ATOMIC_RELAXED);
rrdpush_sender_pending_replication_requests_plus_one(rq->sender);
@@ -734,11 +1063,13 @@ static struct replication_sort_entry *replication_sort_entry_create_unsafe(struc
rq->unique_id = rse->unique_id;
rq->indexed_in_judy = false;
rq->not_indexed_buffer_full = false;
+ rq->not_indexed_preprocessing = false;
return rse;
}
static void replication_sort_entry_destroy(struct replication_sort_entry *rse) {
freez(rse);
+ __atomic_sub_fetch(&replication_globals.atomic.memory, sizeof(struct replication_sort_entry), __ATOMIC_RELAXED);
}
static void replication_sort_entry_add(struct replication_request *rq) {
@@ -747,6 +1078,7 @@ static void replication_sort_entry_add(struct replication_request *rq) {
if(rrdpush_sender_replication_buffer_full_get(rq->sender)) {
rq->indexed_in_judy = false;
rq->not_indexed_buffer_full = true;
+ rq->not_indexed_preprocessing = false;
replication_globals.unsafe.pending_no_room++;
replication_recursive_unlock();
return;
@@ -771,23 +1103,33 @@ static void replication_sort_entry_add(struct replication_request *rq) {
Pvoid_t *inner_judy_ptr;
// find the outer judy entry, using after as key
- inner_judy_ptr = JudyLGet(replication_globals.unsafe.queue.JudyL_array, (Word_t) rq->after, PJE0);
- if(!inner_judy_ptr)
- inner_judy_ptr = JudyLIns(&replication_globals.unsafe.queue.JudyL_array, (Word_t) rq->after, PJE0);
+ size_t mem_before_outer_judyl = JudyLMemUsed(replication_globals.unsafe.queue.JudyL_array);
+ inner_judy_ptr = JudyLIns(&replication_globals.unsafe.queue.JudyL_array, (Word_t) rq->after, PJE0);
+ size_t mem_after_outer_judyl = JudyLMemUsed(replication_globals.unsafe.queue.JudyL_array);
+ if(unlikely(!inner_judy_ptr || inner_judy_ptr == PJERR))
+ fatal("REPLICATION: corrupted outer judyL");
// add it to the inner judy, using unique_id as key
+ size_t mem_before_inner_judyl = JudyLMemUsed(*inner_judy_ptr);
Pvoid_t *item = JudyLIns(inner_judy_ptr, rq->unique_id, PJE0);
+ size_t mem_after_inner_judyl = JudyLMemUsed(*inner_judy_ptr);
+ if(unlikely(!item || item == PJERR))
+ fatal("REPLICATION: corrupted inner judyL");
+
*item = rse;
rq->indexed_in_judy = true;
rq->not_indexed_buffer_full = false;
+ rq->not_indexed_preprocessing = false;
if(!replication_globals.unsafe.first_time_t || rq->after < replication_globals.unsafe.first_time_t)
replication_globals.unsafe.first_time_t = rq->after;
replication_recursive_unlock();
+
+ __atomic_add_fetch(&replication_globals.atomic.memory, (mem_after_inner_judyl - mem_before_inner_judyl) + (mem_after_outer_judyl - mem_before_outer_judyl), __ATOMIC_RELAXED);
}
-static bool replication_sort_entry_unlink_and_free_unsafe(struct replication_sort_entry *rse, Pvoid_t **inner_judy_ppptr) {
+static bool replication_sort_entry_unlink_and_free_unsafe(struct replication_sort_entry *rse, Pvoid_t **inner_judy_ppptr, bool preprocessing) {
fatal_when_replication_is_not_locked_for_me();
bool inner_judy_deleted = false;
@@ -798,19 +1140,30 @@ static bool replication_sort_entry_unlink_and_free_unsafe(struct replication_sor
rrdpush_sender_pending_replication_requests_minus_one(rse->rq->sender);
rse->rq->indexed_in_judy = false;
+ rse->rq->not_indexed_preprocessing = preprocessing;
+
+ size_t memory_saved = 0;
// delete it from the inner judy
+ size_t mem_before_inner_judyl = JudyLMemUsed(**inner_judy_ppptr);
JudyLDel(*inner_judy_ppptr, rse->rq->unique_id, PJE0);
+ size_t mem_after_inner_judyl = JudyLMemUsed(**inner_judy_ppptr);
+ memory_saved = mem_before_inner_judyl - mem_after_inner_judyl;
// if no items left, delete it from the outer judy
if(**inner_judy_ppptr == NULL) {
+ size_t mem_before_outer_judyl = JudyLMemUsed(replication_globals.unsafe.queue.JudyL_array);
JudyLDel(&replication_globals.unsafe.queue.JudyL_array, rse->rq->after, PJE0);
+ size_t mem_after_outer_judyl = JudyLMemUsed(replication_globals.unsafe.queue.JudyL_array);
+ memory_saved += mem_before_outer_judyl - mem_after_outer_judyl;
inner_judy_deleted = true;
}
// free memory
replication_sort_entry_destroy(rse);
+ __atomic_sub_fetch(&replication_globals.atomic.memory, memory_saved, __ATOMIC_RELAXED);
+
return inner_judy_deleted;
}
@@ -826,7 +1179,7 @@ static void replication_sort_entry_del(struct replication_request *rq, bool buff
Pvoid_t *our_item_pptr = JudyLGet(*inner_judy_pptr, rq->unique_id, PJE0);
if (our_item_pptr) {
rse_to_delete = *our_item_pptr;
- replication_sort_entry_unlink_and_free_unsafe(rse_to_delete, &inner_judy_pptr);
+ replication_sort_entry_unlink_and_free_unsafe(rse_to_delete, &inner_judy_pptr, false);
if(buffer_full) {
replication_globals.unsafe.pending_no_room++;
@@ -844,13 +1197,6 @@ static void replication_sort_entry_del(struct replication_request *rq, bool buff
replication_recursive_unlock();
}
-static inline PPvoid_t JudyLFirstOrNext(Pcvoid_t PArray, Word_t * PIndex, bool first) {
- if(unlikely(first))
- return JudyLFirst(PArray, PIndex, PJE0);
-
- return JudyLNext(PArray, PIndex, PJE0);
-}
-
static struct replication_request replication_request_get_first_available() {
Pvoid_t *inner_judy_pptr;
@@ -881,7 +1227,7 @@ static struct replication_request replication_request_get_first_available() {
}
bool find_same_after = true;
- while (!rq_to_return.found && (inner_judy_pptr = JudyLFirstOrNext(replication_globals.unsafe.queue.JudyL_array, &replication_globals.unsafe.queue.after, find_same_after))) {
+ while (!rq_to_return.found && (inner_judy_pptr = JudyLFirstThenNext(replication_globals.unsafe.queue.JudyL_array, &replication_globals.unsafe.queue.after, &find_same_after))) {
Pvoid_t *our_item_pptr;
if(unlikely(round == 2 && replication_globals.unsafe.queue.after > started_after))
@@ -898,14 +1244,11 @@ static struct replication_request replication_request_get_first_available() {
// set the return result to found
rq_to_return.found = true;
- if (replication_sort_entry_unlink_and_free_unsafe(rse, &inner_judy_pptr))
+ if (replication_sort_entry_unlink_and_free_unsafe(rse, &inner_judy_pptr, true))
// we removed the item from the outer JudyL
break;
}
- // call JudyLNext from now on
- find_same_after = false;
-
// prepare for the next iteration on the outer loop
replication_globals.unsafe.queue.unique_id = 0;
}
@@ -945,7 +1288,7 @@ static bool replication_request_conflict_callback(const DICTIONARY_ITEM *item __
replication_recursive_lock();
- if(!rq->indexed_in_judy && rq->not_indexed_buffer_full) {
+ if(!rq->indexed_in_judy && rq->not_indexed_buffer_full && !rq->not_indexed_preprocessing) {
// we can replace this command
internal_error(
true,
@@ -958,7 +1301,7 @@ static bool replication_request_conflict_callback(const DICTIONARY_ITEM *item __
rq->before = rq_new->before;
rq->start_streaming = rq_new->start_streaming;
}
- else if(!rq->indexed_in_judy) {
+ else if(!rq->indexed_in_judy && !rq->not_indexed_preprocessing) {
replication_sort_entry_add(rq);
internal_error(
true,
@@ -1001,55 +1344,57 @@ static void replication_request_delete_callback(const DICTIONARY_ITEM *item __ma
string_freez(rq->chart_id);
}
+static bool sender_is_still_connected_for_this_request(struct replication_request *rq) {
+ return rq->sender_last_flush_ut == rrdpush_sender_get_flush_time(rq->sender);
+};
+
static bool replication_execute_request(struct replication_request *rq, bool workers) {
bool ret = false;
- if(likely(workers))
- worker_is_busy(WORKER_JOB_FIND_CHART);
+ if(!rq->st) {
+ if(likely(workers))
+ worker_is_busy(WORKER_JOB_FIND_CHART);
+
+ rq->st = rrdset_find(rq->sender->host, string2str(rq->chart_id));
+ }
- RRDSET *st = rrdset_find(rq->sender->host, string2str(rq->chart_id));
- if(!st) {
+ if(!rq->st) {
internal_error(true, "REPLAY ERROR: 'host:%s/chart:%s' not found",
rrdhost_hostname(rq->sender->host), string2str(rq->chart_id));
goto cleanup;
}
- if(likely(workers))
- worker_is_busy(WORKER_JOB_QUERYING);
-
netdata_thread_disable_cancelability();
- // send the replication data
- bool start_streaming = replicate_chart_response(
- st->rrdhost, st, rq->start_streaming, rq->after, rq->before);
+ if(!rq->q) {
+ if(likely(workers))
+ worker_is_busy(WORKER_JOB_PREPARE_QUERY);
- netdata_thread_enable_cancelability();
+ rq->q = replication_response_prepare(rq->st, rq->start_streaming, rq->after, rq->before);
+ }
- if(start_streaming && rq->sender_last_flush_ut == rrdpush_sender_get_flush_time(rq->sender)) {
- // enable normal streaming if we have to
- // but only if the sender buffer has not been flushed since we started
+ if(likely(workers))
+ worker_is_busy(WORKER_JOB_QUERYING);
- if(rrdset_flag_check(st, RRDSET_FLAG_SENDER_REPLICATION_IN_PROGRESS)) {
- rrdset_flag_clear(st, RRDSET_FLAG_SENDER_REPLICATION_IN_PROGRESS);
- rrdset_flag_set(st, RRDSET_FLAG_SENDER_REPLICATION_FINISHED);
- rrdhost_sender_replicating_charts_minus_one(st->rrdhost);
+ // send the replication data
+ rq->q->rq = rq;
+ replication_response_execute_and_finalize(
+ rq->q, (size_t)((unsigned long long)rq->sender->host->sender->buffer->max_size * MAX_REPLICATION_MESSAGE_PERCENT_SENDER_BUFFER / 100ULL));
-#ifdef NETDATA_LOG_REPLICATION_REQUESTS
- internal_error(true, "STREAM_SENDER REPLAY: 'host:%s/chart:%s' streaming starts",
- rrdhost_hostname(st->rrdhost), rrdset_id(st));
-#endif
- }
- else
- internal_error(true, "REPLAY ERROR: 'host:%s/chart:%s' received start streaming command, but the chart is not in progress replicating",
- rrdhost_hostname(st->rrdhost), string2str(rq->chart_id));
- }
+ rq->q = NULL;
+ netdata_thread_enable_cancelability();
__atomic_add_fetch(&replication_globals.atomic.executed, 1, __ATOMIC_RELAXED);
ret = true;
cleanup:
+ if(rq->q) {
+ replication_response_cancel_and_finalize(rq->q);
+ rq->q = NULL;
+ }
+
string_freez(rq->chart_id);
worker_is_idle();
return ret;
@@ -1068,6 +1413,7 @@ void replication_add_request(struct sender_state *sender, const char *chart_id,
.sender_last_flush_ut = rrdpush_sender_get_flush_time(sender),
.indexed_in_judy = false,
.not_indexed_buffer_full = false,
+ .not_indexed_preprocessing = false,
};
if(start_streaming && rrdpush_sender_get_buffer_used_percent(sender) <= STREAMING_START_MAX_SENDER_BUFFER_PERCENTAGE_ALLOWED)
@@ -1079,13 +1425,13 @@ void replication_add_request(struct sender_state *sender, const char *chart_id,
void replication_sender_delete_pending_requests(struct sender_state *sender) {
// allow the dictionary destructor to go faster on locks
- replication_recursive_lock();
dictionary_flush(sender->replication.requests);
- replication_recursive_unlock();
}
void replication_init_sender(struct sender_state *sender) {
- sender->replication.requests = dictionary_create(DICT_OPTION_DONT_OVERWRITE_VALUE);
+ sender->replication.requests = dictionary_create_advanced(DICT_OPTION_DONT_OVERWRITE_VALUE | DICT_OPTION_FIXED_SIZE,
+ NULL, sizeof(struct replication_request));
+
dictionary_register_react_callback(sender->replication.requests, replication_request_react_callback, sender);
dictionary_register_conflict_callback(sender->replication.requests, replication_request_conflict_callback, sender);
dictionary_register_delete_callback(sender->replication.requests, replication_request_delete_callback, sender);
@@ -1107,9 +1453,8 @@ void replication_recalculate_buffer_used_ratio_unsafe(struct sender_state *s) {
struct replication_request *rq;
dfe_start_read(s->replication.requests, rq) {
- if(rq->indexed_in_judy && !rq->not_indexed_buffer_full) {
+ if(rq->indexed_in_judy)
replication_sort_entry_del(rq, true);
- }
}
dfe_done(rq);
@@ -1122,9 +1467,8 @@ void replication_recalculate_buffer_used_ratio_unsafe(struct sender_state *s) {
struct replication_request *rq;
dfe_start_read(s->replication.requests, rq) {
- if(!rq->indexed_in_judy && rq->not_indexed_buffer_full) {
+ if(!rq->indexed_in_judy && (rq->not_indexed_buffer_full || rq->not_indexed_preprocessing))
replication_sort_entry_add(rq);
- }
}
dfe_done(rq);
@@ -1214,6 +1558,7 @@ static void replication_initialize_workers(bool master) {
worker_register_job_name(WORKER_JOB_QUERYING, "querying");
worker_register_job_name(WORKER_JOB_DELETE_ENTRY, "dict delete");
worker_register_job_name(WORKER_JOB_FIND_CHART, "find chart");
+ worker_register_job_name(WORKER_JOB_PREPARE_QUERY, "prepare query");
worker_register_job_name(WORKER_JOB_CHECK_CONSISTENCY, "check consistency");
worker_register_job_name(WORKER_JOB_BUFFER_COMMIT, "commit");
worker_register_job_name(WORKER_JOB_CLEANUP, "cleanup");
@@ -1235,24 +1580,137 @@ static void replication_initialize_workers(bool master) {
#define REQUEST_QUEUE_EMPTY (-1)
#define REQUEST_CHART_NOT_FOUND (-2)
-static int replication_execute_next_pending_request(void) {
- worker_is_busy(WORKER_JOB_FIND_NEXT);
- struct replication_request rq = replication_request_get_first_available();
+static int replication_execute_next_pending_request(bool cancel) {
+ static __thread int max_requests_ahead = 0;
+ static __thread struct replication_request *rqs = NULL;
+ static __thread int rqs_last_executed = 0, rqs_last_prepared = 0;
+ static __thread size_t queue_rounds = 0; (void)queue_rounds;
+ struct replication_request *rq;
+
+ if(unlikely(cancel)) {
+ if(rqs) {
+ size_t cancelled = 0;
+ do {
+ if (++rqs_last_executed >= max_requests_ahead)
+ rqs_last_executed = 0;
+
+ rq = &rqs[rqs_last_executed];
+
+ if (rq->q) {
+ internal_fatal(rq->executed, "REPLAY FATAL: query has already been executed!");
+ internal_fatal(!rq->found, "REPLAY FATAL: orphan q in rq");
+
+ replication_response_cancel_and_finalize(rq->q);
+ rq->q = NULL;
+ cancelled++;
+ }
+
+ rq->executed = true;
+ rq->found = false;
+
+ } while (rqs_last_executed != rqs_last_prepared);
+
+ internal_error(true, "REPLICATION: cancelled %zu inflight queries", cancelled);
+ }
+ return REQUEST_QUEUE_EMPTY;
+ }
+
+ if(unlikely(!rqs)) {
+ max_requests_ahead = get_netdata_cpus() / 2;
+
+ if(max_requests_ahead > libuv_worker_threads * 2)
+ max_requests_ahead = libuv_worker_threads * 2;
+
+ if(max_requests_ahead < 2)
+ max_requests_ahead = 2;
+
+ rqs = callocz(max_requests_ahead, sizeof(struct replication_request));
+ __atomic_add_fetch(&replication_buffers_allocated, max_requests_ahead * sizeof(struct replication_request), __ATOMIC_RELAXED);
+ }
+
+ // fill the queue
+ do {
+ if(++rqs_last_prepared >= max_requests_ahead) {
+ rqs_last_prepared = 0;
+ queue_rounds++;
+ }
+
+ internal_fatal(rqs[rqs_last_prepared].q,
+ "REPLAY FATAL: slot is used by query that has not been executed!");
+
+ worker_is_busy(WORKER_JOB_FIND_NEXT);
+ rqs[rqs_last_prepared] = replication_request_get_first_available();
+ rq = &rqs[rqs_last_prepared];
+
+ if(rq->found) {
+ if (!rq->st) {
+ worker_is_busy(WORKER_JOB_FIND_CHART);
+ rq->st = rrdset_find(rq->sender->host, string2str(rq->chart_id));
+ }
+
+ if (rq->st && !rq->q) {
+ worker_is_busy(WORKER_JOB_PREPARE_QUERY);
+ rq->q = replication_response_prepare(rq->st, rq->start_streaming, rq->after, rq->before);
+ }
- if(unlikely(!rq.found)) {
+ rq->executed = false;
+ }
+
+ } while(rq->found && rqs_last_prepared != rqs_last_executed);
+
+ // pick the first usable
+ do {
+ if (++rqs_last_executed >= max_requests_ahead)
+ rqs_last_executed = 0;
+
+ rq = &rqs[rqs_last_executed];
+
+ if(rq->found) {
+ internal_fatal(rq->executed, "REPLAY FATAL: query has already been executed!");
+
+ if (rq->sender_last_flush_ut != rrdpush_sender_get_flush_time(rq->sender)) {
+ // the sender has reconnected since this request was queued,
+ // we can safely throw it away, since the parent will resend it
+ replication_response_cancel_and_finalize(rq->q);
+ rq->executed = true;
+ rq->found = false;
+ rq->q = NULL;
+ }
+ else if (rrdpush_sender_replication_buffer_full_get(rq->sender)) {
+ // the sender buffer is full, so we can ignore this request,
+ // it has already been marked as 'preprocessed' in the dictionary,
+ // and the sender will put it back in when there is
+ // enough room in the buffer for processing replication requests
+ replication_response_cancel_and_finalize(rq->q);
+ rq->executed = true;
+ rq->found = false;
+ rq->q = NULL;
+ }
+ else {
+ // we can execute this,
+ // delete it from the dictionary
+ worker_is_busy(WORKER_JOB_DELETE_ENTRY);
+ dictionary_del(rq->sender->replication.requests, string2str(rq->chart_id));
+ }
+ }
+ else
+ internal_fatal(rq->q, "REPLAY FATAL: slot status says slot is empty, but it has a pending query!");
+
+ } while(!rq->found && rqs_last_executed != rqs_last_prepared);
+
+ if(unlikely(!rq->found)) {
worker_is_idle();
return REQUEST_QUEUE_EMPTY;
}
- // delete the request from the dictionary
- worker_is_busy(WORKER_JOB_DELETE_ENTRY);
- if(!dictionary_del(rq.sender->replication.requests, string2str(rq.chart_id)))
- error("REPLAY ERROR: 'host:%s/chart:%s' failed to be deleted from sender pending charts index",
- rrdhost_hostname(rq.sender->host), string2str(rq.chart_id));
+ replication_set_latest_first_time(rq->after);
- replication_set_latest_first_time(rq.after);
+ bool chart_found = replication_execute_request(rq, true);
+ rq->executed = true;
+ rq->found = false;
+ rq->q = NULL;
- if(unlikely(!replication_execute_request(&rq, true))) {
+ if(unlikely(!chart_found)) {
worker_is_idle();
return REQUEST_CHART_NOT_FOUND;
}
@@ -1262,6 +1720,7 @@ static int replication_execute_next_pending_request(void) {
}
static void replication_worker_cleanup(void *ptr __maybe_unused) {
+ replication_execute_next_pending_request(true);
worker_unregister();
}
@@ -1270,8 +1729,9 @@ static void *replication_worker_thread(void *ptr) {
netdata_thread_cleanup_push(replication_worker_cleanup, ptr);
- while(!netdata_exit) {
- if(unlikely(replication_execute_next_pending_request() == REQUEST_QUEUE_EMPTY)) {
+ while(service_running(SERVICE_REPLICATION)) {
+ if(unlikely(replication_execute_next_pending_request(false) == REQUEST_QUEUE_EMPTY)) {
+ sender_thread_buffer_free();
worker_is_busy(WORKER_JOB_WAIT);
worker_is_idle();
sleep_usec(1 * USEC_PER_SEC);
@@ -1286,13 +1746,17 @@ static void replication_main_cleanup(void *ptr) {
struct netdata_static_thread *static_thread = (struct netdata_static_thread *)ptr;
static_thread->enabled = NETDATA_MAIN_THREAD_EXITING;
+ replication_execute_next_pending_request(true);
+
int threads = (int)replication_globals.main_thread.threads;
for(int i = 0; i < threads ;i++) {
netdata_thread_join(*replication_globals.main_thread.threads_ptrs[i], NULL);
freez(replication_globals.main_thread.threads_ptrs[i]);
+ __atomic_sub_fetch(&replication_buffers_allocated, sizeof(netdata_thread_t), __ATOMIC_RELAXED);
}
freez(replication_globals.main_thread.threads_ptrs);
replication_globals.main_thread.threads_ptrs = NULL;
+ __atomic_sub_fetch(&replication_buffers_allocated, threads * sizeof(netdata_thread_t *), __ATOMIC_RELAXED);
// custom code
worker_unregister();
@@ -1312,10 +1776,14 @@ void *replication_thread_main(void *ptr __maybe_unused) {
if(--threads) {
replication_globals.main_thread.threads = threads;
replication_globals.main_thread.threads_ptrs = mallocz(threads * sizeof(netdata_thread_t *));
+ __atomic_add_fetch(&replication_buffers_allocated, threads * sizeof(netdata_thread_t *), __ATOMIC_RELAXED);
for(int i = 0; i < threads ;i++) {
+ char tag[NETDATA_THREAD_TAG_MAX + 1];
+ snprintfz(tag, NETDATA_THREAD_TAG_MAX, "REPLAY[%d]", i + 2);
replication_globals.main_thread.threads_ptrs[i] = mallocz(sizeof(netdata_thread_t));
- netdata_thread_create(replication_globals.main_thread.threads_ptrs[i], "REPLICATION",
+ __atomic_add_fetch(&replication_buffers_allocated, sizeof(netdata_thread_t), __ATOMIC_RELAXED);
+ netdata_thread_create(replication_globals.main_thread.threads_ptrs[i], tag,
NETDATA_THREAD_OPTION_JOINABLE, replication_worker_thread, NULL);
}
}
@@ -1333,7 +1801,7 @@ void *replication_thread_main(void *ptr __maybe_unused) {
size_t last_executed = 0;
size_t last_sender_resets = 0;
- while(!netdata_exit) {
+ while(service_running(SERVICE_REPLICATION)) {
// statistics
usec_t now_mono_ut = now_monotonic_usec();
@@ -1395,7 +1863,7 @@ void *replication_thread_main(void *ptr __maybe_unused) {
worker_is_idle();
}
- if(unlikely(replication_execute_next_pending_request() == REQUEST_QUEUE_EMPTY)) {
+ if(unlikely(replication_execute_next_pending_request(false) == REQUEST_QUEUE_EMPTY)) {
worker_is_busy(WORKER_JOB_WAIT);
replication_recursive_lock();
@@ -1403,14 +1871,16 @@ void *replication_thread_main(void *ptr __maybe_unused) {
// the timeout also defines now frequently we will traverse all the pending requests
// when the outbound buffers of all senders is full
usec_t timeout;
- if(slow)
+ if(slow) {
// no work to be done, wait for a request to come in
timeout = 1000 * USEC_PER_MS;
+ sender_thread_buffer_free();
+ }
else if(replication_globals.unsafe.pending > 0) {
- if(replication_globals.unsafe.sender_resets == last_sender_resets) {
+ if(replication_globals.unsafe.sender_resets == last_sender_resets)
timeout = 1000 * USEC_PER_MS;
- }
+
else {
// there are pending requests waiting to be executed,
// but none could be executed at this time.
diff --git a/streaming/replication.h b/streaming/replication.h
index 00462cc3..f5b64706 100644
--- a/streaming/replication.h
+++ b/streaming/replication.h
@@ -21,7 +21,7 @@ typedef int (*send_command)(const char *txt, void *data);
bool replicate_chart_request(send_command callback, void *callback_data,
RRDHOST *rh, RRDSET *rs,
- time_t first_entry_child, time_t last_entry_child, time_t child_world_time,
+ time_t child_first_entry, time_t child_last_entry, time_t child_wall_clock_time,
time_t response_first_start_time, time_t response_last_end_time);
void replication_init_sender(struct sender_state *sender);
@@ -30,4 +30,7 @@ void replication_sender_delete_pending_requests(struct sender_state *sender);
void replication_add_request(struct sender_state *sender, const char *chart_id, time_t after, time_t before, bool start_streaming);
void replication_recalculate_buffer_used_ratio_unsafe(struct sender_state *s);
+size_t replication_allocated_memory(void);
+size_t replication_allocated_buffers(void);
+
#endif /* REPLICATION_H */
diff --git a/streaming/rrdpush.c b/streaming/rrdpush.c
index a57f1b08..256fa828 100644
--- a/streaming/rrdpush.c
+++ b/streaming/rrdpush.c
@@ -108,7 +108,7 @@ int rrdpush_init() {
default_rrdpush_seconds_to_replicate = config_get_number(CONFIG_SECTION_DB, "seconds to replicate", default_rrdpush_seconds_to_replicate);
default_rrdpush_replication_step = config_get_number(CONFIG_SECTION_DB, "seconds per replication step", default_rrdpush_replication_step);
- rrdhost_free_orphan_time = config_get_number(CONFIG_SECTION_DB, "cleanup orphan hosts after secs", rrdhost_free_orphan_time);
+ rrdhost_free_orphan_time_s = config_get_number(CONFIG_SECTION_DB, "cleanup orphan hosts after secs", rrdhost_free_orphan_time_s);
#ifdef ENABLE_COMPRESSION
default_compression_enabled = (unsigned int)appconfig_get_boolean(&stream_config, CONFIG_SECTION_STREAM,
@@ -295,40 +295,14 @@ static inline bool rrdpush_send_chart_definition(BUFFER *wb, RRDSET *st) {
rrdsetvar_print_to_streaming_custom_chart_variables(st, wb);
if (stream_has_capability(host->sender, STREAM_CAP_REPLICATION)) {
- time_t first_entry_local = rrdset_first_entry_t_of_tier(st, 0);
- time_t last_entry_local = st->last_updated.tv_sec;
-
- if(unlikely(!last_entry_local))
- last_entry_local = rrdset_last_entry_t(st);
+ time_t db_first_time_t, db_last_time_t;
time_t now = now_realtime_sec();
- if(unlikely(last_entry_local > now)) {
- internal_error(true,
- "RRDSET REPLAY ERROR: 'host:%s/chart:%s' last updated time %ld is in the future, adjusting it to now %ld",
- rrdhost_hostname(st->rrdhost), rrdset_id(st),
- last_entry_local, now);
- last_entry_local = now;
- }
-
- if(unlikely(first_entry_local && last_entry_local && first_entry_local >= last_entry_local)) {
- internal_error(true,
- "RRDSET REPLAY ERROR: 'host:%s/chart:%s' first updated time %ld is equal or bigger than last updated time %ld, adjusting it last updated time - update every",
- rrdhost_hostname(st->rrdhost), rrdset_id(st),
- first_entry_local, last_entry_local);
- first_entry_local = last_entry_local - st->update_every;
- }
-
- if(unlikely(!first_entry_local && last_entry_local)) {
- internal_error(true,
- "RRDSET REPLAY ERROR: 'host:%s/chart:%s' first time %ld, last time %ld, setting both to last time",
- rrdhost_hostname(st->rrdhost), rrdset_id(st),
- first_entry_local, last_entry_local);
- first_entry_local = last_entry_local;
- }
+ rrdset_get_retention_of_tier_for_collected_chart(st, &db_first_time_t, &db_last_time_t, now, 0);
buffer_sprintf(wb, PLUGINSD_KEYWORD_CHART_DEFINITION_END " %llu %llu %llu\n",
- (unsigned long long)first_entry_local,
- (unsigned long long)last_entry_local,
+ (unsigned long long)db_first_time_t,
+ (unsigned long long)db_last_time_t,
(unsigned long long)now);
rrdset_flag_set(st, RRDSET_FLAG_SENDER_REPLICATION_IN_PROGRESS);
@@ -342,17 +316,17 @@ static inline bool rrdpush_send_chart_definition(BUFFER *wb, RRDSET *st) {
#endif
}
- st->upstream_resync_time = st->last_collected_time.tv_sec + (remote_clock_resync_iterations * st->update_every);
+ st->upstream_resync_time_s = st->last_collected_time.tv_sec + (remote_clock_resync_iterations * st->update_every);
return replication_progress;
}
// sends the current chart dimensions
-static void rrdpush_send_chart_metrics(BUFFER *wb, RRDSET *st, struct sender_state *s, RRDSET_FLAGS flags) {
+static void rrdpush_send_chart_metrics(BUFFER *wb, RRDSET *st, struct sender_state *s __maybe_unused, RRDSET_FLAGS flags) {
buffer_fast_strcat(wb, "BEGIN \"", 7);
buffer_fast_strcat(wb, rrdset_id(st), string_strlen(st->id));
buffer_fast_strcat(wb, "\" ", 2);
- if(stream_has_capability(s, STREAM_CAP_REPLICATION) || st->last_collected_time.tv_sec > st->upstream_resync_time)
+ if(st->last_collected_time.tv_sec > st->upstream_resync_time_s)
buffer_print_llu(wb, st->usec_since_last_update);
else
buffer_fast_strcat(wb, "0", 1);
@@ -399,6 +373,7 @@ bool rrdset_push_chart_definition_now(RRDSET *st) {
BUFFER *wb = sender_start(host->sender);
rrdpush_send_chart_definition(wb, st);
sender_commit(host->sender, wb);
+ sender_thread_buffer_free();
return true;
}
@@ -463,6 +438,8 @@ void rrdpush_send_host_labels(RRDHOST *host) {
buffer_sprintf(wb, "OVERWRITE %s\n", "labels");
sender_commit(host->sender, wb);
+
+ sender_thread_buffer_free();
}
void rrdpush_claimed_id(RRDHOST *host)
@@ -480,6 +457,8 @@ void rrdpush_claimed_id(RRDHOST *host)
rrdhost_aclk_state_unlock(host);
sender_commit(host->sender, wb);
+
+ sender_thread_buffer_free();
}
int connect_to_one_of_destinations(
@@ -496,20 +475,11 @@ int connect_to_one_of_destinations(
for (struct rrdpush_destinations *d = host->destinations; d; d = d->next) {
time_t now = now_realtime_sec();
- if(d->postpone_reconnection_until > now) {
- info(
- "STREAM %s: skipping destination '%s' (default port: %d) due to last error (code: %d, %s), will retry it in %d seconds",
- rrdhost_hostname(host),
- string2str(d->destination),
- default_port,
- d->last_handshake, d->last_error?d->last_error:"unset reason description",
- (int)(d->postpone_reconnection_until - now));
-
+ if(d->postpone_reconnection_until > now)
continue;
- }
info(
- "STREAM %s: attempting to connect to '%s' (default port: %d)...",
+ "STREAM %s: connecting to '%s' (default port: %d)...",
rrdhost_hostname(host),
string2str(d->destination),
default_port);
@@ -528,8 +498,8 @@ int connect_to_one_of_destinations(
// move the current item to the end of the list
// without this, this destination will break the loop again and again
// not advancing the destinations to find one that may work
- DOUBLE_LINKED_LIST_REMOVE_UNSAFE(host->destinations, d, prev, next);
- DOUBLE_LINKED_LIST_APPEND_UNSAFE(host->destinations, d, prev, next);
+ DOUBLE_LINKED_LIST_REMOVE_ITEM_UNSAFE(host->destinations, d, prev, next);
+ DOUBLE_LINKED_LIST_APPEND_ITEM_UNSAFE(host->destinations, d, prev, next);
break;
}
@@ -550,7 +520,9 @@ bool destinations_init_add_one(char *entry, void *data) {
struct rrdpush_destinations *d = callocz(1, sizeof(struct rrdpush_destinations));
d->destination = string_strdupz(entry);
- DOUBLE_LINKED_LIST_APPEND_UNSAFE(t->list, d, prev, next);
+ __atomic_add_fetch(&netdata_buffers_statistics.rrdhost_senders, sizeof(struct rrdpush_destinations), __ATOMIC_RELAXED);
+
+ DOUBLE_LINKED_LIST_APPEND_ITEM_UNSAFE(t->list, d, prev, next);
t->count++;
info("STREAM: added streaming destination No %d: '%s' to host '%s'", t->count, string2str(d->destination), rrdhost_hostname(t->host));
@@ -577,9 +549,10 @@ void rrdpush_destinations_init(RRDHOST *host) {
void rrdpush_destinations_free(RRDHOST *host) {
while (host->destinations) {
struct rrdpush_destinations *tmp = host->destinations;
- DOUBLE_LINKED_LIST_REMOVE_UNSAFE(host->destinations, tmp, prev, next);
+ DOUBLE_LINKED_LIST_REMOVE_ITEM_UNSAFE(host->destinations, tmp, prev, next);
string_freez(tmp->destination);
freez(tmp);
+ __atomic_sub_fetch(&netdata_buffers_statistics.rrdhost_senders, sizeof(struct rrdpush_destinations), __ATOMIC_RELAXED);
}
host->destinations = NULL;
@@ -590,25 +563,16 @@ void rrdpush_destinations_free(RRDHOST *host) {
// Either the receiver lost the connection or the host is being destroyed.
// The sender mutex guards thread creation, any spurious data is wiped on reconnection.
-void rrdpush_sender_thread_stop(RRDHOST *host) {
-
+void rrdpush_sender_thread_stop(RRDHOST *host, const char *reason, bool wait) {
if (!host->sender)
return;
netdata_mutex_lock(&host->sender->mutex);
- netdata_thread_t thr = 0;
if(rrdhost_flag_check(host, RRDHOST_FLAG_RRDPUSH_SENDER_SPAWN)) {
- rrdhost_flag_clear(host, RRDHOST_FLAG_RRDPUSH_SENDER_SPAWN);
-
- info("STREAM %s [send]: signaling sending thread to stop...", rrdhost_hostname(host));
-
- // signal the thread that we want to join it
- rrdhost_flag_set(host, RRDHOST_FLAG_RRDPUSH_SENDER_JOIN);
- // copy the thread id, so that we will be waiting for the right one
- // even if a new one has been spawn
- thr = host->rrdpush_sender_thread;
+ host->sender->exit.shutdown = true;
+ host->sender->exit.reason = reason;
// signal it to cancel
netdata_thread_cancel(host->rrdpush_sender_thread);
@@ -616,11 +580,14 @@ void rrdpush_sender_thread_stop(RRDHOST *host) {
netdata_mutex_unlock(&host->sender->mutex);
- if(thr != 0) {
- info("STREAM %s [send]: waiting for the sending thread to stop...", rrdhost_hostname(host));
- void *result;
- netdata_thread_join(thr, &result);
- info("STREAM %s [send]: sending thread has exited.", rrdhost_hostname(host));
+ if(wait) {
+ netdata_mutex_lock(&host->sender->mutex);
+ while(host->sender->tid) {
+ netdata_mutex_unlock(&host->sender->mutex);
+ sleep_usec(10 * USEC_PER_MS);
+ netdata_mutex_lock(&host->sender->mutex);
+ }
+ netdata_mutex_unlock(&host->sender->mutex);
}
}
@@ -638,9 +605,9 @@ static void rrdpush_sender_thread_spawn(RRDHOST *host) {
if(!rrdhost_flag_check(host, RRDHOST_FLAG_RRDPUSH_SENDER_SPAWN)) {
char tag[NETDATA_THREAD_TAG_MAX + 1];
- snprintfz(tag, NETDATA_THREAD_TAG_MAX, "STREAM_SENDER[%s]", rrdhost_hostname(host));
+ snprintfz(tag, NETDATA_THREAD_TAG_MAX, THREAD_TAG_STREAM_SENDER "[%s]", rrdhost_hostname(host));
- if(netdata_thread_create(&host->rrdpush_sender_thread, tag, NETDATA_THREAD_OPTION_JOINABLE, rrdpush_sender_thread, (void *) host->sender))
+ if(netdata_thread_create(&host->rrdpush_sender_thread, tag, NETDATA_THREAD_OPTION_DEFAULT, rrdpush_sender_thread, (void *) host->sender))
error("STREAM %s [send]: failed to create new thread for client.", rrdhost_hostname(host));
else
rrdhost_flag_set(host, RRDHOST_FLAG_RRDPUSH_SENDER_SPAWN);
@@ -654,7 +621,7 @@ int rrdpush_receiver_permission_denied(struct web_client *w) {
// to prevent an attacker from gaining info about the error
buffer_flush(w->response.data);
buffer_sprintf(w->response.data, "You are not permitted to access this. Check the logs for more info.");
- return 401;
+ return HTTP_RESP_UNAUTHORIZED;
}
int rrdpush_receiver_too_busy_now(struct web_client *w) {
@@ -662,21 +629,42 @@ int rrdpush_receiver_too_busy_now(struct web_client *w) {
// to prevent an attacker from gaining info about the error
buffer_flush(w->response.data);
buffer_sprintf(w->response.data, "The server is too busy now to accept this request. Try later.");
- return 503;
+ return HTTP_RESP_SERVICE_UNAVAILABLE;
}
void *rrdpush_receiver_thread(void *ptr);
int rrdpush_receiver_thread_spawn(struct web_client *w, char *url) {
- info("clients wants to STREAM metrics.");
- char *key = NULL, *hostname = NULL, *registry_hostname = NULL, *machine_guid = NULL, *os = "unknown", *timezone = "unknown", *abbrev_timezone = "UTC", *tags = NULL;
- int32_t utc_offset = 0;
- int update_every = default_rrd_update_every;
- uint32_t stream_version = UINT_MAX;
- char buf[GUID_LEN + 1];
+ if(!service_running(ABILITY_STREAMING_CONNECTIONS))
+ return rrdpush_receiver_too_busy_now(w);
+
+ struct receiver_state *rpt = callocz(1, sizeof(*rpt));
+ rpt->last_msg_t = now_realtime_sec();
+ rpt->capabilities = STREAM_CAP_INVALID;
+ rpt->hops = 1;
+
+ __atomic_add_fetch(&netdata_buffers_statistics.rrdhost_receivers, sizeof(*rpt), __ATOMIC_RELAXED);
+ __atomic_add_fetch(&netdata_buffers_statistics.rrdhost_allocations_size, sizeof(struct rrdhost_system_info), __ATOMIC_RELAXED);
+
+ rpt->system_info = callocz(1, sizeof(struct rrdhost_system_info));
+ rpt->system_info->hops = rpt->hops;
+
+ rpt->fd = w->ifd;
+ rpt->client_ip = strdupz(w->client_ip);
+ rpt->client_port = strdupz(w->client_port);
+
+ rpt->config.update_every = default_rrd_update_every;
+
+#ifdef ENABLE_HTTPS
+ rpt->ssl.conn = w->ssl.conn;
+ rpt->ssl.flags = w->ssl.flags;
+
+ w->ssl.conn = NULL;
+ w->ssl.flags = NETDATA_SSL_START;
+#endif
+
+ // parse the parameters and fill rpt and rpt->system_info
- struct rrdhost_system_info *system_info = callocz(1, sizeof(struct rrdhost_system_info));
- system_info->hops = 1;
while(url) {
char *value = mystrsep(&url, "&");
if(!value || !*value) continue;
@@ -685,178 +673,307 @@ int rrdpush_receiver_thread_spawn(struct web_client *w, char *url) {
if(!name || !*name) continue;
if(!value || !*value) continue;
- if(!strcmp(name, "key"))
- key = value;
- else if(!strcmp(name, "hostname"))
- hostname = value;
- else if(!strcmp(name, "registry_hostname"))
- registry_hostname = value;
- else if(!strcmp(name, "machine_guid"))
- machine_guid = value;
+ if(!strcmp(name, "key") && !rpt->key)
+ rpt->key = strdupz(value);
+
+ else if(!strcmp(name, "hostname") && !rpt->hostname)
+ rpt->hostname = strdupz(value);
+
+ else if(!strcmp(name, "registry_hostname") && !rpt->registry_hostname)
+ rpt->registry_hostname = strdupz(value);
+
+ else if(!strcmp(name, "machine_guid") && !rpt->machine_guid)
+ rpt->machine_guid = strdupz(value);
+
else if(!strcmp(name, "update_every"))
- update_every = (int)strtoul(value, NULL, 0);
- else if(!strcmp(name, "os"))
- os = value;
- else if(!strcmp(name, "timezone"))
- timezone = value;
- else if(!strcmp(name, "abbrev_timezone"))
- abbrev_timezone = value;
+ rpt->config.update_every = (int)strtoul(value, NULL, 0);
+
+ else if(!strcmp(name, "os") && !rpt->os)
+ rpt->os = strdupz(value);
+
+ else if(!strcmp(name, "timezone") && !rpt->timezone)
+ rpt->timezone = strdupz(value);
+
+ else if(!strcmp(name, "abbrev_timezone") && !rpt->abbrev_timezone)
+ rpt->abbrev_timezone = strdupz(value);
+
else if(!strcmp(name, "utc_offset"))
- utc_offset = (int32_t)strtol(value, NULL, 0);
+ rpt->utc_offset = (int32_t)strtol(value, NULL, 0);
+
else if(!strcmp(name, "hops"))
- system_info->hops = (uint16_t) strtoul(value, NULL, 0);
+ rpt->hops = rpt->system_info->hops = (uint16_t) strtoul(value, NULL, 0);
+
else if(!strcmp(name, "ml_capable"))
- system_info->ml_capable = strtoul(value, NULL, 0);
+ rpt->system_info->ml_capable = strtoul(value, NULL, 0);
+
else if(!strcmp(name, "ml_enabled"))
- system_info->ml_enabled = strtoul(value, NULL, 0);
+ rpt->system_info->ml_enabled = strtoul(value, NULL, 0);
+
else if(!strcmp(name, "mc_version"))
- system_info->mc_version = strtoul(value, NULL, 0);
- else if(!strcmp(name, "tags"))
- tags = value;
- else if(!strcmp(name, "ver"))
- stream_version = convert_stream_version_to_capabilities(strtoul(value, NULL, 0));
+ rpt->system_info->mc_version = strtoul(value, NULL, 0);
+
+ else if(!strcmp(name, "tags") && !rpt->tags)
+ rpt->tags = strdupz(value);
+
+ else if(!strcmp(name, "ver") && (rpt->capabilities & STREAM_CAP_INVALID))
+ rpt->capabilities = convert_stream_version_to_capabilities(strtoul(value, NULL, 0));
+
else {
// An old Netdata child does not have a compatible streaming protocol, map to something sane.
if (!strcmp(name, "NETDATA_SYSTEM_OS_NAME"))
name = "NETDATA_HOST_OS_NAME";
+
else if (!strcmp(name, "NETDATA_SYSTEM_OS_ID"))
name = "NETDATA_HOST_OS_ID";
+
else if (!strcmp(name, "NETDATA_SYSTEM_OS_ID_LIKE"))
name = "NETDATA_HOST_OS_ID_LIKE";
+
else if (!strcmp(name, "NETDATA_SYSTEM_OS_VERSION"))
name = "NETDATA_HOST_OS_VERSION";
+
else if (!strcmp(name, "NETDATA_SYSTEM_OS_VERSION_ID"))
name = "NETDATA_HOST_OS_VERSION_ID";
+
else if (!strcmp(name, "NETDATA_SYSTEM_OS_DETECTION"))
name = "NETDATA_HOST_OS_DETECTION";
- else if(!strcmp(name, "NETDATA_PROTOCOL_VERSION") && stream_version == UINT_MAX) {
- stream_version = convert_stream_version_to_capabilities(1);
- }
- if (unlikely(rrdhost_set_system_info_variable(system_info, name, value))) {
- info("STREAM [receive from [%s]:%s]: request has parameter '%s' = '%s', which is not used.",
- w->client_ip, w->client_port, name, value);
+ else if(!strcmp(name, "NETDATA_PROTOCOL_VERSION") && (rpt->capabilities & STREAM_CAP_INVALID))
+ rpt->capabilities = convert_stream_version_to_capabilities(1);
+
+ if (unlikely(rrdhost_set_system_info_variable(rpt->system_info, name, value))) {
+ info("STREAM '%s' [receive from [%s]:%s]: "
+ "request has parameter '%s' = '%s', which is not used."
+ , (rpt->hostname && *rpt->hostname) ? rpt->hostname : "-"
+ , rpt->client_ip, rpt->client_port
+ , name, value);
}
}
}
- if (stream_version == UINT_MAX)
- stream_version = convert_stream_version_to_capabilities(0);
+ if (rpt->capabilities & STREAM_CAP_INVALID)
+ // no version is supplied, assume version 0;
+ rpt->capabilities = convert_stream_version_to_capabilities(0);
- if(!key || !*key) {
- rrdhost_system_info_free(system_info);
- log_stream_connection(w->client_ip, w->client_port, (key && *key)?key:"-", (machine_guid && *machine_guid)?machine_guid:"-", (hostname && *hostname)?hostname:"-", "ACCESS DENIED - NO KEY");
- error("STREAM [receive from [%s]:%s]: request without an API key. Forbidding access.", w->client_ip, w->client_port);
- return rrdpush_receiver_permission_denied(w);
+ // find the program name and version
+ if(w->user_agent && w->user_agent[0]) {
+ char *t = strchr(w->user_agent, '/');
+ if(t && *t) {
+ *t = '\0';
+ t++;
+ }
+
+ rpt->program_name = strdupz(w->user_agent);
+ if(t && *t) rpt->program_version = strdupz(t);
}
- if(!hostname || !*hostname) {
- rrdhost_system_info_free(system_info);
- log_stream_connection(w->client_ip, w->client_port, (key && *key)?key:"-", (machine_guid && *machine_guid)?machine_guid:"-", (hostname && *hostname)?hostname:"-", "ACCESS DENIED - NO HOSTNAME");
- error("STREAM [receive from [%s]:%s]: request without a hostname. Forbidding access.", w->client_ip, w->client_port);
+ // check if we should accept this connection
+
+ if(!rpt->key || !*rpt->key) {
+ rrdpush_receive_log_status(
+ rpt,
+ "request without an API key",
+ "NO API KEY PERMISSION DENIED");
+
+ receiver_state_free(rpt);
return rrdpush_receiver_permission_denied(w);
}
- if(!machine_guid || !*machine_guid) {
- rrdhost_system_info_free(system_info);
- log_stream_connection(w->client_ip, w->client_port, (key && *key)?key:"-", (machine_guid && *machine_guid)?machine_guid:"-", (hostname && *hostname)?hostname:"-", "ACCESS DENIED - NO MACHINE GUID");
- error("STREAM [receive from [%s]:%s]: request without a machine GUID. Forbidding access.", w->client_ip, w->client_port);
+ if(!rpt->hostname || !*rpt->hostname) {
+ rrdpush_receive_log_status(
+ rpt,
+ "request without a hostname",
+ "NO HOSTNAME PERMISSION DENIED");
+
+ receiver_state_free(rpt);
return rrdpush_receiver_permission_denied(w);
}
- if(regenerate_guid(key, buf) == -1) {
- rrdhost_system_info_free(system_info);
- log_stream_connection(w->client_ip, w->client_port, key, machine_guid, hostname, "ACCESS DENIED - INVALID KEY");
- error("STREAM [receive from [%s]:%s]: API key '%s' is not valid GUID (use the command uuidgen to generate one). Forbidding access.", w->client_ip, w->client_port, key);
+ if(!rpt->registry_hostname)
+ rpt->registry_hostname = strdupz(rpt->hostname);
+
+ if(!rpt->machine_guid || !*rpt->machine_guid) {
+ rrdpush_receive_log_status(
+ rpt,
+ "request without a machine GUID",
+ "NO MACHINE GUID PERMISSION DENIED");
+
+ receiver_state_free(rpt);
return rrdpush_receiver_permission_denied(w);
}
- if(regenerate_guid(machine_guid, buf) == -1) {
- rrdhost_system_info_free(system_info);
- log_stream_connection(w->client_ip, w->client_port, key, machine_guid, hostname, "ACCESS DENIED - INVALID MACHINE GUID");
- error("STREAM [receive from [%s]:%s]: machine GUID '%s' is not GUID. Forbidding access.", w->client_ip, w->client_port, machine_guid);
- return rrdpush_receiver_permission_denied(w);
+ {
+ char buf[GUID_LEN + 1];
+
+ if (regenerate_guid(rpt->key, buf) == -1) {
+ rrdpush_receive_log_status(
+ rpt,
+ "API key is not a valid UUID (use the command uuidgen to generate one)",
+ "INVALID API KEY PERMISSION DENIED");
+
+ receiver_state_free(rpt);
+ return rrdpush_receiver_permission_denied(w);
+ }
+
+ if (regenerate_guid(rpt->machine_guid, buf) == -1) {
+ rrdpush_receive_log_status(
+ rpt,
+ "machine GUID is not a valid UUID",
+ "INVALID MACHINE GUID PERMISSION DENIED");
+
+ receiver_state_free(rpt);
+ return rrdpush_receiver_permission_denied(w);
+ }
}
- const char *api_key_type = appconfig_get(&stream_config, key, "type", "api");
+ const char *api_key_type = appconfig_get(&stream_config, rpt->key, "type", "api");
if(!api_key_type || !*api_key_type) api_key_type = "unknown";
if(strcmp(api_key_type, "api") != 0) {
- rrdhost_system_info_free(system_info);
- log_stream_connection(w->client_ip, w->client_port, key, machine_guid, hostname, "ACCESS DENIED - API KEY GIVEN IS NOT API KEY");
- error("STREAM [receive from [%s]:%s]: API key '%s' is a %s GUID. Forbidding access.", w->client_ip, w->client_port, key, api_key_type);
+ rrdpush_receive_log_status(
+ rpt,
+ "API key is a machine GUID",
+ "INVALID API KEY PERMISSION DENIED");
+
+ receiver_state_free(rpt);
return rrdpush_receiver_permission_denied(w);
}
- if(!appconfig_get_boolean(&stream_config, key, "enabled", 0)) {
- rrdhost_system_info_free(system_info);
- log_stream_connection(w->client_ip, w->client_port, key, machine_guid, hostname, "ACCESS DENIED - KEY NOT ENABLED");
- error("STREAM [receive from [%s]:%s]: API key '%s' is not allowed. Forbidding access.", w->client_ip, w->client_port, key);
+ if(!appconfig_get_boolean(&stream_config, rpt->key, "enabled", 0)) {
+ rrdpush_receive_log_status(
+ rpt,
+ "API key is not enabled",
+ "API KEY DISABLED PERMISSION DENIED");
+
+ receiver_state_free(rpt);
return rrdpush_receiver_permission_denied(w);
}
{
- SIMPLE_PATTERN *key_allow_from = simple_pattern_create(appconfig_get(&stream_config, key, "allow from", "*"), NULL, SIMPLE_PATTERN_EXACT);
+ SIMPLE_PATTERN *key_allow_from = simple_pattern_create(
+ appconfig_get(&stream_config, rpt->key, "allow from", "*"),
+ NULL, SIMPLE_PATTERN_EXACT);
+
if(key_allow_from) {
if(!simple_pattern_matches(key_allow_from, w->client_ip)) {
simple_pattern_free(key_allow_from);
- rrdhost_system_info_free(system_info);
- log_stream_connection(w->client_ip, w->client_port, key, machine_guid, hostname, "ACCESS DENIED - KEY NOT ALLOWED FROM THIS IP");
- error("STREAM [receive from [%s]:%s]: API key '%s' is not permitted from this IP. Forbidding access.", w->client_ip, w->client_port, key);
+
+ rrdpush_receive_log_status(
+ rpt,
+ "API key is not allowed from this IP",
+ "NOT ALLOWED IP PERMISSION DENIED");
+
+ receiver_state_free(rpt);
return rrdpush_receiver_permission_denied(w);
}
+
simple_pattern_free(key_allow_from);
}
}
- const char *machine_guid_type = appconfig_get(&stream_config, machine_guid, "type", "machine");
- if(!machine_guid_type || !*machine_guid_type) machine_guid_type = "unknown";
- if(strcmp(machine_guid_type, "machine") != 0) {
- rrdhost_system_info_free(system_info);
- log_stream_connection(w->client_ip, w->client_port, key, machine_guid, hostname, "ACCESS DENIED - MACHINE GUID GIVEN IS NOT A MACHINE GUID");
- error("STREAM [receive from [%s]:%s]: machine GUID '%s' is a %s GUID. Forbidding access.", w->client_ip, w->client_port, machine_guid, machine_guid_type);
- return rrdpush_receiver_permission_denied(w);
+ {
+ const char *machine_guid_type = appconfig_get(&stream_config, rpt->machine_guid, "type", "machine");
+ if (!machine_guid_type || !*machine_guid_type) machine_guid_type = "unknown";
+
+ if (strcmp(machine_guid_type, "machine") != 0) {
+ rrdpush_receive_log_status(
+ rpt,
+ "machine GUID is an API key",
+ "INVALID MACHINE GUID PERMISSION DENIED");
+
+ receiver_state_free(rpt);
+ return rrdpush_receiver_permission_denied(w);
+ }
}
- if(!appconfig_get_boolean(&stream_config, machine_guid, "enabled", 1)) {
- rrdhost_system_info_free(system_info);
- log_stream_connection(w->client_ip, w->client_port, key, machine_guid, hostname, "ACCESS DENIED - MACHINE GUID NOT ENABLED");
- error("STREAM [receive from [%s]:%s]: machine GUID '%s' is not allowed. Forbidding access.", w->client_ip, w->client_port, machine_guid);
+ if(!appconfig_get_boolean(&stream_config, rpt->machine_guid, "enabled", 1)) {
+ rrdpush_receive_log_status(
+ rpt,
+ "machine GUID is not enabled",
+ "MACHINE GUID DISABLED PERMISSION DENIED");
+
+ receiver_state_free(rpt);
return rrdpush_receiver_permission_denied(w);
}
{
- SIMPLE_PATTERN *machine_allow_from = simple_pattern_create(appconfig_get(&stream_config, machine_guid, "allow from", "*"), NULL, SIMPLE_PATTERN_EXACT);
+ SIMPLE_PATTERN *machine_allow_from = simple_pattern_create(
+ appconfig_get(&stream_config, rpt->machine_guid, "allow from", "*"),
+ NULL, SIMPLE_PATTERN_EXACT);
+
if(machine_allow_from) {
if(!simple_pattern_matches(machine_allow_from, w->client_ip)) {
simple_pattern_free(machine_allow_from);
- rrdhost_system_info_free(system_info);
- log_stream_connection(w->client_ip, w->client_port, key, machine_guid, hostname, "ACCESS DENIED - MACHINE GUID NOT ALLOWED FROM THIS IP");
- error("STREAM [receive from [%s]:%s]: Machine GUID '%s' is not permitted from this IP. Forbidding access.", w->client_ip, w->client_port, machine_guid);
+
+ rrdpush_receive_log_status(
+ rpt,
+ "machine GUID is not allowed from this IP",
+ "NOT ALLOWED IP PERMISSION DENIED");
+
+ receiver_state_free(rpt);
return rrdpush_receiver_permission_denied(w);
}
+
simple_pattern_free(machine_allow_from);
}
}
+ if (strcmp(rpt->machine_guid, localhost->machine_guid) == 0) {
+
+ rrdpush_receive_log_status(
+ rpt,
+ "machine GUID is my own",
+ "LOCALHOST PERMISSION DENIED");
+
+ char initial_response[HTTP_HEADER_SIZE + 1];
+ snprintfz(initial_response, HTTP_HEADER_SIZE, "%s", START_STREAMING_ERROR_SAME_LOCALHOST);
+
+ if(send_timeout(
+#ifdef ENABLE_HTTPS
+ &rpt->ssl,
+#endif
+ rpt->fd, initial_response, strlen(initial_response), 0, 60) != (ssize_t)strlen(initial_response)) {
+
+ error("STREAM '%s' [receive from [%s]:%s]: "
+ "failed to reply."
+ , rpt->hostname
+ , rpt->client_ip, rpt->client_port
+ );
+ }
+
+ close(rpt->fd);
+ receiver_state_free(rpt);
+ return web_client_socket_is_now_used_for_streaming(w);
+ }
+
if(unlikely(web_client_streaming_rate_t > 0)) {
- static netdata_mutex_t stream_rate_mutex = NETDATA_MUTEX_INITIALIZER;
- static volatile time_t last_stream_accepted_t = 0;
+ static SPINLOCK spinlock = NETDATA_SPINLOCK_INITIALIZER;
+ static time_t last_stream_accepted_t = 0;
- netdata_mutex_lock(&stream_rate_mutex);
time_t now = now_realtime_sec();
+ netdata_spinlock_lock(&spinlock);
if(unlikely(last_stream_accepted_t == 0))
last_stream_accepted_t = now;
if(now - last_stream_accepted_t < web_client_streaming_rate_t) {
- netdata_mutex_unlock(&stream_rate_mutex);
- rrdhost_system_info_free(system_info);
- error("STREAM [receive from [%s]:%s]: too busy to accept new streaming request. Will be allowed in %ld secs.", w->client_ip, w->client_port, (long)(web_client_streaming_rate_t - (now - last_stream_accepted_t)));
+ netdata_spinlock_unlock(&spinlock);
+
+ char msg[100 + 1];
+ snprintfz(msg, 100,
+ "rate limit, will accept new connection in %ld secs",
+ (long)(web_client_streaming_rate_t - (now - last_stream_accepted_t)));
+
+ rrdpush_receive_log_status(
+ rpt,
+ msg,
+ "RATE LIMIT TRY LATER");
+
+ receiver_state_free(rpt);
return rrdpush_receiver_too_busy_now(w);
}
last_stream_accepted_t = now;
- netdata_mutex_unlock(&stream_rate_mutex);
+ netdata_spinlock_unlock(&spinlock);
}
/*
@@ -867,117 +984,85 @@ int rrdpush_receiver_thread_spawn(struct web_client *w, char *url) {
* lock to prevent race-hazard (two threads try to create the host concurrently, one wins and the other does a
* lookup to the now-attached structure).
*/
- struct receiver_state *rpt = callocz(1, sizeof(*rpt));
- rrd_rdlock();
- RRDHOST *host = rrdhost_find_by_guid(machine_guid);
- if (unlikely(host && rrdhost_flag_check(host, RRDHOST_FLAG_ARCHIVED))) /* Ignore archived hosts. */
- host = NULL;
- if (host) {
- rrdhost_wrlock(host);
- netdata_mutex_lock(&host->receiver_lock);
- rrdhost_flag_clear(host, RRDHOST_FLAG_ORPHAN);
- host->senders_disconnected_time = 0;
- if (host->receiver != NULL) {
- time_t age = now_realtime_sec() - host->receiver->last_msg_t;
- if (age > 30) {
- host->receiver->shutdown = 1;
- shutdown(host->receiver->fd, SHUT_RDWR);
- host->receiver = NULL; // Thread holds reference to structure
- info(
- "STREAM %s [receive from [%s]:%s]: multiple connections for same host detected - "
- "existing connection is dead (%"PRId64" sec), accepting new connection.",
- rrdhost_hostname(host),
- w->client_ip,
- w->client_port,
- (int64_t)age);
- }
- else {
- netdata_mutex_unlock(&host->receiver_lock);
- rrdhost_unlock(host);
- rrd_unlock();
- log_stream_connection(w->client_ip, w->client_port, key, host->machine_guid, rrdhost_hostname(host),
- "REJECTED - ALREADY CONNECTED");
- info(
- "STREAM %s [receive from [%s]:%s]: multiple connections for same host detected - "
- "existing connection is active (within last %"PRId64" sec), rejecting new connection.",
- rrdhost_hostname(host),
- w->client_ip,
- w->client_port,
- (int64_t)age);
- // Have not set WEB_CLIENT_FLAG_DONT_CLOSE_SOCKET - caller should clean up
- buffer_flush(w->response.data);
- buffer_strcat(w->response.data, "This GUID is already streaming to this server");
- freez(rpt);
- return 409;
+ {
+ time_t age = 0;
+ bool receiver_stale = false;
+ bool receiver_working = false;
+
+ rrd_rdlock();
+ RRDHOST *host = rrdhost_find_by_guid(rpt->machine_guid);
+ if (unlikely(host && rrdhost_flag_check(host, RRDHOST_FLAG_ARCHIVED))) /* Ignore archived hosts. */
+ host = NULL;
+
+ if (host) {
+ netdata_mutex_lock(&host->receiver_lock);
+ if (host->receiver) {
+ age = now_realtime_sec() - host->receiver->last_msg_t;
+
+ if (age < 30)
+ receiver_working = true;
+ else
+ receiver_stale = true;
}
+ netdata_mutex_unlock(&host->receiver_lock);
}
- host->receiver = rpt;
- netdata_mutex_unlock(&host->receiver_lock);
- rrdhost_unlock(host);
- }
- rrd_unlock();
-
- rpt->last_msg_t = now_realtime_sec();
-
- rpt->host = host;
- rpt->fd = w->ifd;
- rpt->key = strdupz(key);
- rpt->hostname = strdupz(hostname);
- rpt->registry_hostname = strdupz((registry_hostname && *registry_hostname)?registry_hostname:hostname);
- rpt->machine_guid = strdupz(machine_guid);
- rpt->os = strdupz(os);
- rpt->timezone = strdupz(timezone);
- rpt->abbrev_timezone = strdupz(abbrev_timezone);
- rpt->utc_offset = utc_offset;
- rpt->tags = (tags)?strdupz(tags):NULL;
- rpt->client_ip = strdupz(w->client_ip);
- rpt->client_port = strdupz(w->client_port);
- rpt->update_every = update_every;
- rpt->system_info = system_info;
- rpt->capabilities = stream_version;
-#ifdef ENABLE_HTTPS
- rpt->ssl.conn = w->ssl.conn;
- rpt->ssl.flags = w->ssl.flags;
-
- w->ssl.conn = NULL;
- w->ssl.flags = NETDATA_SSL_START;
-#endif
-
- if(w->user_agent && w->user_agent[0]) {
- char *t = strchr(w->user_agent, '/');
- if(t && *t) {
- *t = '\0';
- t++;
+ rrd_unlock();
+
+ if (receiver_stale && stop_streaming_receiver(host, "STALE RECEIVER")) {
+ // we stopped the receiver
+ // we can proceed with this connection
+ receiver_stale = false;
+
+ info("STREAM '%s' [receive from [%s]:%s]: "
+ "stopped previous stale receiver to accept this one."
+ , rpt->hostname
+ , rpt->client_ip, rpt->client_port
+ );
}
- rpt->program_name = strdupz(w->user_agent);
- if(t && *t) rpt->program_version = strdupz(t);
+ if (receiver_working || receiver_stale) {
+ // another receiver is already connected
+ // try again later
+
+ char msg[200 + 1];
+ snprintfz(msg, 200,
+ "multiple connections for same host, "
+ "old connection was used %ld secs ago%s",
+ age, receiver_stale ? " (signaled old receiver to stop)" : " (new connection not accepted)");
+
+ rrdpush_receive_log_status(
+ rpt,
+ msg,
+ "ALREADY CONNECTED");
+
+ // Have not set WEB_CLIENT_FLAG_DONT_CLOSE_SOCKET - caller should clean up
+ buffer_flush(w->response.data);
+ buffer_strcat(w->response.data, "This GUID is already streaming to this server");
+ receiver_state_free(rpt);
+ return HTTP_RESP_CONFLICT;
+ }
}
-
-
debug(D_SYSTEM, "starting STREAM receive thread.");
char tag[FILENAME_MAX + 1];
- snprintfz(tag, FILENAME_MAX, "STREAM_RECEIVER[%s,[%s]:%s]", rpt->hostname, w->client_ip, w->client_port);
-
- if(netdata_thread_create(&rpt->thread, tag, NETDATA_THREAD_OPTION_DEFAULT, rrdpush_receiver_thread, (void *)rpt))
- error("Failed to create new STREAM receive thread for client.");
-
- // prevent the caller from closing the streaming socket
- if(web_server_mode == WEB_SERVER_MODE_STATIC_THREADED) {
- web_client_flag_set(w, WEB_CLIENT_FLAG_DONT_CLOSE_SOCKET);
- }
- else {
- if(w->ifd == w->ofd)
- w->ifd = w->ofd = -1;
- else
- w->ifd = -1;
+ snprintfz(tag, FILENAME_MAX, THREAD_TAG_STREAM_RECEIVER "[%s,[%s]:%s]", rpt->hostname, w->client_ip, w->client_port);
+
+ if(netdata_thread_create(&rpt->thread, tag, NETDATA_THREAD_OPTION_DEFAULT, rrdpush_receiver_thread, (void *)rpt)) {
+ rrdpush_receive_log_status(
+ rpt,
+ "can't create receiver thread",
+ "INTERNAL SERVER ERROR");
+
+ buffer_flush(w->response.data);
+ buffer_strcat(w->response.data, "Can't handle this request");
+ receiver_state_free(rpt);
+ return HTTP_RESP_INTERNAL_SERVER_ERROR;
}
- buffer_flush(w->response.data);
- return 200;
+ // prevent the caller from closing the streaming socket
+ return web_client_socket_is_now_used_for_streaming(w);
}
static void stream_capabilities_to_string(BUFFER *wb, STREAM_CAPABILITIES caps) {
@@ -995,7 +1080,7 @@ static void stream_capabilities_to_string(BUFFER *wb, STREAM_CAPABILITIES caps)
}
void log_receiver_capabilities(struct receiver_state *rpt) {
- BUFFER *wb = buffer_create(100);
+ BUFFER *wb = buffer_create(100, NULL);
stream_capabilities_to_string(wb, rpt->capabilities);
info("STREAM %s [receive from [%s]:%s]: established link with negotiated capabilities: %s",
@@ -1005,7 +1090,7 @@ void log_receiver_capabilities(struct receiver_state *rpt) {
}
void log_sender_capabilities(struct sender_state *s) {
- BUFFER *wb = buffer_create(100);
+ BUFFER *wb = buffer_create(100, NULL);
stream_capabilities_to_string(wb, s->capabilities);
info("STREAM %s [send to %s]: established link with negotiated capabilities: %s",
diff --git a/streaming/rrdpush.h b/streaming/rrdpush.h
index a0c7e8de..94c1320e 100644
--- a/streaming/rrdpush.h
+++ b/streaming/rrdpush.h
@@ -3,12 +3,14 @@
#ifndef NETDATA_RRDPUSH_H
#define NETDATA_RRDPUSH_H 1
-#include "database/rrd.h"
#include "libnetdata/libnetdata.h"
-#include "web/server/web_client.h"
#include "daemon/common.h"
+#include "web/server/web_client.h"
+#include "database/rrd.h"
#define CONNECTED_TO_SIZE 100
+#define CBUFFER_INITIAL_SIZE (16 * 1024)
+#define THREAD_BUFFER_INITIAL_SIZE (CBUFFER_INITIAL_SIZE / 2)
// ----------------------------------------------------------------------------
// obsolete versions - do not use anymore
@@ -22,6 +24,9 @@
typedef enum {
// do not use the first 3 bits
+ // they used to be versions 1, 2 and 3
+ // before we introduce capabilities
+
STREAM_CAP_V1 = (1 << 3), // v1 = the oldest protocol
STREAM_CAP_V2 = (1 << 4), // v2 = the second version of the protocol (with host labels)
STREAM_CAP_VN = (1 << 5), // version negotiation supported (for versions 3, 4, 5 of the protocol)
@@ -37,6 +42,7 @@ typedef enum {
STREAM_CAP_REPLICATION = (1 << 12), // replication supported
STREAM_CAP_BINARY = (1 << 13), // streaming supports binary data
+ STREAM_CAP_INVALID = (1 << 30), // used as an invalid value for capabilities when this is set
// this must be signed int, so don't use the last bit
// needed for negotiating errors between parent and child
} STREAM_CAPABILITIES;
@@ -125,8 +131,8 @@ struct decompressor_state {
// Metric transmission: collector threads asynchronously fill the buffer, sender thread uses it.
typedef enum {
- SENDER_FLAG_OVERFLOW = (1 << 0), // The buffer has been overflown
- SENDER_FLAG_COMPRESSION = (1 << 1), // The stream needs to have and has compression
+ SENDER_FLAG_OVERFLOW = (1 << 0), // The buffer has been overflown
+ SENDER_FLAG_COMPRESSION = (1 << 1), // The stream needs to have and has compression
} SENDER_FLAGS;
struct sender_state {
@@ -155,6 +161,8 @@ struct sender_state {
int rrdpush_sender_pipe[2]; // collector to sender thread signaling
int rrdpush_sender_socket;
+ uint16_t hops;
+
#ifdef ENABLE_COMPRESSION
struct compressor_state *compressor;
#endif
@@ -163,6 +171,11 @@ struct sender_state {
#endif
struct {
+ bool shutdown;
+ const char *reason;
+ } exit;
+
+ struct {
DICTIONARY *requests; // de-duplication of replication requests, per chart
struct {
@@ -176,9 +189,13 @@ struct sender_state {
struct {
size_t buffer_used_percentage; // the current utilization of the sending buffer
usec_t last_flush_time_ut; // the last time the sender flushed the sending buffer in USEC
+ time_t last_buffer_recreate_s; // true when the sender buffer should be re-created
} atomic;
};
+#define rrdpush_sender_last_buffer_recreate_get(sender) __atomic_load_n(&(sender)->atomic.last_buffer_recreate_s, __ATOMIC_RELAXED)
+#define rrdpush_sender_last_buffer_recreate_set(sender, value) __atomic_store_n(&(sender)->atomic.last_buffer_recreate_s, value, __ATOMIC_RELAXED)
+
#define rrdpush_sender_replication_buffer_full_set(sender, value) __atomic_store_n(&((sender)->replication.atomic.reached_max), value, __ATOMIC_SEQ_CST)
#define rrdpush_sender_replication_buffer_full_get(sender) __atomic_load_n(&((sender)->replication.atomic.reached_max), __ATOMIC_SEQ_CST)
@@ -216,13 +233,34 @@ struct receiver_state {
char *program_name; // Duplicated in pluginsd
char *program_version;
struct rrdhost_system_info *system_info;
- int update_every;
STREAM_CAPABILITIES capabilities;
time_t last_msg_t;
char read_buffer[PLUGINSD_LINE_MAX + 1];
int read_len;
- unsigned int shutdown:1; // Tell the thread to exit
- unsigned int exited; // Indicates that the thread has exited (NOT A BITFIELD!)
+
+ uint16_t hops;
+
+ struct {
+ bool shutdown; // signal the streaming parser to exit
+ const char *reason; // the reason of disconnection to log
+ } exit;
+
+ struct {
+ RRD_MEMORY_MODE mode;
+ int history;
+ int update_every;
+ int health_enabled; // CONFIG_BOOLEAN_YES, CONFIG_BOOLEAN_NO, CONFIG_BOOLEAN_AUTO
+ time_t alarms_delay;
+ int rrdpush_enabled;
+ char *rrdpush_api_key; // DONT FREE - it is allocated in appconfig
+ char *rrdpush_send_charts_matching; // DONT FREE - it is allocated in appconfig
+ bool rrdpush_enable_replication;
+ time_t rrdpush_seconds_to_replicate;
+ time_t rrdpush_replication_step;
+ char *rrdpush_destination; // DONT FREE - it is allocated in appconfig
+ unsigned int rrdpush_compression;
+ } config;
+
#ifdef ENABLE_HTTPS
struct netdata_ssl ssl;
#endif
@@ -260,11 +298,8 @@ extern unsigned int remote_clock_resync_iterations;
void rrdpush_destinations_init(RRDHOST *host);
void rrdpush_destinations_free(RRDHOST *host);
-void sender_init(RRDHOST *host);
-
BUFFER *sender_start(struct sender_state *s);
void sender_commit(struct sender_state *s, BUFFER *wb);
-void sender_cancel(struct sender_state *s);
int rrdpush_init();
bool rrdpush_receiver_needs_dbengine();
int configured_as_parent();
@@ -274,8 +309,11 @@ void *rrdpush_sender_thread(void *ptr);
void rrdpush_send_host_labels(RRDHOST *host);
void rrdpush_claimed_id(RRDHOST *host);
+#define THREAD_TAG_STREAM_RECEIVER "RCVR" // "[host]" is appended
+#define THREAD_TAG_STREAM_SENDER "SNDR" // "[host]" is appended
+
int rrdpush_receiver_thread_spawn(struct web_client *w, char *url);
-void rrdpush_sender_thread_stop(RRDHOST *host);
+void rrdpush_sender_thread_stop(RRDHOST *host, const char *reason, bool wait);
void rrdpush_sender_send_this_host_variable_now(RRDHOST *host, const RRDVAR_ACQUIRED *rva);
void log_stream_connection(const char *client_ip, const char *client_port, const char *api_key, const char *machine_guid, const char *host, const char *msg);
@@ -295,11 +333,17 @@ struct compressor_state *create_compressor();
struct decompressor_state *create_decompressor();
#endif
+void rrdpush_receive_log_status(struct receiver_state *rpt, const char *msg, const char *status);
void log_receiver_capabilities(struct receiver_state *rpt);
void log_sender_capabilities(struct sender_state *s);
STREAM_CAPABILITIES convert_stream_version_to_capabilities(int32_t version);
int32_t stream_capabilities_to_vn(uint32_t caps);
+void receiver_state_free(struct receiver_state *rpt);
+bool stop_streaming_receiver(RRDHOST *host, const char *reason);
+
+void sender_thread_buffer_free(void);
+
#include "replication.h"
#endif //NETDATA_RRDPUSH_H
diff --git a/streaming/sender.c b/streaming/sender.c
index 62097e39..854b57fc 100644
--- a/streaming/sender.c
+++ b/streaming/sender.c
@@ -36,36 +36,41 @@ extern char *netdata_ssl_ca_file;
static __thread BUFFER *sender_thread_buffer = NULL;
static __thread bool sender_thread_buffer_used = false;
+static __thread time_t sender_thread_buffer_last_reset_s = 0;
void sender_thread_buffer_free(void) {
- if(sender_thread_buffer) {
- buffer_free(sender_thread_buffer);
- sender_thread_buffer = NULL;
- }
+ buffer_free(sender_thread_buffer);
+ sender_thread_buffer = NULL;
+ sender_thread_buffer_used = false;
}
// Collector thread starting a transmission
-BUFFER *sender_start(struct sender_state *s __maybe_unused) {
- if(!sender_thread_buffer)
- sender_thread_buffer = buffer_create(1024);
-
- if(sender_thread_buffer_used)
+BUFFER *sender_start(struct sender_state *s) {
+ if(unlikely(sender_thread_buffer_used))
fatal("STREAMING: thread buffer is used multiple times concurrently.");
+ if(unlikely(rrdpush_sender_last_buffer_recreate_get(s) > sender_thread_buffer_last_reset_s)) {
+ if(unlikely(sender_thread_buffer && sender_thread_buffer->size > THREAD_BUFFER_INITIAL_SIZE)) {
+ buffer_free(sender_thread_buffer);
+ sender_thread_buffer = NULL;
+ }
+ }
+
+ if(unlikely(!sender_thread_buffer)) {
+ sender_thread_buffer = buffer_create(THREAD_BUFFER_INITIAL_SIZE, &netdata_buffers_statistics.buffers_streaming);
+ sender_thread_buffer_last_reset_s = rrdpush_sender_last_buffer_recreate_get(s);
+ }
+
sender_thread_buffer_used = true;
buffer_flush(sender_thread_buffer);
return sender_thread_buffer;
}
-void sender_cancel(struct sender_state *s __maybe_unused) {
- sender_thread_buffer_used = false;
-}
-
static inline void rrdpush_sender_thread_close_socket(RRDHOST *host);
#ifdef ENABLE_COMPRESSION
/*
-* In case of stream compression buffer oveflow
+* In case of stream compression buffer overflow
* Inform the user through the error log file and
* deactivate compression by downgrading the stream protocol.
*/
@@ -99,11 +104,11 @@ void sender_commit(struct sender_state *s, BUFFER *wb) {
netdata_mutex_lock(&s->mutex);
- if(unlikely(s->host->sender->buffer->max_size < (src_len + 1) * SENDER_BUFFER_ADAPT_TO_TIMES_MAX_SIZE)) {
+ if(unlikely(s->buffer->max_size < (src_len + 1) * SENDER_BUFFER_ADAPT_TO_TIMES_MAX_SIZE)) {
info("STREAM %s [send to %s]: max buffer size of %zu is too small for a data message of size %zu. Increasing the max buffer size to %d times the max data message size.",
- rrdhost_hostname(s->host), s->connected_to, s->host->sender->buffer->max_size, buffer_strlen(wb) + 1, SENDER_BUFFER_ADAPT_TO_TIMES_MAX_SIZE);
+ rrdhost_hostname(s->host), s->connected_to, s->buffer->max_size, buffer_strlen(wb) + 1, SENDER_BUFFER_ADAPT_TO_TIMES_MAX_SIZE);
- s->host->sender->buffer->max_size = (src_len + 1) * SENDER_BUFFER_ADAPT_TO_TIMES_MAX_SIZE;
+ s->buffer->max_size = (src_len + 1) * SENDER_BUFFER_ADAPT_TO_TIMES_MAX_SIZE;
}
#ifdef ENABLE_COMPRESSION
@@ -150,17 +155,17 @@ void sender_commit(struct sender_state *s, BUFFER *wb) {
}
}
- if(cbuffer_add_unsafe(s->host->sender->buffer, dst, dst_len))
+ if(cbuffer_add_unsafe(s->buffer, dst, dst_len))
s->flags |= SENDER_FLAG_OVERFLOW;
src = src + size_to_compress;
src_len -= size_to_compress;
}
}
- else if(cbuffer_add_unsafe(s->host->sender->buffer, src, src_len))
+ else if(cbuffer_add_unsafe(s->buffer, src, src_len))
s->flags |= SENDER_FLAG_OVERFLOW;
#else
- if(cbuffer_add_unsafe(s->host->sender->buffer, src, src_len))
+ if(cbuffer_add_unsafe(s->buffer, src, src_len))
s->flags |= SENDER_FLAG_OVERFLOW;
#endif
@@ -186,6 +191,7 @@ void rrdpush_sender_send_this_host_variable_now(RRDHOST *host, const RRDVAR_ACQU
BUFFER *wb = sender_start(host->sender);
rrdpush_sender_add_host_variable_to_buffer(wb, rva);
sender_commit(host->sender, wb);
+ sender_thread_buffer_free();
}
}
@@ -214,6 +220,7 @@ static void rrdpush_sender_thread_send_custom_host_variables(RRDHOST *host) {
int ret = rrdvar_walkthrough_read(host->rrdvars, rrdpush_sender_thread_custom_host_variables_callback, &tmp);
(void)ret;
sender_commit(host->sender, wb);
+ sender_thread_buffer_free();
debug(D_STREAM, "RRDVAR sent %d VARIABLES", ret);
}
@@ -222,14 +229,12 @@ static void rrdpush_sender_thread_send_custom_host_variables(RRDHOST *host) {
// resets all the chart, so that their definitions
// will be resent to the central netdata
static void rrdpush_sender_thread_reset_all_charts(RRDHOST *host) {
- error("Clearing stream_collected_metrics flag in charts of host %s", rrdhost_hostname(host));
-
RRDSET *st;
rrdset_foreach_read(st, host) {
rrdset_flag_clear(st, RRDSET_FLAG_UPSTREAM_EXPOSED | RRDSET_FLAG_SENDER_REPLICATION_IN_PROGRESS);
rrdset_flag_set(st, RRDSET_FLAG_SENDER_REPLICATION_FINISHED);
- st->upstream_resync_time = 0;
+ st->upstream_resync_time_s = 0;
RRDDIM *rd;
rrddim_foreach_read(rd, st)
@@ -241,6 +246,30 @@ static void rrdpush_sender_thread_reset_all_charts(RRDHOST *host) {
rrdhost_sender_replicating_charts_zero(host);
}
+static void rrdpush_sender_cbuffer_recreate_timed(struct sender_state *s, time_t now_s, bool have_mutex, bool force) {
+ static __thread time_t last_reset_time_s = 0;
+
+ if(!force && now_s - last_reset_time_s < 300)
+ return;
+
+ if(!have_mutex)
+ netdata_mutex_lock(&s->mutex);
+
+ rrdpush_sender_last_buffer_recreate_set(s, now_s);
+ last_reset_time_s = now_s;
+
+ if(s->buffer && s->buffer->size > CBUFFER_INITIAL_SIZE) {
+ size_t max = s->buffer->max_size;
+ cbuffer_free(s->buffer);
+ s->buffer = cbuffer_new(CBUFFER_INITIAL_SIZE, max, &netdata_buffers_statistics.cbuffers_streaming);
+ }
+
+ sender_thread_buffer_free();
+
+ if(!have_mutex)
+ netdata_mutex_unlock(&s->mutex);
+}
+
static void rrdpush_sender_cbuffer_flush(RRDHOST *host) {
rrdpush_sender_set_flush_time(host->sender);
@@ -248,6 +277,7 @@ static void rrdpush_sender_cbuffer_flush(RRDHOST *host) {
// flush the output buffer from any data it may have
cbuffer_flush(host->sender->buffer);
+ rrdpush_sender_cbuffer_recreate_timed(host->sender, now_monotonic_sec(), true, true);
replication_recalculate_buffer_used_ratio_unsafe(host->sender);
netdata_mutex_unlock(&host->sender->mutex);
@@ -268,6 +298,9 @@ static void rrdpush_sender_charts_and_replication_reset(RRDHOST *host) {
static void rrdpush_sender_on_connect(RRDHOST *host) {
rrdpush_sender_cbuffer_flush(host);
rrdpush_sender_charts_and_replication_reset(host);
+}
+
+static void rrdpush_sender_after_connect(RRDHOST *host) {
rrdpush_sender_thread_send_custom_host_variables(host);
}
@@ -418,13 +451,17 @@ static inline bool rrdpush_sender_validate_response(RRDHOST *host, struct sender
return true;
}
- error("STREAM %s [send to %s]: %s.", rrdhost_hostname(host), s->connected_to, error);
-
worker_is_busy(worker_job_id);
rrdpush_sender_thread_close_socket(host);
host->destination->last_error = error;
host->destination->last_handshake = version;
host->destination->postpone_reconnection_until = now_realtime_sec() + delay;
+
+ char buf[LOG_DATE_LENGTH];
+ log_date(buf, LOG_DATE_LENGTH, host->destination->postpone_reconnection_until);
+ error("STREAM %s [send to %s]: %s - will retry in %ld secs, at %s",
+ rrdhost_hostname(host), s->connected_to, error, delay, buf);
+
return false;
}
@@ -449,11 +486,11 @@ static bool rrdpush_sender_thread_connect_to_parent(RRDHOST *host, int default_p
);
if(unlikely(s->rrdpush_sender_socket == -1)) {
- error("STREAM %s [send to %s]: could not connect to parent node at this time.", rrdhost_hostname(host), host->rrdpush_send_destination);
+ // error("STREAM %s [send to %s]: could not connect to parent node at this time.", rrdhost_hostname(host), host->rrdpush_send_destination);
return false;
}
- info("STREAM %s [send to %s]: initializing communication...", rrdhost_hostname(host), s->connected_to);
+ // info("STREAM %s [send to %s]: initializing communication...", rrdhost_hostname(host), s->connected_to);
#ifdef ENABLE_HTTPS
if(netdata_ssl_client_ctx){
@@ -499,6 +536,8 @@ static bool rrdpush_sender_thread_connect_to_parent(RRDHOST *host, int default_p
stream_encoded_t se;
rrdpush_encode_variable(&se, host);
+ host->sender->hops = host->system_info->hops + 1;
+
char http[HTTP_HEADER_SIZE + 1];
int eol = snprintfz(http, HTTP_HEADER_SIZE,
"STREAM "
@@ -557,7 +596,7 @@ static bool rrdpush_sender_thread_connect_to_parent(RRDHOST *host, int default_p
, rrdhost_timezone(host)
, rrdhost_abbrev_timezone(host)
, host->utc_offset
- , host->system_info->hops + 1
+ , host->sender->hops
, host->system_info->ml_capable
, host->system_info->ml_enabled
, host->system_info->mc_version
@@ -657,7 +696,7 @@ static bool rrdpush_sender_thread_connect_to_parent(RRDHOST *host, int default_p
return false;
}
- info("STREAM %s [send to %s]: waiting response from remote netdata...", rrdhost_hostname(host), s->connected_to);
+ // info("STREAM %s [send to %s]: waiting response from remote netdata...", rrdhost_hostname(host), s->connected_to);
bytes = recv_timeout(
#ifdef ENABLE_HTTPS
@@ -726,6 +765,8 @@ static bool attempt_to_connect(struct sender_state *state)
// let the data collection threads know we are ready
rrdhost_flag_set(state->host, RRDHOST_FLAG_RRDPUSH_SENDER_CONNECTED);
+ rrdpush_sender_after_connect(state->host);
+
return true;
}
@@ -738,7 +779,13 @@ static bool attempt_to_connect(struct sender_state *state)
state->sent_bytes_on_this_connection = 0;
// slow re-connection on repeating errors
- sleep_usec(USEC_PER_SEC * state->reconnect_delay); // seconds
+ usec_t now_ut = now_monotonic_usec();
+ usec_t end_ut = now_ut + USEC_PER_SEC * state->reconnect_delay;
+ while(now_ut < end_ut) {
+ netdata_thread_testcancel();
+ sleep_usec(500 * USEC_PER_MS); // seconds
+ now_ut = now_monotonic_usec();
+ }
return false;
}
@@ -757,8 +804,8 @@ static ssize_t attempt_to_send(struct sender_state *s) {
debug(D_STREAM, "STREAM: Sending data. Buffer r=%zu w=%zu s=%zu, next chunk=%zu", cb->read, cb->write, cb->size, outstanding);
#ifdef ENABLE_HTTPS
- SSL *conn = s->host->sender->ssl.conn ;
- if(conn && s->host->sender->ssl.flags == NETDATA_SSL_HANDSHAKE_COMPLETE)
+ SSL *conn = s->ssl.conn ;
+ if(conn && s->ssl.flags == NETDATA_SSL_HANDSHAKE_COMPLETE)
ret = netdata_ssl_write(conn, chunk, outstanding);
else
ret = send(s->rrdpush_sender_socket, chunk, outstanding, MSG_DONTWAIT);
@@ -793,16 +840,18 @@ static ssize_t attempt_read(struct sender_state *s) {
ssize_t ret = 0;
#ifdef ENABLE_HTTPS
- if (s->host->sender->ssl.conn && s->host->sender->ssl.flags == NETDATA_SSL_HANDSHAKE_COMPLETE) {
+ if (s->ssl.conn && s->ssl.flags == NETDATA_SSL_HANDSHAKE_COMPLETE) {
size_t desired = sizeof(s->read_buffer) - s->read_len - 1;
- ret = netdata_ssl_read(s->host->sender->ssl.conn, s->read_buffer, desired);
+ ret = netdata_ssl_read(s->ssl.conn, s->read_buffer, desired);
if (ret > 0 ) {
s->read_len += (int)ret;
return ret;
}
- worker_is_busy(WORKER_SENDER_JOB_DISCONNECT_SSL_ERROR);
- rrdpush_sender_thread_close_socket(s->host);
+ if (ret == -1) {
+ worker_is_busy(WORKER_SENDER_JOB_DISCONNECT_SSL_ERROR);
+ rrdpush_sender_thread_close_socket(s->host);
+ }
return ret;
}
#endif
@@ -852,6 +901,7 @@ void stream_execute_function_callback(BUFFER *func_wb, int code, void *data) {
pluginsd_function_result_end_to_buffer(wb);
sender_commit(s, wb);
+ sender_thread_buffer_free();
internal_error(true, "STREAM %s [send to %s] FUNCTION transaction %s sending back response (%zu bytes, %llu usec).",
rrdhost_hostname(s->host), s->connected_to,
@@ -876,7 +926,7 @@ void execute_commands(struct sender_state *s) {
log_access("STREAM: %d from '%s' for host '%s': %s",
gettid(), s->connected_to, rrdhost_hostname(s->host), start);
- internal_error(true, "STREAM %s [send to %s] received command over connection: %s", rrdhost_hostname(s->host), s->connected_to, start);
+ // internal_error(true, "STREAM %s [send to %s] received command over connection: %s", rrdhost_hostname(s->host), s->connected_to, start);
char *words[PLUGINSD_MAX_WORDS] = { NULL };
size_t num_words = pluginsd_split_words(start, words, PLUGINSD_MAX_WORDS, NULL, NULL, 0);
@@ -906,7 +956,7 @@ void execute_commands(struct sender_state *s) {
tmp->received_ut = now_realtime_usec();
tmp->sender = s;
tmp->transaction = string_strdupz(transaction);
- BUFFER *wb = buffer_create(PLUGINSD_LINE_MAX + 1);
+ BUFFER *wb = buffer_create(PLUGINSD_LINE_MAX + 1, &netdata_buffers_statistics.buffers_functions);
int code = rrd_call_function_async(s->host, wb, timeout, function, stream_execute_function_callback, tmp);
if(code != HTTP_RESP_OK) {
@@ -1019,59 +1069,83 @@ void rrdpush_signal_sender_to_wake_up(struct sender_state *s) {
}
}
-static void rrdpush_sender_thread_cleanup_callback(void *ptr) {
- struct rrdpush_sender_thread_data *data = ptr;
- worker_unregister();
-
- RRDHOST *host = data->host;
+static bool rrdhost_set_sender(RRDHOST *host) {
+ if(unlikely(!host->sender)) return false;
+ bool ret = false;
netdata_mutex_lock(&host->sender->mutex);
+ if(!host->sender->tid) {
+ rrdhost_flag_clear(host, RRDHOST_FLAG_RRDPUSH_SENDER_CONNECTED | RRDHOST_FLAG_RRDPUSH_SENDER_READY_4_METRICS);
+ rrdhost_flag_set(host, RRDHOST_FLAG_RRDPUSH_SENDER_SPAWN);
+ host->sender->tid = gettid();
+ ret = true;
+ }
+ netdata_mutex_unlock(&host->sender->mutex);
+
+ return ret;
+}
- info("STREAM %s [send]: sending thread cleans up...", rrdhost_hostname(host));
+static void rrdhost_clear_sender___while_having_sender_mutex(RRDHOST *host) {
+ if(unlikely(!host->sender)) return;
- rrdpush_sender_thread_close_socket(host);
- rrdpush_sender_pipe_close(host, host->sender->rrdpush_sender_pipe, false);
+ if(host->sender->tid == gettid()) {
+ host->sender->tid = 0;
+ host->sender->exit.shutdown = false;
+ host->sender->exit.reason = NULL;
+ rrdhost_flag_clear(host, RRDHOST_FLAG_RRDPUSH_SENDER_SPAWN | RRDHOST_FLAG_RRDPUSH_SENDER_CONNECTED | RRDHOST_FLAG_RRDPUSH_SENDER_READY_4_METRICS);
+ }
+}
- if(!rrdhost_flag_check(host, RRDHOST_FLAG_RRDPUSH_SENDER_JOIN)) {
- info("STREAM %s [send]: sending thread detaches itself.", rrdhost_hostname(host));
- netdata_thread_detach(netdata_thread_self());
+static bool rrdhost_sender_should_exit(struct sender_state *s) {
+ // check for outstanding cancellation requests
+ netdata_thread_testcancel();
+
+ if(unlikely(!service_running(SERVICE_STREAMING))) {
+ if(!s->exit.reason)
+ s->exit.reason = "NETDATA EXIT";
+ return true;
}
- rrdhost_flag_clear(host, RRDHOST_FLAG_RRDPUSH_SENDER_SPAWN);
+ if(unlikely(!rrdhost_has_rrdpush_sender_enabled(s->host))) {
+ if(!s->exit.reason)
+ s->exit.reason = "NON STREAMABLE HOST";
+ return true;
+ }
- info("STREAM %s [send]: sending thread now exits.", rrdhost_hostname(host));
+ if(unlikely(s->exit.shutdown)) {
+ if(!s->exit.reason)
+ s->exit.reason = "SENDER SHUTDOWN REQUESTED";
+ return true;
+ }
- netdata_mutex_unlock(&host->sender->mutex);
+ if(unlikely(rrdhost_flag_check(s->host, RRDHOST_FLAG_ORPHAN))) {
+ if(!s->exit.reason)
+ s->exit.reason = "RECEIVER LEFT";
+ return true;
+ }
- freez(data->pipe_buffer);
- freez(data);
+ return false;
}
-void sender_init(RRDHOST *host)
-{
- if (host->sender)
- return;
+static void rrdpush_sender_thread_cleanup_callback(void *ptr) {
+ struct rrdpush_sender_thread_data *s = ptr;
+ worker_unregister();
+
+ RRDHOST *host = s->host;
- host->sender = callocz(1, sizeof(*host->sender));
- host->sender->host = host;
- host->sender->buffer = cbuffer_new(1024, 1024 * 1024);
- host->sender->capabilities = STREAM_OUR_CAPABILITIES;
+ netdata_mutex_lock(&host->sender->mutex);
+ info("STREAM %s [send]: sending thread exits %s",
+ rrdhost_hostname(host),
+ host->sender->exit.reason ? host->sender->exit.reason : "");
- host->sender->rrdpush_sender_pipe[PIPE_READ] = -1;
- host->sender->rrdpush_sender_pipe[PIPE_WRITE] = -1;
- host->sender->rrdpush_sender_socket = -1;
+ rrdpush_sender_thread_close_socket(host);
+ rrdpush_sender_pipe_close(host, host->sender->rrdpush_sender_pipe, false);
-#ifdef ENABLE_COMPRESSION
- if(default_compression_enabled) {
- host->sender->flags |= SENDER_FLAG_COMPRESSION;
- host->sender->compressor = create_compressor();
- }
- else
- host->sender->flags &= ~SENDER_FLAG_COMPRESSION;
-#endif
+ rrdhost_clear_sender___while_having_sender_mutex(host);
+ netdata_mutex_unlock(&host->sender->mutex);
- netdata_mutex_init(&host->sender->mutex);
- replication_init_sender(host->sender);
+ freez(s->pipe_buffer);
+ freez(s);
}
void *rrdpush_sender_thread(void *ptr) {
@@ -1103,13 +1177,18 @@ void *rrdpush_sender_thread(void *ptr) {
worker_register_job_custom_metric(WORKER_SENDER_JOB_REPLAY_DICT_SIZE, "replication dict entries", "entries", WORKER_METRIC_ABSOLUTE);
struct sender_state *s = ptr;
- s->tid = gettid();
if(!rrdhost_has_rrdpush_sender_enabled(s->host) || !s->host->rrdpush_send_destination ||
!*s->host->rrdpush_send_destination || !s->host->rrdpush_send_api_key ||
!*s->host->rrdpush_send_api_key) {
error("STREAM %s [send]: thread created (task id %d), but host has streaming disabled.",
- rrdhost_hostname(s->host), s->tid);
+ rrdhost_hostname(s->host), gettid());
+ return NULL;
+ }
+
+ if(!rrdhost_set_sender(s->host)) {
+ error("STREAM %s [send]: thread created (task id %d), but there is another sender running for this host.",
+ rrdhost_hostname(s->host), gettid());
return NULL;
}
@@ -1125,7 +1204,7 @@ void *rrdpush_sender_thread(void *ptr) {
}
#endif
- info("STREAM %s [send]: thread created (task id %d)", rrdhost_hostname(s->host), s->tid);
+ info("STREAM %s [send]: thread created (task id %d)", rrdhost_hostname(s->host), gettid());
s->timeout = (int)appconfig_get_number(
&stream_config, CONFIG_SECTION_STREAM, "timeout seconds", 600);
@@ -1166,28 +1245,33 @@ void *rrdpush_sender_thread(void *ptr) {
thread_data->sender_state = s;
thread_data->host = s->host;
- // reset our cleanup flags
- rrdhost_flag_clear(s->host, RRDHOST_FLAG_RRDPUSH_SENDER_JOIN);
-
netdata_thread_cleanup_push(rrdpush_sender_thread_cleanup_callback, thread_data);
- for(; rrdhost_has_rrdpush_sender_enabled(s->host) && !netdata_exit ;) {
- // check for outstanding cancellation requests
- netdata_thread_testcancel();
+ size_t iterations = 0;
+ time_t now_s = now_monotonic_sec();
+ while(!rrdhost_sender_should_exit(s)) {
+ iterations++;
// The connection attempt blocks (after which we use the socket in nonblocking)
if(unlikely(s->rrdpush_sender_socket == -1)) {
worker_is_busy(WORKER_SENDER_JOB_CONNECT);
+
+ now_s = now_monotonic_sec();
+ rrdpush_sender_cbuffer_recreate_timed(s, now_s, false, true);
+
rrdhost_flag_clear(s->host, RRDHOST_FLAG_RRDPUSH_SENDER_READY_4_METRICS);
s->flags &= ~SENDER_FLAG_OVERFLOW;
s->read_len = 0;
s->buffer->read = 0;
s->buffer->write = 0;
- if(unlikely(!attempt_to_connect(s)))
+ if(!attempt_to_connect(s))
continue;
- s->last_traffic_seen_t = now_monotonic_sec();
+ if(rrdhost_sender_should_exit(s))
+ break;
+
+ now_s = s->last_traffic_seen_t = now_monotonic_sec();
rrdpush_claimed_id(s->host);
rrdpush_send_host_labels(s->host);
@@ -1197,8 +1281,11 @@ void *rrdpush_sender_thread(void *ptr) {
continue;
}
+ if(iterations % 1000 == 0)
+ now_s = now_monotonic_sec();
+
// If the TCP window never opened then something is wrong, restart connection
- if(unlikely(now_monotonic_sec() - s->last_traffic_seen_t > s->timeout &&
+ if(unlikely(now_s - s->last_traffic_seen_t > s->timeout &&
!rrdpush_sender_pending_replication_requests(s) &&
!rrdpush_sender_replicating_charts(s)
)) {
@@ -1209,11 +1296,13 @@ void *rrdpush_sender_thread(void *ptr) {
}
netdata_mutex_lock(&s->mutex);
- size_t outstanding = cbuffer_next_unsafe(s->host->sender->buffer, NULL);
- size_t available = cbuffer_available_size_unsafe(s->host->sender->buffer);
+ size_t outstanding = cbuffer_next_unsafe(s->buffer, NULL);
+ size_t available = cbuffer_available_size_unsafe(s->buffer);
+ if (unlikely(!outstanding))
+ rrdpush_sender_cbuffer_recreate_timed(s, now_s, true, false);
netdata_mutex_unlock(&s->mutex);
- worker_set_metric(WORKER_SENDER_JOB_BUFFER_RATIO, (NETDATA_DOUBLE)(s->host->sender->buffer->max_size - available) * 100.0 / (NETDATA_DOUBLE)s->host->sender->buffer->max_size);
+ worker_set_metric(WORKER_SENDER_JOB_BUFFER_RATIO, (NETDATA_DOUBLE)(s->buffer->max_size - available) * 100.0 / (NETDATA_DOUBLE)s->buffer->max_size);
if(outstanding)
s->send_attempts++;
@@ -1246,12 +1335,14 @@ void *rrdpush_sender_thread(void *ptr) {
.revents = 0,
}
};
+
int poll_rc = poll(fds, 2, 1000);
debug(D_STREAM, "STREAM: poll() finished collector=%d socket=%d (current chunk %zu bytes)...",
fds[Collector].revents, fds[Socket].revents, outstanding);
- if(unlikely(netdata_exit)) break;
+ if(unlikely(rrdhost_sender_should_exit(s)))
+ break;
internal_error(fds[Collector].fd != s->rrdpush_sender_pipe[PIPE_READ],
"STREAM %s [send to %s]: pipe changed after poll().", rrdhost_hostname(s->host), s->connected_to);
@@ -1261,7 +1352,9 @@ void *rrdpush_sender_thread(void *ptr) {
// Spurious wake-ups without error - loop again
if (poll_rc == 0 || ((poll_rc == -1) && (errno == EAGAIN || errno == EINTR))) {
+ netdata_thread_testcancel();
debug(D_STREAM, "Spurious wakeup");
+ now_s = now_monotonic_sec();
continue;
}
diff --git a/system/Makefile.am b/system/Makefile.am
index 72d123da..1a1b41e2 100644
--- a/system/Makefile.am
+++ b/system/Makefile.am
@@ -3,7 +3,6 @@
MAINTAINERCLEANFILES = $(srcdir)/Makefile.in
CLEANFILES = \
- edit-config \
netdata-openrc \
netdata.logrotate \
netdata.service \
@@ -55,7 +54,6 @@ dist_libsys_DATA = \
$(NULL)
dist_noinst_DATA = \
- edit-config.in \
install-service.sh.in \
netdata-openrc.in \
netdata.logrotate.in \
diff --git a/system/edit-config b/system/edit-config
new file mode 100755
index 00000000..754f9374
--- /dev/null
+++ b/system/edit-config
@@ -0,0 +1,309 @@
+#!/usr/bin/env sh
+
+# shellcheck disable=SC1091
+[ -f /etc/profile ] && . /etc/profile
+
+set -e
+
+script_dir="$(CDPATH="" cd -- "$(dirname -- "$0")" && pwd -P)"
+
+usage() {
+ check_directories
+ cat <<EOF
+USAGE:
+ ${0} [options] FILENAME
+
+ Copy and edit the stock config file named: FILENAME
+ if FILENAME is already copied, it will be edited as-is.
+
+ Stock config files at: '${NETDATA_STOCK_CONFIG_DIR}'
+ User config files at: '${NETDATA_USER_CONFIG_DIR}'
+
+ The editor to use can be specified either by setting the EDITOR
+ environment variable, or by using the --editor option.
+
+ The file to edit can also be specified using the --file option.
+
+ For a list of known config files, run '${0} --list'
+EOF
+ exit 0
+}
+
+error() {
+ echo >&2 "ERROR: ${1}"
+}
+
+abspath() {
+ if [ -d "${1}" ]; then
+ echo "$(cd "${1}" && /usr/bin/env PWD= pwd -P)/"
+ else
+ echo "$(cd "$(dirname "${1}")" && /usr/bin/env PWD= pwd -P)/$(basename "${1}")"
+ fi
+}
+
+is_prefix() {
+ echo "${2}" | grep -qE "^${1}"
+ return $?
+}
+
+check_directories() {
+ if [ -e "${script_dir}/.environment" ]; then
+ OLDPATH="${PATH}"
+ # shellcheck disable=SC1091
+ . "${script_dir}/.environment"
+ PATH="${OLDPATH}"
+ fi
+
+ if [ -n "${NETDATA_PREFIX}" ] && [ -d "${NETDATA_PREFIX}/usr/lib/netdata/conf.d" ]; then
+ stock_dir="${NETDATA_PREFIX}/usr/lib/netdata/conf.d"
+ elif [ -n "${NETDATA_PREFIX}" ] && [ -d "${NETDATA_PREFIX}/lib/netdata/conf.d" ]; then
+ stock_dir="${NETDATA_PREFIX}/lib/netdata/conf.d"
+ elif [ -d "${script_dir}/../../usr/lib/netdata/conf.d" ]; then
+ stock_dir="${script_dir}/../../usr/lib/netdata/conf.d"
+ elif [ -d "${script_dir}/../../lib/netdata/conf.d" ]; then
+ stock_dir="${script_dir}/../../lib/netdata/conf.d"
+ elif [ -d "/usr/lib/netdata/conf.d" ]; then
+ stock_dir="/usr/lib/netdata/conf.d"
+ fi
+
+ [ -z "${NETDATA_USER_CONFIG_DIR}" ] && NETDATA_USER_CONFIG_DIR="${script_dir}"
+ [ -z "${NETDATA_STOCK_CONFIG_DIR}" ] && NETDATA_STOCK_CONFIG_DIR="${stock_dir}"
+
+ if [ -z "${NETDATA_STOCK_CONFIG_DIR}" ]; then
+ error "Unable to find stock config directory."
+ exit 1
+ fi
+}
+
+check_editor() {
+ if [ -z "${editor}" ]; then
+ if [ -n "${EDITOR}" ] && command -v "${EDITOR}" >/dev/null 2>&1; then
+ editor="${EDITOR}"
+ elif command -v editor >/dev/null 2>&1; then
+ editor="editor"
+ elif command -v vi >/dev/null 2>&1; then
+ editor="vi"
+ else
+ error "Unable to find a usable editor, tried \${EDITOR} (${EDITOR}), editor, and vi."
+ exit 1
+ fi
+ elif ! command -v "${editor}" >/dev/null 2>&1; then
+ error "Unable to locate user specified editor ${editor}, is it in your PATH?"
+ exit 1
+ fi
+}
+
+running_in_container() {
+ [ -e /.dockerenv ] && return 0
+ [ -e /.dockerinit ] && return 0
+ [ -r /proc/1/environ ] && tr '\000' '\n' </proc/1/environ | grep -Eiq '^container=podman' && return 0
+ grep -qF -e /docker/ -e /libpod- /proc/self/cgroup 2>/dev/null && return 0
+}
+
+get_docker_command() {
+ if [ -x "${docker}" ]; then
+ return 0
+ elif command -v docker >/dev/null 2>&1; then
+ docker="$(command -v docker)"
+ elif command -v podman >/dev/null 2>&1; then
+ docker="$(command -v podman)"
+ else
+ error "Unable to find a usable container tool stack. I support Docker and Podman."
+ exit 1
+ fi
+}
+
+run_in_container() {
+ ${docker} exec "${1}" /bin/sh -c "${2}" || return 1
+ return 0
+}
+
+check_for_container() {
+ get_docker_command
+ ${docker} container inspect "${1}" >/dev/null 2>&1 || return 1
+ run_in_container "${1}" "[ -d \"${NETDATA_STOCK_CONFIG_DIR}\" ]" >/dev/null 2>&1 || return 1
+ return 0
+}
+
+handle_container() {
+ if running_in_container; then
+ return 0
+ elif [ -z "${container}" ] && [ -f "${script_dir}/.container-hostname" ]; then
+ echo >&2 "Autodetected containerized Netdata instance. Attempting to autodetect container ID."
+ possible_container="$(cat "${script_dir}/.container-hostname")"
+ if check_for_container "${possible_container}"; then
+ container="${possible_container}"
+ elif check_for_container netdata; then
+ container="netdata"
+ else
+ error "Could not autodetect container ID. It must be supplied on the command line with the --container option."
+ exit 1
+ fi
+
+ echo >&2 "Found Netdata container with ID or name ${container}"
+ elif [ -n "${container}" ]; then
+ if ! check_for_container "${container}"; then
+ error "No container with ID or name ${container} exists."
+ exit 1
+ fi
+ fi
+}
+
+list_files() {
+ check_directories
+ handle_container
+
+ if test -t; then
+ width="$(tput cols)"
+ fi
+
+ if [ -z "${container}" ]; then
+ if [ "$(uname -s)" = "Linux" ]; then
+ # shellcheck disable=SC2046,SC2086
+ files="$(cd "${NETDATA_STOCK_CONFIG_DIR}" && ls ${width:+-C} ${width:+-w ${width}} $(find . -type f | cut -d '/' -f 2-))"
+ elif [ "$(uname -s)" = "FreeBSD" ]; then
+ if [ -n "${width}" ]; then
+ export COLUMNS="${width}"
+ fi
+
+ # shellcheck disable=SC2046
+ files="$(cd "${NETDATA_STOCK_CONFIG_DIR}" && ls ${width:+-C} $(find . -type f | cut -d '/' -f 2-))"
+ else
+ # shellcheck disable=SC2046
+ files="$(cd "${NETDATA_STOCK_CONFIG_DIR}" && ls $(find . -type f | cut -d '/' -f 2-))"
+ fi
+ else
+ files="$(run_in_container "${container}" "cd /usr/lib/netdata/conf.d && ls ${width:+-C} ${width:+-w ${width}} \$(find . -type f | cut -d '/' -f 2-)")"
+ fi
+
+ if [ -z "${files}" ]; then
+ error "Failed to find any configuration files."
+ exit 1
+ fi
+
+ cat <<EOF
+The following configuration files are known to this script:
+
+${files}
+EOF
+ exit 0
+}
+
+parse_args() {
+ while [ -n "${1}" ]; do
+ case "${1}" in
+ "--help") usage ;;
+ "--list") list_files ;;
+ "--file")
+ if [ -n "${2}" ]; then
+ file="${2}"
+ shift 1
+ else
+ error "No file specified to edit."
+ exit 1
+ fi
+ ;;
+ "--container")
+ if [ -n "${2}" ]; then
+ container="${2}"
+ shift 1
+ else
+ error "No container ID or name specified with the --container option."
+ exit 1
+ fi
+ ;;
+ "--editor")
+ if [ -n "${2}" ]; then
+ editor="${2}"
+ shift 1
+ else
+ error "No editor specified with the --editor option."
+ exit 1
+ fi
+ ;;
+ *)
+ if [ -z "${2}" ]; then
+ file="${1}"
+ else
+ error "Unrecognized option ${1}."
+ exit 1
+ fi
+ ;;
+ esac
+ shift 1
+ done
+
+ [ -z "${file}" ] && usage
+
+ absfile="$(abspath "${file}")"
+ if ! is_prefix "${script_dir}" "${absfile}"; then
+ error "${file} is not located under ${script_dir}"
+ exit 1
+ fi
+
+ file="${absfile##"${script_dir}"}"
+}
+
+copy_native() {
+ if [ ! -w "${NETDATA_USER_CONFIG_DIR}" ]; then
+ error "Cannot write to ${NETDATA_USER_CONFIG_DIR}!"
+ exit 1
+ fi
+
+ if [ -f "${NETDATA_STOCK_CONFIG_DIR}/${1}" ]; then
+ echo >&2 "Copying '${NETDATA_STOCK_CONFIG_DIR}/${1}' to '${NETDATA_USER_CONFIG_DIR}/${1}' ... "
+ cp -p "${NETDATA_STOCK_CONFIG_DIR}/${1}" "${NETDATA_USER_CONFIG_DIR}/${1}" || exit 1
+ else
+ echo >&2 "Creating empty '${NETDATA_USER_CONFIG_DIR}/${1}' ... "
+ touch "${NETDATA_USER_CONFIG_DIR}/${1}" || exit 1
+ fi
+}
+
+copy_container() {
+ if [ ! -w "${NETDATA_USER_CONFIG_DIR}" ]; then
+ error "Cannot write to ${NETDATA_USER_CONFIG_DIR}!"
+ exit 1
+ fi
+
+ if run_in_container "${container}" "[ -f \"${NETDATA_STOCK_CONFIG_DIR}/${1}\" ]"; then
+ echo >&2 "Copying '${NETDATA_STOCK_CONFIG_DIR}/${1}' to '${NETDATA_USER_CONFIG_DIR}/${1}' ... "
+ ${docker} cp -a "${container}:${NETDATA_STOCK_CONFIG_DIR}/${1}" "${NETDATA_USER_CONFIG_DIR}/${1}" || exit 1
+ else
+ echo >&2 "Creating empty '${NETDATA_USER_CONFIG_DIR}/${1}' ... "
+ touch "${NETDATA_USER_CONFIG_DIR}/${1}" || exit 1
+ fi
+}
+
+copy() {
+ if [ -f "${NETDATA_USER_CONFIG_DIR}/${1}" ]; then
+ return 0
+ elif [ -n "${container}" ]; then
+ copy_container "${1}"
+ else
+ copy_native "${1}"
+ fi
+}
+
+edit() {
+ echo >&2 "Editing '${1}' ..."
+
+ # check we can edit
+ if [ ! -w "${1}" ]; then
+ error "Cannot write to ${1}!"
+ exit 1
+ fi
+
+ "${editor}" "${1}"
+ exit $?
+}
+
+main() {
+ parse_args "${@}"
+ check_directories
+ check_editor
+ handle_container
+ copy "${file}"
+ edit "${absfile}"
+}
+
+main "${@}"
diff --git a/system/edit-config.in b/system/edit-config.in
deleted file mode 100755
index 050d97cd..00000000
--- a/system/edit-config.in
+++ /dev/null
@@ -1,83 +0,0 @@
-#!/usr/bin/env sh
-
-[ -f /etc/profile ] && . /etc/profile
-
-file="${1}"
-
-if [ "$(command -v editor)" ]; then
- EDITOR="${EDITOR-editor}"
-else
- EDITOR="${EDITOR-vi}"
-fi
-
-[ -z "${NETDATA_USER_CONFIG_DIR}" ] && NETDATA_USER_CONFIG_DIR="@configdir_POST@"
-[ -z "${NETDATA_STOCK_CONFIG_DIR}" ] && NETDATA_STOCK_CONFIG_DIR="@libconfigdir_POST@"
-
-if [ -z "${file}" ]; then
- cat << EOF
-
-USAGE:
- ${0} FILENAME
-
- Copy and edit the stock config file named: FILENAME
- if FILENAME is already copied, it will be edited as-is.
-
- The EDITOR shell variable is used to define the editor to be used.
-
- Stock config files at: '${NETDATA_STOCK_CONFIG_DIR}'
- User config files at: '${NETDATA_USER_CONFIG_DIR}'
-
- Available files in '${NETDATA_STOCK_CONFIG_DIR}' to copy and edit:
-
-EOF
-
- cd "${NETDATA_STOCK_CONFIG_DIR}" || exit 1
- ls >&2 -R ./*.conf ./*/*.conf
- exit 1
-
-fi
-
-edit() {
- echo >&2 "Editing '${1}' ..."
-
- # check we can edit
- if [ ! -w "${1}" ]; then
- echo >&2 "Cannot write to ${1}! Aborting ..."
- exit 1
- fi
-
- "${EDITOR}" "${1}"
- exit $?
-}
-
-copy_and_edit() {
- # check we can copy
- if [ ! -w "${NETDATA_USER_CONFIG_DIR}" ]; then
- echo >&2 "Cannot write to ${NETDATA_USER_CONFIG_DIR}! Aborting ..."
- exit 1
- fi
-
- if [ ! -f "${NETDATA_USER_CONFIG_DIR}/${1}" ]; then
- echo >&2 "Copying '${NETDATA_STOCK_CONFIG_DIR}/${1}' to '${NETDATA_USER_CONFIG_DIR}/${1}' ... "
- cp -p "${NETDATA_STOCK_CONFIG_DIR}/${1}" "${NETDATA_USER_CONFIG_DIR}/${1}" || exit 1
- fi
-
- edit "${NETDATA_USER_CONFIG_DIR}/${1}"
-}
-
-# make sure it is not absolute filename
-c1="$(echo "${file}" | cut -b 1)"
-if [ "${c1}" = "/" ] || [ "${c1}" = "." ]; then
- echo >&2 "Please don't use filenames starting with '/' or '.'"
- exit 1
-fi
-
-# already exists
-[ -f "${NETDATA_USER_CONFIG_DIR}/${file}" ] && edit "${NETDATA_USER_CONFIG_DIR}/${file}"
-
-# stock config is valid, copy and edit
-[ -f "${NETDATA_STOCK_CONFIG_DIR}/${file}" ] && copy_and_edit "${file}"
-
-# no such config found
-echo >&2 "File '${file}' is not found in '${NETDATA_STOCK_CONFIG_DIR}'"
-exit 1
diff --git a/system/netdata.logrotate.in b/system/netdata.logrotate.in
index a766b6cc..2c4949e5 100644
--- a/system/netdata.logrotate.in
+++ b/system/netdata.logrotate.in
@@ -7,6 +7,6 @@
notifempty
sharedscripts
postrotate
- /bin/kill -HUP `cat @localstatedir_POST@/run/netdata/netdata.pid 2>/dev/null` 2>/dev/null || true
+ /bin/kill -HUP `cat /run/netdata/netdata.pid 2>/dev/null` 2>/dev/null || true
endscript
}
diff --git a/system/netdata.service.in b/system/netdata.service.in
index 3947392f..25d95b2b 100644
--- a/system/netdata.service.in
+++ b/system/netdata.service.in
@@ -52,7 +52,7 @@ CapabilityBoundingSet=CAP_SYS_ADMIN CAP_PERFMON
CapabilityBoundingSet=CAP_SYS_PTRACE
# is required for ebpf plugin
CapabilityBoundingSet=CAP_SYS_RESOURCE
-# is required for fping app
+# is required for go.d/ping app
CapabilityBoundingSet=CAP_NET_RAW
# is required for cgroups plugin
CapabilityBoundingSet=CAP_SYS_CHROOT
@@ -71,6 +71,10 @@ ProtectControlGroups=on
ReadWriteDirectories=/run/netdata
# This is needed to make email-based alert deliver work if Postfix is the email provider on the system.
ReadWriteDirectories=-/var/spool/postfix/maildrop
+# LXCFS directories (https://github.com/lxc/lxcfs#lxcfs)
+# If we don't set them explicitly, systemd mounts procfs from the host. See https://github.com/netdata/netdata/issues/14238.
+BindReadOnlyPaths=-/proc/cpuinfo -/proc/diskstats -/proc/loadavg -/proc/meminfo
+BindReadOnlyPaths=-/proc/stat -/proc/swaps -/proc/uptime -/proc/slabinfo
[Install]
WantedBy=multi-user.target
diff --git a/tests/alarm_repetition/netdata.conf_with_repetition b/tests/alarm_repetition/netdata.conf_with_repetition
index ddee852f..e1424abf 100644
--- a/tests/alarm_repetition/netdata.conf_with_repetition
+++ b/tests/alarm_repetition/netdata.conf_with_repetition
@@ -38,7 +38,6 @@
nfacct = no
python.d = no
apps = no
- fping = no
cups = no
[health]
diff --git a/tests/alarm_repetition/netdata.conf_without_repetition b/tests/alarm_repetition/netdata.conf_without_repetition
index 7add0328..f11b1632 100644
--- a/tests/alarm_repetition/netdata.conf_without_repetition
+++ b/tests/alarm_repetition/netdata.conf_without_repetition
@@ -38,7 +38,6 @@
nfacct = no
python.d = no
apps = no
- fping = no
cups = no
[health]
diff --git a/tests/health_mgmtapi/README.md b/tests/health_mgmtapi/README.md
index e19b612a..aa51c0d6 100644
--- a/tests/health_mgmtapi/README.md
+++ b/tests/health_mgmtapi/README.md
@@ -5,7 +5,7 @@ custom_edit_url: https://github.com/netdata/netdata/edit/master/tests/health_mgm
# Health command API tester
-The directory `tests/health_cmdapi` contains the test script `health-cmdapi-test.sh` for the [health command API](/web/api/health/README.md).
+The directory `tests/health_cmdapi` contains the test script `health-cmdapi-test.sh` for the [health command API](https://github.com/netdata/netdata/blob/master/web/api/health/README.md).
The script can be executed with options to prepare the system for the tests, run them and restore the system to its previous state.
diff --git a/tests/installer/checksums.sh b/tests/installer/checksums.sh
deleted file mode 100755
index ff0400ba..00000000
--- a/tests/installer/checksums.sh
+++ /dev/null
@@ -1,53 +0,0 @@
-#!/bin/sh
-
-#
-# Mechanism to validate kickstart files integrity status
-#
-# Copyright: SPDX-License-Identifier: GPL-3.0-or-later
-#
-# Author : Pawel Krupa (pawel@netdata.cloud)
-# Author : Pavlos Emm. Katsoulakis (paul@netdata.cloud)
-# Author : Austin S. Hemmelgarn (austin@netdata.cloud)
-set -e
-
-# If we are not in netdata git repo, at the top level directory, fail
-TOP_LEVEL=$(basename "$(git rev-parse --show-toplevel 2>/dev/null || echo "")")
-CWD="$(git rev-parse --show-cdup 2>/dev/null || echo "")"
-if [ -n "$CWD" ] || [ "${TOP_LEVEL}" != "netdata" ]; then
- echo "Run as ./tests/installer/$(basename "$0") from top level directory of netdata git repository"
- echo "Kickstart validation process aborted"
- exit 1
-fi
-
-check_file() {
- README_MD5=$(grep "$1" "$2" | grep md5sum | grep curl | cut -d '"' -f2)
- KICKSTART_URL="https://my-netdata.io/$1"
- KICKSTART="packaging/installer/$1"
- KICKSTART_MD5="$(md5sum "${KICKSTART}" | cut -d' ' -f1)"
- CALCULATED_MD5="$(curl -Ss "${KICKSTART_URL}" | md5sum | cut -d ' ' -f 1)"
-
- # Conditionally run the website validation
- if [ -z "${LOCAL_ONLY}" ]; then
- echo "Validating ${KICKSTART_URL} against local file ${KICKSTART} with MD5 ${KICKSTART_MD5}.."
- if [ "$KICKSTART_MD5" = "$CALCULATED_MD5" ]; then
- echo "${KICKSTART_URL} looks fine"
- else
- echo "${KICKSTART_URL} md5sum does not match local file, it needs to be updated"
- exit 2
- fi
- fi
-
- echo "Validating documentation for $1"
- if [ "$KICKSTART_MD5" != "$README_MD5" ]; then
- echo "Invalid checksum for $1 in $2."
- echo "checksum in docs: $README_MD5"
- echo "current checksum: $KICKSTART_MD5"
- exit 2
- else
- echo "$1 MD5Sum is well documented"
- fi
-}
-
-check_file kickstart.sh packaging/installer/methods/kickstart.md
-
-echo "No problems found, exiting successfully!"
diff --git a/tests/installer/slack.sh b/tests/installer/slack.sh
deleted file mode 100755
index 3f3eff6e..00000000
--- a/tests/installer/slack.sh
+++ /dev/null
@@ -1,65 +0,0 @@
-# #No shebang necessary
-# BASH Lib: Simple incoming webhook for slack integration.
-#
-# The script expects the following parameters to be defined by the upper layer:
-# SLACK_NOTIFY_WEBHOOK_URL
-# SLACK_BOT_NAME
-# SLACK_CHANNEL
-#
-# Copyright:
-#
-# Author: Pavlos Emm. Katsoulakis <paul@netdata.cloud
-
-post_message() {
- TYPE="$1"
- MESSAGE="$2"
- CUSTOM_CHANNEL="$3"
-
- case "$TYPE" in
- "PLAIN_MESSAGE")
- curl -X POST --data-urlencode "payload={\"channel\": \"${SLACK_CHANNEL}\", \"username\": \"${SLACK_BOT_NAME}\", \"text\": \"${MESSAGE}\", \"icon_emoji\": \":space_invader:\"}" "${SLACK_NOTIFY_WEBHOOK_URL}"
- ;;
- "TRAVIS_MESSAGE")
- if [ "${TRAVIS_EVENT_TYPE}" == "pull_request" ] || [ "${TRAVIS_BRANCH}" != "master" ] ; then
- echo "Skipping notification due to build type."
- return 0
- fi
-
- if [ -n "${CUSTOM_CHANNEL}" ]; then
- echo "Sending travis message to custom channel ${CUSTOM_CHANNEL}"
- OPTIONAL_CHANNEL_INFO="\"channel\": \"${CUSTOM_CHANNEL}\","
- fi
-
- POST_MESSAGE="{
- ${OPTIONAL_CHANNEL_INFO}
- \"text\": \"${TRAVIS_REPO_SLUG}, ${MESSAGE}\",
- \"attachments\": [{
- \"text\": \"${TRAVIS_JOB_NUMBER}: Event type '${TRAVIS_EVENT_TYPE}', on '${TRAVIS_OS_NAME}' \",
- \"fallback\": \"I could not determine the build\",
- \"callback_id\": \"\",
- \"color\": \"#3AA3E3\",
- \"attachment_type\": \"default\",
- \"actions\": [
- {
- \"name\": \"${TRAVIS_BUILD_NUMBER}\",
- \"text\": \"Build #${TRAVIS_BUILD_NUMBER}\",
- \"type\": \"button\",
- \"url\": \"${TRAVIS_BUILD_WEB_URL}\"
- },
- {
- \"name\": \"${TRAVIS_JOB_NUMBER}\",
- \"text\": \"Job #${TRAVIS_JOB_NUMBER}\",
- \"type\": \"button\",
- \"url\": \"${TRAVIS_JOB_WEB_URL}\"
- }]
- }]
- }"
- echo "Sending ${POST_MESSAGE}"
- curl -X POST --data-urlencode "payload=${POST_MESSAGE}" "${SLACK_NOTIFY_WEBHOOK_URL}"
- ;;
- *)
- echo "Unrecognized message type \"$TYPE\" was given"
- return 1
- ;;
- esac
-}
diff --git a/tests/lifecycle.bats b/tests/lifecycle.bats
deleted file mode 100755
index bf626fe1..00000000
--- a/tests/lifecycle.bats
+++ /dev/null
@@ -1,60 +0,0 @@
-#!/usr/bin/env bats
-#
-# Netdata installation lifecycle testing script.
-# This is to validate the install, update and uninstall of netdata
-#
-# Copyright: SPDX-License-Identifier: GPL-3.0-or-later
-#
-# Author : Pavlos Emm. Katsoulakis <paul@netdata.cloud)
-#
-
-INSTALLATION="$BATS_TMPDIR/installation"
-ENV="${INSTALLATION}/netdata/etc/netdata/.environment"
-# list of files which need to be checked. Path cannot start from '/'
-FILES="usr/libexec/netdata/plugins.d/go.d.plugin
- usr/libexec/netdata/plugins.d/charts.d.plugin
- usr/libexec/netdata/plugins.d/python.d.plugin"
-
-DIRS="usr/sbin/netdata
- etc/netdata
- usr/share/netdata
- usr/libexec/netdata
- var/cache/netdata
- var/lib/netdata
- var/log/netdata"
-
-setup() {
- # If we are not in netdata git repo, at the top level directory, fail
- TOP_LEVEL=$(basename "$(git rev-parse --show-toplevel)")
- CWD=$(git rev-parse --show-cdup || echo "")
- if [ -n "${CWD}" ] || [ ! "${TOP_LEVEL}" == "netdata" ]; then
- echo "Run as ./tests/lifecycle/$(basename "$0") from top level directory of git repository"
- exit 1
- fi
-}
-
-@test "install netdata" {
- ./netdata-installer.sh --dont-wait --dont-start-it --auto-update --install "${INSTALLATION}"
-
- # Validate particular files
- for file in $FILES; do
- [ ! -f "$BATS_TMPDIR/$file" ]
- done
-
- # Validate particular directories
- for a_dir in $DIRS; do
- [ ! -d "$BATS_TMPDIR/$a_dir" ]
- done
-}
-
-@test "update netdata" {
- export ENVIRONMENT_FILE="${ENV}"
- ${INSTALLATION}/netdata/usr/libexec/netdata/netdata-updater.sh --not-running-from-cron
- ! grep "new_installation" "${ENV}"
-}
-
-@test "uninstall netdata" {
- ./packaging/installer/netdata-uninstaller.sh --yes --force --env "${ENV}"
- [ ! -f "${INSTALLATION}/netdata/usr/sbin/netdata" ]
- [ ! -f "/etc/cron.daily/netdata-updater" ]
-}
diff --git a/tests/run-unit-tests.sh b/tests/run-unit-tests.sh
index 70d618ad..d3935177 100755
--- a/tests/run-unit-tests.sh
+++ b/tests/run-unit-tests.sh
@@ -21,7 +21,7 @@
install_netdata() {
echo "Installing Netdata"
fakeroot ./netdata-installer.sh \
- --install "$HOME" \
+ --install-prefix "$HOME" \
--dont-wait \
--dont-start-it \
--enable-plugin-nfacct \
diff --git a/tests/updater_checks.bats b/tests/updater_checks.bats
deleted file mode 100755
index 15ffce8f..00000000
--- a/tests/updater_checks.bats
+++ /dev/null
@@ -1,65 +0,0 @@
-#!/usr/bin/env bats
-#
-# This script is responsible for validating
-# updater capabilities after a change
-#
-# Copyright: SPDX-License-Identifier: GPL-3.0-or-later
-#
-# Author : Pavlos Emm. Katsoulakis <paul@netdata.cloud)
-#
-
-INSTALLATION="$BATS_TMPDIR/installation"
-ENV="${INSTALLATION}/netdata/etc/netdata/.environment"
-# list of files which need to be checked. Path cannot start from '/'
-FILES="usr/libexec/netdata/plugins.d/go.d.plugin
- usr/libexec/netdata/plugins.d/charts.d.plugin
- usr/libexec/netdata/plugins.d/python.d.plugin"
-
-DIRS="usr/sbin/netdata
- etc/netdata
- usr/share/netdata
- usr/libexec/netdata
- var/cache/netdata
- var/lib/netdata
- var/log/netdata"
-
-setup() {
- # If we are not in netdata git repo, at the top level directory, fail
- TOP_LEVEL=$(basename "$(git rev-parse --show-toplevel)")
- CWD=$(git rev-parse --show-cdup || echo "")
- if [ -n "${CWD}" ] || [ ! "${TOP_LEVEL}" == "netdata" ]; then
- echo "Run as ./tests/$(basename "$0") from top level directory of git repository"
- exit 1
- fi
-}
-
-@test "install stable netdata using kickstart" {
- ./packaging/installer/kickstart.sh --dont-wait --dont-start-it --auto-update --install ${INSTALLATION}
-
- # Validate particular files
- for file in $FILES; do
- [ ! -f "$BATS_TMPDIR/$file" ]
- done
-
- # Validate particular directories
- for a_dir in $DIRS; do
- [ ! -d "$BATS_TMPDIR/$a_dir" ]
- done
-
- # Cleanup
- rm -rf ${kickstart_file}
-}
-
-@test "update netdata using the new updater" {
- export ENVIRONMENT_FILE="${ENV}"
- # Run the updater, with the override so that it uses the local repo we have at hand
- export NETDATA_LOCAL_TARBALL_OVERRIDE="${PWD}"
- ${INSTALLATION}/netdata/usr/libexec/netdata/netdata-updater.sh --not-running-from-cron
- ! grep "new_installation" "${ENV}"
-}
-
-@test "uninstall netdata using latest uninstaller" {
- ./packaging/installer/netdata-uninstaller.sh --yes --force --env "${ENV}"
- [ ! -f "${INSTALLATION}/netdata/usr/sbin/netdata" ]
- [ ! -f "/etc/cron.daily/netdata-updater" ]
-}
diff --git a/tests/updater_checks.sh b/tests/updater_checks.sh
deleted file mode 100755
index 1c8d7b02..00000000
--- a/tests/updater_checks.sh
+++ /dev/null
@@ -1,71 +0,0 @@
-#!/usr/bin/env sh
-#
-# Wrapper script that installs the required dependencies
-# for the BATS script to run successfully
-#
-# Copyright: SPDX-License-Identifier: GPL-3.0-or-later
-#
-# Author : Pavlos Emm. Katsoulakis <paul@netdata.cloud)
-#
-
-echo "Syncing/updating repository.."
-
-blind_arch_grep_install() {
- # There is a peculiar docker case with arch, where grep is not available
- # This method will have to be triggered blindly, to inject grep so that we can process
- # It starts to become a chicken-egg situation with all the distros..
- echo "* * Workaround hack * *"
- echo "Attempting blind install for archlinux case"
-
- if command -v pacman > /dev/null 2>&1; then
- echo "Executing grep installation"
- pacman -Sy
- pacman --noconfirm --needed -S grep
- fi
-}
-blind_arch_grep_install || echo "Workaround failed, proceed as usual"
-
-running_os="$(grep '^ID=' /etc/os-release | cut -d'=' -f2 | sed -e 's/"//g')"
-
-case "${running_os}" in
-"centos"|"fedora"|"CentOS")
- echo "Running on CentOS, updating YUM repository.."
- yum clean all
- yum update -y
-
- echo "Installing extra dependencies.."
- yum install -y epel-release
- yum install -y bats curl
- ;;
-"debian"|"ubuntu")
- echo "Running ${running_os}, updating APT repository"
- apt-get update -y
- apt-get install -y bats curl
- ;;
-"opensuse-leap"|"opensuse-tumbleweed")
- zypper update -y
- zypper install -y bats curl
-
- # Fixes curl: (60) SSL certificate problem: unable to get local issuer certificate
- # https://travis-ci.com/netdata/netdata/jobs/267573805
- update-ca-certificates
- ;;
-"arch")
- pacman --noconfirm -Syu
- pacman --noconfirm --needed -S bash-bats curl libffi
- ;;
-"alpine")
- apk update
- apk add bash curl bats
- ;;
-*)
- echo "Running on ${running_os}, no repository preparation done"
- ;;
-esac
-
-# Run dependency scriptlet, before anything else
-#
-./packaging/installer/install-required-packages.sh --non-interactive netdata
-
-echo "Running BATS file.."
-bats --tap tests/updater_checks.bats
diff --git a/web/README.md b/web/README.md
index 7093ca18..eae57934 100644
--- a/web/README.md
+++ b/web/README.md
@@ -14,17 +14,17 @@ team and the community, but you can also customize them yourself.
There are two primary ways to view Netdata's dashboards:
-1. The [local Agent dashboard](/web/gui/README.md) that comes pre-configured with every Netdata installation. You can
+1. The [local Agent dashboard](https://github.com/netdata/netdata/blob/master/web/gui/README.md) that comes pre-configured with every Netdata installation. You can
see it at `http://NODE:19999`, replacing `NODE` with `localhost`, the hostname of your node, or its IP address. You
can customize the contents and colors of the standard dashboard [using
- JavaScript](/web/gui/README.md#customizing-the-local-dashboard).
+ JavaScript](https://github.com/netdata/netdata/blob/master/web/gui/README.md#customizing-the-local-dashboard).
2. The [`dashboard.js` JavaScript library](#dashboardjs), which helps you
- [customize the standard dashboards](/web/gui/README.md#customizing-the-local-dashboard)
- using JavaScript, or create entirely new [custom dashboards](/web/gui/custom/README.md) or
- [Atlassian Confluence dashboards](/web/gui/confluence/README.md).
+ [customize the standard dashboards](https://github.com/netdata/netdata/blob/master/web/gui/README.md#customizing-the-local-dashboard)
+ using JavaScript, or create entirely new [custom dashboards](https://github.com/netdata/netdata/blob/master/web/gui/custom/README.md) or
+ [Atlassian Confluence dashboards](https://github.com/netdata/netdata/blob/master/web/gui/confluence/README.md).
-You can also view all the data Netdata collects through the [REST API v1](/web/api/README.md#netdata-rest-api).
+You can also view all the data Netdata collects through the [REST API v1](https://github.com/netdata/netdata/blob/master/web/api/README.md#netdata-rest-api).
No matter where you use Netdata's charts, you'll want to know how to [use](#using-charts) them. You'll also want to
understand how Netdata defines [charts](#charts), [dimensions](#dimensions), [families](#families), and
@@ -84,7 +84,7 @@ Netdata organizes metrics into charts, dimensions, families, and contexts.
A **chart** is an individual, interactive, always-updating graphic displaying
one or more collected/calculated metrics. Charts are generated by
-[collectors](/collectors/README.md).
+[collectors](https://github.com/netdata/netdata/blob/master/collectors/README.md).
Here's the system CPU chart, the first chart displayed on the standard
dashboard:
@@ -182,7 +182,7 @@ hover over the date above the list of dimensions. A tooltip will appear that
shows you two pieces of information: the collector that produces the chart, and
the chart's context.
-Netdata also uses [contexts for alarm templates](/health/REFERENCE.md#alarm-line-on). You can create an alarm for the
+Netdata also uses [contexts for alarm templates](https://github.com/netdata/netdata/blob/master/health/REFERENCE.md#alarm-line-on). You can create an alarm for the
`net.packets` context to receive alerts for any chart with that context, no matter which family it's attached to.
## Positive and negative values on charts
@@ -215,7 +215,7 @@ all the charts and other visualizations that appear on any Netdata dashboard.
You need to put `dashboard.js` on any HTML page that's going to render Netdata
charts.
-The [custom dashboards documentation](/web/gui/custom/README.md) contains examples of such
+The [custom dashboards documentation](https://github.com/netdata/netdata/blob/master/web/gui/custom/README.md) contains examples of such
custom HTML pages.
### Generating dashboard.js
diff --git a/web/api/README.md b/web/api/README.md
index fc520a09..82a55eb2 100644
--- a/web/api/README.md
+++ b/web/api/README.md
@@ -1,6 +1,10 @@
<!--
title: "API"
-custom_edit_url: https://github.com/netdata/netdata/edit/master/web/api/README.md
+custom_edit_url: "https://github.com/netdata/netdata/edit/master/web/api/README.md"
+sidebar_label: "API"
+learn_status: "Published"
+learn_topic_type: "Tasks"
+learn_rel_path: "Operations"
-->
# API
diff --git a/web/api/badges/README.md b/web/api/badges/README.md
index 84409471..8f6eca62 100644
--- a/web/api/badges/README.md
+++ b/web/api/badges/README.md
@@ -25,7 +25,7 @@ Similarly, there is [a chart that shows outbound bandwidth per class](http://lon
The right one is a **volume** calculation. Netdata calculated the total of the last 86.400 seconds (a day) which gives `kilobits`, then divided it by 8 to make it KB, then by 1024 to make it MB and then by 1024 to make it GB. Calculations like this are quite accurate, since for every value collected, every second, Netdata interpolates it to second boundary using microsecond calculations.
-Let's see a few more badge examples (they come from the [Netdata registry](/registry/README.md)):
+Let's see a few more badge examples (they come from the [Netdata registry](https://github.com/netdata/netdata/blob/master/registry/README.md)):
- **cpu usage of user `root`** (you can pick any user; 100% = 1 core). This will be `green <10%`, `yellow <20%`, `orange <50%`, `blue <100%` (1 core), `red` otherwise (you define thresholds and colors on the URL).
diff --git a/web/api/badges/web_buffer_svg.c b/web/api/badges/web_buffer_svg.c
index 080f2240..ca0f4b7a 100644
--- a/web/api/badges/web_buffer_svg.c
+++ b/web/api/badges/web_buffer_svg.c
@@ -913,7 +913,7 @@ int web_client_api_request_v1_badge(RRDHOST *host, struct web_client *w, char *u
if(!strcmp(name, "chart")) chart = value;
else if(!strcmp(name, "dimension") || !strcmp(name, "dim") || !strcmp(name, "dimensions") || !strcmp(name, "dims")) {
if(!dimensions)
- dimensions = buffer_create(100);
+ dimensions = buffer_create(100, &netdata_buffers_statistics.buffers_api);
buffer_strcat(dimensions, "|");
buffer_strcat(dimensions, value);
@@ -969,7 +969,7 @@ int web_client_api_request_v1_badge(RRDHOST *host, struct web_client *w, char *u
ret = HTTP_RESP_OK;
goto cleanup;
}
- st->last_accessed_time = now_realtime_sec();
+ st->last_accessed_time_s = now_realtime_sec();
if(alarm) {
rca = rrdcalc_from_rrdset_get(st, alarm);
@@ -1110,14 +1110,14 @@ int web_client_api_request_v1_badge(RRDHOST *host, struct web_client *w, char *u
ret = HTTP_RESP_INTERNAL_SERVER_ERROR;
// if the collected value is too old, don't calculate its value
- if (rrdset_last_entry_t(st) >= (now_realtime_sec() - (st->update_every * st->gap_when_lost_iterations_above)))
+ if (rrdset_last_entry_s(st) >= (now_realtime_sec() - (st->update_every * gap_when_lost_iterations_above)))
ret = rrdset2value_api_v1(st, w->response.data, &n,
(dimensions) ? buffer_tostring(dimensions) : NULL,
points, after, before, group, group_options, 0, options,
NULL, &latest_timestamp,
NULL, NULL, NULL,
&value_is_null, NULL, 0, 0,
- QUERY_SOURCE_API_BADGE);
+ QUERY_SOURCE_API_BADGE, STORAGE_PRIORITY_NORMAL);
// if the value cannot be calculated, show empty badge
if (ret != HTTP_RESP_OK) {
diff --git a/web/api/exporters/prometheus/README.md b/web/api/exporters/prometheus/README.md
index cf7e2caa..1ff86f4e 100644
--- a/web/api/exporters/prometheus/README.md
+++ b/web/api/exporters/prometheus/README.md
@@ -5,6 +5,6 @@ custom_edit_url: https://github.com/netdata/netdata/edit/master/web/api/exporter
# Prometheus exporter
-Read the Prometheus exporter documentation: [Using Netdata with Prometheus](/exporting/prometheus/README.md).
+Read the Prometheus exporter documentation: [Using Netdata with Prometheus](https://github.com/netdata/netdata/blob/master/exporting/prometheus/README.md).
diff --git a/web/api/exporters/shell/allmetrics_shell.c b/web/api/exporters/shell/allmetrics_shell.c
index 0ffbac67..dded5a53 100644
--- a/web/api/exporters/shell/allmetrics_shell.c
+++ b/web/api/exporters/shell/allmetrics_shell.c
@@ -127,7 +127,7 @@ void rrd_stats_api_v1_charts_allmetrics_json(RRDHOST *host, const char *filter_s
rrdset_family(st),
rrdset_context(st),
rrdset_units(st),
- (int64_t)rrdset_last_entry_t(st));
+ (int64_t) rrdset_last_entry_s(st));
chart_counter++;
dimension_counter = 0;
diff --git a/web/api/formatters/README.md b/web/api/formatters/README.md
index 3e67ac6e..4c281f06 100644
--- a/web/api/formatters/README.md
+++ b/web/api/formatters/README.md
@@ -12,18 +12,18 @@ The following formats are supported:
| format|module|content type|description|
|:----:|:----:|:----------:|:----------|
-| `array`|[ssv](/web/api/formatters/ssv/README.md)|application/json|a JSON array|
-| `csv`|[csv](/web/api/formatters/csv/README.md)|text/plain|a text table, comma separated, with a header line (dimension names) and `\r\n` at the end of the lines|
-| `csvjsonarray`|[csv](/web/api/formatters/csv/README.md)|application/json|a JSON array, with each row as another array (the first row has the dimension names)|
-| `datasource`|[json](/web/api/formatters/json/README.md)|application/json|a Google Visualization Provider `datasource` javascript callback|
-| `datatable`|[json](/web/api/formatters/json/README.md)|application/json|a Google `datatable`|
-| `html`|[csv](/web/api/formatters/csv/README.md)|text/html|an html table|
-| `json`|[json](/web/api/formatters/json/README.md)|application/json|a JSON object|
-| `jsonp`|[json](/web/api/formatters/json/README.md)|application/json|a JSONP javascript callback|
-| `markdown`|[csv](/web/api/formatters/csv/README.md)|text/plain|a markdown table|
-| `ssv`|[ssv](/web/api/formatters/ssv/README.md)|text/plain|a space separated list of values|
-| `ssvcomma`|[ssv](/web/api/formatters/ssv/README.md)|text/plain|a comma separated list of values|
-| `tsv`|[csv](/web/api/formatters/csv/README.md)|text/plain|a TAB delimited `csv` (MS Excel flavor)|
+| `array`|[ssv](https://github.com/netdata/netdata/blob/master/web/api/formatters/ssv/README.md)|application/json|a JSON array|
+| `csv`|[csv](https://github.com/netdata/netdata/blob/master/web/api/formatters/csv/README.md)|text/plain|a text table, comma separated, with a header line (dimension names) and `\r\n` at the end of the lines|
+| `csvjsonarray`|[csv](https://github.com/netdata/netdata/blob/master/web/api/formatters/csv/README.md)|application/json|a JSON array, with each row as another array (the first row has the dimension names)|
+| `datasource`|[json](https://github.com/netdata/netdata/blob/master/web/api/formatters/json/README.md)|application/json|a Google Visualization Provider `datasource` javascript callback|
+| `datatable`|[json](https://github.com/netdata/netdata/blob/master/web/api/formatters/json/README.md)|application/json|a Google `datatable`|
+| `html`|[csv](https://github.com/netdata/netdata/blob/master/web/api/formatters/csv/README.md)|text/html|an html table|
+| `json`|[json](https://github.com/netdata/netdata/blob/master/web/api/formatters/json/README.md)|application/json|a JSON object|
+| `jsonp`|[json](https://github.com/netdata/netdata/blob/master/web/api/formatters/json/README.md)|application/json|a JSONP javascript callback|
+| `markdown`|[csv](https://github.com/netdata/netdata/blob/master/web/api/formatters/csv/README.md)|text/plain|a markdown table|
+| `ssv`|[ssv](https://github.com/netdata/netdata/blob/master/web/api/formatters/ssv/README.md)|text/plain|a space separated list of values|
+| `ssvcomma`|[ssv](https://github.com/netdata/netdata/blob/master/web/api/formatters/ssv/README.md)|text/plain|a comma separated list of values|
+| `tsv`|[csv](https://github.com/netdata/netdata/blob/master/web/api/formatters/csv/README.md)|text/plain|a TAB delimited `csv` (MS Excel flavor)|
For examples of each format, check the relative module documentation.
diff --git a/web/api/formatters/charts2json.c b/web/api/formatters/charts2json.c
index 1fc20b49..61a9ecf2 100644
--- a/web/api/formatters/charts2json.c
+++ b/web/api/formatters/charts2json.c
@@ -10,7 +10,7 @@ const char* get_release_channel() {
if (use_stable == -1) {
char filename[FILENAME_MAX + 1];
snprintfz(filename, FILENAME_MAX, "%s/.environment", netdata_configured_user_config_dir);
- procfile *ff = procfile_open(filename, "=", PROCFILE_FLAG_DEFAULT);
+ procfile *ff = procfile_open(filename, "=", PROCFILE_FLAG_ERROR_ON_ERROR_LOG);
if (ff) {
procfile_set_quotes(ff, "'\"");
ff = procfile_readall(ff);
@@ -78,7 +78,7 @@ void charts2json(RRDHOST *host, BUFFER *wb, int skip_volatile, int show_archived
rrdset2json(st, wb, &dimensions, &memory, skip_volatile);
c++;
- st->last_accessed_time = now;
+ st->last_accessed_time_s = now;
}
}
rrdset_foreach_done(st);
@@ -102,10 +102,10 @@ void charts2json(RRDHOST *host, BUFFER *wb, int skip_volatile, int show_archived
, dimensions
, alarms
, memory
- , rrd_hosts_available
+ , rrdhost_hosts_available()
);
- if(unlikely(rrd_hosts_available > 1)) {
+ if(unlikely(rrdhost_hosts_available() > 1)) {
rrd_rdlock();
size_t found = 0;
@@ -178,7 +178,7 @@ void chartcollectors2json(RRDHOST *host, BUFFER *wb) {
};
sprintf(name, "%s:%s", col.plugin, col.module);
dictionary_set(dict, name, &col, sizeof(struct collector));
- st->last_accessed_time = now;
+ st->last_accessed_time_s = now;
}
}
rrdset_foreach_done(st);
diff --git a/web/api/formatters/csv/README.md b/web/api/formatters/csv/README.md
index df7c11ef..fc5ffec1 100644
--- a/web/api/formatters/csv/README.md
+++ b/web/api/formatters/csv/README.md
@@ -5,7 +5,7 @@ custom_edit_url: https://github.com/netdata/netdata/edit/master/web/api/formatte
# CSV formatter
-The CSV formatter presents [results of database queries](/web/api/queries/README.md) in the following formats:
+The CSV formatter presents [results of database queries](https://github.com/netdata/netdata/blob/master/web/api/queries/README.md) in the following formats:
| format|content type|description|
| :----:|:----------:|:----------|
diff --git a/web/api/formatters/json/README.md b/web/api/formatters/json/README.md
index a0f8108e..75f729ad 100644
--- a/web/api/formatters/json/README.md
+++ b/web/api/formatters/json/README.md
@@ -5,7 +5,7 @@ custom_edit_url: https://github.com/netdata/netdata/edit/master/web/api/formatte
# JSON formatter
-The CSV formatter presents [results of database queries](/web/api/queries/README.md) in the following formats:
+The CSV formatter presents [results of database queries](https://github.com/netdata/netdata/blob/master/web/api/queries/README.md) in the following formats:
| format | content type | description|
|:----:|:----------:|:----------|
diff --git a/web/api/formatters/json_wrapper.c b/web/api/formatters/json_wrapper.c
index 8b9b7522..beb74912 100644
--- a/web/api/formatters/json_wrapper.c
+++ b/web/api/formatters/json_wrapper.c
@@ -32,6 +32,53 @@ static int fill_formatted_callback(const char *name, const char *value, RRDLABEL
return 1;
}
+void rrdr_show_plan(RRDR *r, BUFFER *wb, const char *kq, const char *sq __maybe_unused) {
+ QUERY_TARGET *qt = r->internal.qt;
+
+ buffer_sprintf(wb, "\n\t%squery_plan%s: {", kq, kq);
+
+ for(size_t m = 0; m < qt->query.used; m++) {
+ QUERY_METRIC *qm = &qt->query.array[m];
+
+ if(m)
+ buffer_strcat(wb, ",");
+
+ buffer_sprintf(wb, "\n\t\t%s%s%s: {", kq, string2str(qm->dimension.id), kq);
+
+ buffer_sprintf(wb, "\n\t\t\t%splans%s: [", kq, kq);
+ for(size_t p = 0; p < qm->plan.used ;p++) {
+ QUERY_PLAN_ENTRY *qp = &qm->plan.array[p];
+ if(p)
+ buffer_strcat(wb, ",");
+
+ buffer_strcat(wb, "\n\t\t\t\t{");
+ buffer_sprintf(wb, "\n\t\t\t\t\t%stier%s: %zu,", kq, kq, qp->tier);
+ buffer_sprintf(wb, "\n\t\t\t\t\t%safter%s: %ld,", kq, kq, qp->after);
+ buffer_sprintf(wb, "\n\t\t\t\t\t%sbefore%s: %ld", kq, kq, qp->before);
+ buffer_strcat(wb, "\n\t\t\t\t}");
+ }
+ buffer_strcat(wb, "\n\t\t\t],");
+
+ buffer_sprintf(wb, "\n\t\t\t%stiers%s: [", kq, kq);
+ for(size_t tier = 0; tier < storage_tiers ;tier++) {
+ if(tier)
+ buffer_strcat(wb, ",");
+
+ buffer_strcat(wb, "\n\t\t\t\t{");
+ buffer_sprintf(wb, "\n\t\t\t\t\t%stier%s: %zu,", kq, kq, tier);
+ buffer_sprintf(wb, "\n\t\t\t\t\t%sdb_first_time%s: %ld,", kq, kq, qm->tiers[tier].db_first_time_s);
+ buffer_sprintf(wb, "\n\t\t\t\t\t%sdb_last_time%s: %ld,", kq, kq, qm->tiers[tier].db_last_time_s);
+ buffer_sprintf(wb, "\n\t\t\t\t\t%sweight%s: %ld", kq, kq, qm->tiers[tier].weight);
+ buffer_strcat(wb, "\n\t\t\t\t}");
+ }
+ buffer_strcat(wb, "\n\t\t\t]");
+
+ buffer_strcat(wb, "\n\t\t}");
+ }
+
+ buffer_strcat(wb, "\n\t},");
+}
+
void rrdr_json_wrapper_begin(RRDR *r, BUFFER *wb, uint32_t format, RRDR_OPTIONS options, int string_value,
RRDR_GROUPING group_method)
{
@@ -70,9 +117,9 @@ void rrdr_json_wrapper_begin(RRDR *r, BUFFER *wb, uint32_t format, RRDR_OPTIONS
, kq, kq, sq, qt->id, sq
, kq, kq, sq, qt->id, sq
, kq, kq, (long long)r->update_every
- , kq, kq, (long long)qt->db.minimum_latest_update_every
- , kq, kq, (long long)qt->db.first_time_t
- , kq, kq, (long long)qt->db.last_time_t
+ , kq, kq, (long long)qt->db.minimum_latest_update_every_s
+ , kq, kq, (long long)qt->db.first_time_s
+ , kq, kq, (long long)qt->db.last_time_s
, kq, kq, (long long)r->before
, kq, kq, (long long)r->after
, kq, kq, sq, web_client_api_request_v1_data_group_to_string(group_method), sq
@@ -369,9 +416,12 @@ void rrdr_json_wrapper_begin(RRDR *r, BUFFER *wb, uint32_t format, RRDR_OPTIONS
for(size_t tier = 0; tier < storage_tiers ; tier++)
buffer_sprintf(wb, "%s%zu", tier>0?", ":"", r->internal.tier_points_read[tier]);
- buffer_strcat(wb, " ]");
+ buffer_strcat(wb, " ],");
+
+ if(options & RRDR_OPTION_SHOW_PLAN)
+ rrdr_show_plan(r, wb, kq, sq);
- buffer_sprintf(wb, ",\n %sresult%s: ", kq, kq);
+ buffer_sprintf(wb, "\n %sresult%s: ", kq, kq);
if(string_value) buffer_strcat(wb, sq);
//info("JSONWRAPPER(): %s: END", r->st->id);
diff --git a/web/api/formatters/rrd2json.c b/web/api/formatters/rrd2json.c
index 8bf54719..64cde5b2 100644
--- a/web/api/formatters/rrd2json.c
+++ b/web/api/formatters/rrd2json.c
@@ -77,6 +77,7 @@ int rrdset2value_api_v1(
, time_t timeout
, size_t tier
, QUERY_SOURCE query_source
+ , STORAGE_PRIORITY priority
) {
int ret = HTTP_RESP_INTERNAL_SERVER_ERROR;
@@ -94,7 +95,8 @@ int rrdset2value_api_v1(
group_options,
timeout,
tier,
- query_source);
+ query_source,
+ priority);
if(!r) {
if(value_is_null) *value_is_null = 1;
diff --git a/web/api/formatters/rrd2json.h b/web/api/formatters/rrd2json.h
index 048281d7..88b9f773 100644
--- a/web/api/formatters/rrd2json.h
+++ b/web/api/formatters/rrd2json.h
@@ -79,6 +79,7 @@ int rrdset2value_api_v1(
, time_t timeout
, size_t tier
, QUERY_SOURCE query_source
+ , STORAGE_PRIORITY priority
);
#endif /* NETDATA_RRD2JSON_H */
diff --git a/web/api/formatters/rrdset2json.c b/web/api/formatters/rrdset2json.c
index 1e810633..449d4ddf 100644
--- a/web/api/formatters/rrdset2json.c
+++ b/web/api/formatters/rrdset2json.c
@@ -25,8 +25,8 @@ void chart_labels2json(RRDSET *st, BUFFER *wb, size_t indentation)
// generate JSON for the /api/v1/chart API call
void rrdset2json(RRDSET *st, BUFFER *wb, size_t *dimensions_count, size_t *memory_used, int skip_volatile) {
- time_t first_entry_t = rrdset_first_entry_t(st);
- time_t last_entry_t = rrdset_last_entry_t(st);
+ time_t first_entry_t = rrdset_first_entry_s(st);
+ time_t last_entry_t = rrdset_last_entry_s(st);
buffer_sprintf(
wb,
@@ -83,7 +83,7 @@ void rrdset2json(RRDSET *st, BUFFER *wb, size_t *dimensions_count, size_t *memor
"\t\t\t\"dimensions\": {\n",
st->update_every);
- unsigned long memory = sizeof(RRDSET) + st->memsize;
+ unsigned long memory = sizeof(RRDSET);
size_t dimensions = 0;
RRDDIM *rd;
diff --git a/web/api/formatters/ssv/README.md b/web/api/formatters/ssv/README.md
index d9e193d6..4ca2a64c 100644
--- a/web/api/formatters/ssv/README.md
+++ b/web/api/formatters/ssv/README.md
@@ -5,7 +5,7 @@ custom_edit_url: https://github.com/netdata/netdata/edit/master/web/api/formatte
# SSV formatter
-The SSV formatter sums all dimensions in [results of database queries](/web/api/queries/README.md)
+The SSV formatter sums all dimensions in [results of database queries](https://github.com/netdata/netdata/blob/master/web/api/queries/README.md)
to a single value and returns a list of such values showing how it changes through time.
It supports the following formats:
diff --git a/web/api/formatters/value/README.md b/web/api/formatters/value/README.md
index a51e32de..5b75ded7 100644
--- a/web/api/formatters/value/README.md
+++ b/web/api/formatters/value/README.md
@@ -5,7 +5,7 @@ custom_edit_url: https://github.com/netdata/netdata/edit/master/web/api/formatte
# Value formatter
-The Value formatter presents [results of database queries](/web/api/queries/README.md) as a single value.
+The Value formatter presents [results of database queries](https://github.com/netdata/netdata/blob/master/web/api/queries/README.md) as a single value.
To calculate the single value to be returned, it sums the values of all dimensions.
@@ -18,7 +18,7 @@ The Value formatter respects the following API `&options=`:
| `min2max` | yes | to return the delta from the minimum value to the maximum value (across dimensions)|
The Value formatter is not exposed by the API by itself.
-Instead it is used by the [`ssv`](/web/api/formatters/ssv/README.md) formatter
-and [health monitoring queries](/health/README.md).
+Instead it is used by the [`ssv`](https://github.com/netdata/netdata/blob/master/web/api/formatters/ssv/README.md) formatter
+and [health monitoring queries](https://github.com/netdata/netdata/blob/master/health/README.md).
diff --git a/web/api/formatters/value/value.c b/web/api/formatters/value/value.c
index 46a71303..915d58ac 100644
--- a/web/api/formatters/value/value.c
+++ b/web/api/formatters/value/value.c
@@ -106,7 +106,7 @@ QUERY_VALUE rrdmetric2value(RRDHOST *host,
struct rrdcontext_acquired *rca, struct rrdinstance_acquired *ria, struct rrdmetric_acquired *rma,
time_t after, time_t before,
RRDR_OPTIONS options, RRDR_GROUPING group_method, const char *group_options,
- size_t tier, time_t timeout, QUERY_SOURCE query_source
+ size_t tier, time_t timeout, QUERY_SOURCE query_source, STORAGE_PRIORITY priority
) {
QUERY_TARGET_REQUEST qtr = {
.host = host,
@@ -122,6 +122,7 @@ QUERY_VALUE rrdmetric2value(RRDHOST *host,
.tier = tier,
.timeout = timeout,
.query_source = query_source,
+ .priority = priority,
};
ONEWAYALLOC *owa = onewayalloc_create(16 * 1024);
diff --git a/web/api/formatters/value/value.h b/web/api/formatters/value/value.h
index 76b1869f..3f7f51cc 100644
--- a/web/api/formatters/value/value.h
+++ b/web/api/formatters/value/value.h
@@ -23,7 +23,7 @@ QUERY_VALUE rrdmetric2value(RRDHOST *host,
struct rrdcontext_acquired *rca, struct rrdinstance_acquired *ria, struct rrdmetric_acquired *rma,
time_t after, time_t before,
RRDR_OPTIONS options, RRDR_GROUPING group_method, const char *group_options,
- size_t tier, time_t timeout, QUERY_SOURCE query_source
+ size_t tier, time_t timeout, QUERY_SOURCE query_source, STORAGE_PRIORITY priority
);
NETDATA_DOUBLE rrdr2value(RRDR *r, long i, RRDR_OPTIONS options, int *all_values_are_null, NETDATA_DOUBLE *anomaly_rate);
diff --git a/web/api/health/README.md b/web/api/health/README.md
index 9ec8f31c..bfdd0ac6 100644
--- a/web/api/health/README.md
+++ b/web/api/health/README.md
@@ -72,7 +72,7 @@ You can access the API via GET requests, by adding the bearer token to an `Autho
curl "http://NODE:19999/api/v1/manage/health?cmd=RESET" -H "X-Auth-Token: Mytoken"
```
-By default access to the health management API is only allowed from `localhost`. Accessing the API from anything else will return a 403 error with the message `You are not allowed to access this resource.`. You can change permissions by editing the `allow management from` variable in `netdata.conf` within the [web] section. See [web server access lists](/web/server/README.md#access-lists) for more information.
+By default access to the health management API is only allowed from `localhost`. Accessing the API from anything else will return a 403 error with the message `You are not allowed to access this resource.`. You can change permissions by editing the `allow management from` variable in `netdata.conf` within the [web] section. See [web server access lists](https://github.com/netdata/netdata/blob/master/web/server/README.md#access-lists) for more information.
The command `RESET` just returns Netdata to the default operation, with all health checks and notifications enabled.
If you've configured and entered your token correctly, you should see the plain text response `All health checks and notifications are enabled`.
@@ -126,7 +126,7 @@ curl "http://NODE:19999/api/v1/manage/health?cmd=SILENCE&context=load" -H "X-Aut
#### Selection criteria
-The `selection criteria` are key/value pairs, in the format `key : value`, where value is a Netdata [simple pattern](/libnetdata/simple_pattern/README.md). This means that you can create very powerful selectors (you will rarely need more than one or two).
+The `selection criteria` are key/value pairs, in the format `key : value`, where value is a Netdata [simple pattern](https://github.com/netdata/netdata/blob/master/libnetdata/simple_pattern/README.md). This means that you can create very powerful selectors (you will rarely need more than one or two).
The accepted keys for the `selection criteria` are the following:
@@ -220,6 +220,6 @@ The file's location is configurable in `netdata.conf`. The default is shown belo
### Further reading
-The test script under [tests/health_mgmtapi](/tests/health_mgmtapi/README.md) contains a series of tests that you can either run or read through to understand the various calls and responses better.
+The test script under [tests/health_mgmtapi](https://github.com/netdata/netdata/blob/master/tests/health_mgmtapi/README.md) contains a series of tests that you can either run or read through to understand the various calls and responses better.
diff --git a/web/api/health/health_cmdapi.c b/web/api/health/health_cmdapi.c
index bad3e960..7a939bc0 100644
--- a/web/api/health/health_cmdapi.c
+++ b/web/api/health/health_cmdapi.c
@@ -196,7 +196,7 @@ int web_client_api_request_v1_mgmt_health(RRDHOST *host, struct web_client *w, c
w->response.data = wb;
buffer_no_cacheable(w->response.data);
if (ret == HTTP_RESP_OK && config_changed) {
- BUFFER *jsonb = buffer_create(200);
+ BUFFER *jsonb = buffer_create(200, &netdata_buffers_statistics.buffers_health);
health_silencers2json(jsonb);
health_silencers2file(jsonb);
buffer_free(jsonb);
diff --git a/web/api/queries/README.md b/web/api/queries/README.md
index 44cdd05b..2a17ac78 100644
--- a/web/api/queries/README.md
+++ b/web/api/queries/README.md
@@ -88,7 +88,7 @@ To disable alignment, pass `&options=unaligned` to the query.
To execute the query, the engine evaluates all dimensions of the chart, one after another.
-The engine does not evaluate dimensions that do not match the [simple pattern](/libnetdata/simple_pattern/README.md)
+The engine does not evaluate dimensions that do not match the [simple pattern](https://github.com/netdata/netdata/blob/master/libnetdata/simple_pattern/README.md)
given at the `dimensions` parameter, except when `options=percentage` is given (this option
requires all the dimensions to be evaluated to find the percentage of each dimension vs to chart
total).
diff --git a/web/api/queries/query.c b/web/api/queries/query.c
index 0365b6e9..1f10c513 100644
--- a/web/api/queries/query.c
+++ b/web/api/queries/query.c
@@ -17,6 +17,8 @@
#include "percentile/percentile.h"
#include "trimmed_mean/trimmed_mean.h"
+#define POINTS_TO_EXPAND_QUERY 5
+
// ----------------------------------------------------------------------------
static struct {
@@ -694,7 +696,7 @@ static inline void rrdr_done(RRDR *r, long rrdr_line) {
// tier management
static bool query_metric_is_valid_tier(QUERY_METRIC *qm, size_t tier) {
- if(!qm->tiers[tier].db_metric_handle || !qm->tiers[tier].db_first_time_t || !qm->tiers[tier].db_last_time_t || !qm->tiers[tier].db_update_every)
+ if(!qm->tiers[tier].db_metric_handle || !qm->tiers[tier].db_first_time_s || !qm->tiers[tier].db_last_time_s || !qm->tiers[tier].db_update_every_s)
return false;
return true;
@@ -705,11 +707,11 @@ static size_t query_metric_first_working_tier(QUERY_METRIC *qm) {
// find the db time-range for this tier for all metrics
STORAGE_METRIC_HANDLE *db_metric_handle = qm->tiers[tier].db_metric_handle;
- time_t first_t = qm->tiers[tier].db_first_time_t;
- time_t last_t = qm->tiers[tier].db_last_time_t;
- time_t update_every = qm->tiers[tier].db_update_every;
+ time_t first_time_s = qm->tiers[tier].db_first_time_s;
+ time_t last_time_s = qm->tiers[tier].db_last_time_s;
+ time_t update_every_s = qm->tiers[tier].db_update_every_s;
- if(!db_metric_handle || !first_t || !last_t || !update_every)
+ if(!db_metric_handle || !first_time_s || !last_time_s || !update_every_s)
continue;
return tier;
@@ -718,19 +720,23 @@ static size_t query_metric_first_working_tier(QUERY_METRIC *qm) {
return 0;
}
-static long query_plan_points_coverage_weight(time_t db_first_t, time_t db_last_t, time_t db_update_every, time_t after_wanted, time_t before_wanted, size_t points_wanted, size_t tier __maybe_unused) {
- if(db_first_t == 0 || db_last_t == 0 || db_update_every == 0)
+static long query_plan_points_coverage_weight(time_t db_first_time_s, time_t db_last_time_s, time_t db_update_every_s, time_t after_wanted, time_t before_wanted, size_t points_wanted, size_t tier __maybe_unused) {
+ if(db_first_time_s == 0 ||
+ db_last_time_s == 0 ||
+ db_update_every_s == 0 ||
+ db_first_time_s > before_wanted ||
+ db_last_time_s < after_wanted)
return -LONG_MAX;
- time_t common_first_t = MAX(db_first_t, after_wanted);
- time_t common_last_t = MIN(db_last_t, before_wanted);
+ long long common_first_t = MAX(db_first_time_s, after_wanted);
+ long long common_last_t = MIN(db_last_time_s, before_wanted);
- long time_coverage = (common_last_t - common_first_t) * 1000000 / (before_wanted - after_wanted);
- size_t points_wanted_in_coverage = points_wanted * time_coverage / 1000000;
+ long long time_coverage = (common_last_t - common_first_t) * 1000000LL / (before_wanted - after_wanted);
+ long long points_wanted_in_coverage = (long long)points_wanted * time_coverage / 1000000LL;
- long points_available = (common_last_t - common_first_t) / db_update_every;
- long points_delta = (long)(points_available - points_wanted_in_coverage);
- long points_coverage = (points_delta < 0) ? (long)(points_available * time_coverage / points_wanted_in_coverage) : time_coverage;
+ long long points_available = (common_last_t - common_first_t) / db_update_every_s;
+ long long points_delta = (long)(points_available - points_wanted_in_coverage);
+ long long points_coverage = (points_delta < 0) ? (long)(points_available * time_coverage / points_wanted_in_coverage) : time_coverage;
// a way to benefit higher tiers
// points_coverage += (long)tier * 10000;
@@ -738,7 +744,7 @@ static long query_plan_points_coverage_weight(time_t db_first_t, time_t db_last_
if(points_available <= 0)
return -LONG_MAX;
- return points_coverage;
+ return (long)(points_coverage + (25000LL * tier)); // 2.5% benefit for each higher tier
}
static size_t query_metric_best_tier_for_timeframe(QUERY_METRIC *qm, time_t after_wanted, time_t before_wanted, size_t points_wanted) {
@@ -748,27 +754,49 @@ static size_t query_metric_best_tier_for_timeframe(QUERY_METRIC *qm, time_t afte
if(unlikely(after_wanted == before_wanted || points_wanted <= 0))
return query_metric_first_working_tier(qm);
- long weight[storage_tiers];
+ time_t min_first_time_s = 0;
+ time_t max_last_time_s = 0;
+
+ for(size_t tier = 0; tier < storage_tiers ; tier++) {
+ time_t first_time_s = qm->tiers[tier].db_first_time_s;
+ time_t last_time_s = qm->tiers[tier].db_last_time_s;
+
+ if(!min_first_time_s || (first_time_s && first_time_s < min_first_time_s))
+ min_first_time_s = first_time_s;
+
+ if(!max_last_time_s || (last_time_s && last_time_s > max_last_time_s))
+ max_last_time_s = last_time_s;
+ }
for(size_t tier = 0; tier < storage_tiers ; tier++) {
// find the db time-range for this tier for all metrics
STORAGE_METRIC_HANDLE *db_metric_handle = qm->tiers[tier].db_metric_handle;
- time_t first_t = qm->tiers[tier].db_first_time_t;
- time_t last_t = qm->tiers[tier].db_last_time_t;
- time_t update_every = qm->tiers[tier].db_update_every;
-
- if(!db_metric_handle || !first_t || !last_t || !update_every) {
- weight[tier] = -LONG_MAX;
+ time_t first_time_s = qm->tiers[tier].db_first_time_s;
+ time_t last_time_s = qm->tiers[tier].db_last_time_s;
+ time_t update_every_s = qm->tiers[tier].db_update_every_s;
+
+ if( !db_metric_handle ||
+ !first_time_s ||
+ !last_time_s ||
+ !update_every_s ||
+ first_time_s > before_wanted ||
+ last_time_s < after_wanted
+ ) {
+ qm->tiers[tier].weight = -LONG_MAX;
continue;
}
- weight[tier] = query_plan_points_coverage_weight(first_t, last_t, update_every, after_wanted, before_wanted, points_wanted, tier);
+ internal_fatal(first_time_s > before_wanted || last_time_s < after_wanted, "QUERY: invalid db durations");
+
+ qm->tiers[tier].weight = query_plan_points_coverage_weight(
+ min_first_time_s, max_last_time_s, update_every_s,
+ after_wanted, before_wanted, points_wanted, tier);
}
size_t best_tier = 0;
for(size_t tier = 1; tier < storage_tiers ; tier++) {
- if(weight[tier] >= weight[best_tier])
+ if(qm->tiers[tier].weight >= qm->tiers[best_tier].weight)
best_tier = tier;
}
@@ -788,38 +816,38 @@ static size_t rrddim_find_best_tier_for_timeframe(QUERY_TARGET *qt, time_t after
for(size_t tier = 0; tier < storage_tiers ; tier++) {
- time_t common_first_t = 0;
- time_t common_last_t = 0;
- time_t common_update_every = 0;
+ time_t common_first_time_s = 0;
+ time_t common_last_time_s = 0;
+ time_t common_update_every_s = 0;
// find the db time-range for this tier for all metrics
for(size_t i = 0, used = qt->query.used; i < used ; i++) {
QUERY_METRIC *qm = &qt->query.array[i];
- time_t first_t = qm->tiers[tier].db_first_time_t;
- time_t last_t = qm->tiers[tier].db_last_time_t;
- time_t update_every = qm->tiers[tier].db_update_every;
+ time_t first_time_s = qm->tiers[tier].db_first_time_s;
+ time_t last_time_s = qm->tiers[tier].db_last_time_s;
+ time_t update_every_s = qm->tiers[tier].db_update_every_s;
- if(!first_t || !last_t || !update_every)
+ if(!first_time_s || !last_time_s || !update_every_s)
continue;
- if(!common_first_t)
- common_first_t = first_t;
+ if(!common_first_time_s)
+ common_first_time_s = first_time_s;
else
- common_first_t = MIN(first_t, common_first_t);
+ common_first_time_s = MIN(first_time_s, common_first_time_s);
- if(!common_last_t)
- common_last_t = last_t;
+ if(!common_last_time_s)
+ common_last_time_s = last_time_s;
else
- common_last_t = MAX(last_t, common_last_t);
+ common_last_time_s = MAX(last_time_s, common_last_time_s);
- if(!common_update_every)
- common_update_every = update_every;
+ if(!common_update_every_s)
+ common_update_every_s = update_every_s;
else
- common_update_every = MIN(update_every, common_update_every);
+ common_update_every_s = MIN(update_every_s, common_update_every_s);
}
- weight[tier] = query_plan_points_coverage_weight(common_first_t, common_last_t, common_update_every, after_wanted, before_wanted, points_wanted, tier);
+ weight[tier] = query_plan_points_coverage_weight(common_first_time_s, common_last_time_s, common_update_every_s, after_wanted, before_wanted, points_wanted, tier);
}
size_t best_tier = 0;
@@ -842,19 +870,19 @@ static time_t rrdset_find_natural_update_every_for_timeframe(QUERY_TARGET *qt, t
best_tier = rrddim_find_best_tier_for_timeframe(qt, after_wanted, before_wanted, points_wanted);
// find the db minimum update every for this tier for all metrics
- time_t common_update_every = default_rrd_update_every;
+ time_t common_update_every_s = default_rrd_update_every;
for(size_t i = 0, used = qt->query.used; i < used ; i++) {
QUERY_METRIC *qm = &qt->query.array[i];
- time_t update_every = qm->tiers[best_tier].db_update_every;
+ time_t update_every_s = qm->tiers[best_tier].db_update_every_s;
if(!i)
- common_update_every = update_every;
+ common_update_every_s = update_every_s;
else
- common_update_every = MIN(update_every, common_update_every);
+ common_update_every_s = MIN(update_every_s, common_update_every_s);
}
- return common_update_every;
+ return common_update_every_s;
}
// ----------------------------------------------------------------------------
@@ -888,17 +916,6 @@ QUERY_POINT QUERY_POINT_EMPTY = {
#define query_point_set_id(point, point_id) debug_dummy()
#endif
-typedef struct query_plan_entry {
- size_t tier;
- time_t after;
- time_t before;
-} QUERY_PLAN_ENTRY;
-
-typedef struct query_plan {
- size_t entries;
- QUERY_PLAN_ENTRY data[RRD_STORAGE_TIERS*2];
-} QUERY_PLAN;
-
typedef struct query_engine_ops {
// configuration
RRDR *r;
@@ -908,14 +925,15 @@ typedef struct query_engine_ops {
TIER_QUERY_FETCH tier_query_fetch;
// query planer
- QUERY_PLAN plan;
size_t current_plan;
time_t current_plan_expire_time;
+ time_t plan_expanded_after;
+ time_t plan_expanded_before;
// storage queries
size_t tier;
struct query_metric_tier *tier_ptr;
- struct storage_engine_query_handle handle;
+ struct storage_engine_query_handle *handle;
STORAGE_POINT (*next_metric)(struct storage_engine_query_handle *handle);
int (*is_finished)(struct storage_engine_query_handle *handle);
void (*finalize)(struct storage_engine_query_handle *handle);
@@ -937,31 +955,128 @@ typedef struct query_engine_ops {
// ----------------------------------------------------------------------------
// query planer
-#define query_plan_should_switch_plan(ops, now) ((now) >= (ops).current_plan_expire_time)
+#define query_plan_should_switch_plan(ops, now) ((now) >= (ops)->current_plan_expire_time)
+
+static size_t query_planer_expand_duration_in_points(time_t this_update_every, time_t next_update_every) {
+
+ time_t delta = this_update_every - next_update_every;
+ if(delta < 0) delta = -delta;
+
+ size_t points;
+ if(delta < this_update_every * POINTS_TO_EXPAND_QUERY)
+ points = POINTS_TO_EXPAND_QUERY;
+ else
+ points = (delta + this_update_every - 1) / this_update_every;
+
+ return points;
+}
-static void query_planer_activate_plan(QUERY_ENGINE_OPS *ops, size_t plan_id, time_t overwrite_after) {
- if(unlikely(plan_id >= ops->plan.entries))
- plan_id = ops->plan.entries - 1;
+static void query_planer_initialize_plans(QUERY_ENGINE_OPS *ops) {
+ QUERY_METRIC *qm = ops->qm;
+
+ for(size_t p = 0; p < qm->plan.used ; p++) {
+ size_t tier = qm->plan.array[p].tier;
+ time_t update_every = qm->tiers[tier].db_update_every_s;
+
+ size_t points_to_add_to_after;
+ if(p > 0) {
+ // there is another plan before to this
+
+ size_t tier0 = qm->plan.array[p - 1].tier;
+ time_t update_every0 = qm->tiers[tier0].db_update_every_s;
+
+ points_to_add_to_after = query_planer_expand_duration_in_points(update_every, update_every0);
+ }
+ else
+ points_to_add_to_after = (tier == 0) ? 0 : POINTS_TO_EXPAND_QUERY;
- time_t after = ops->plan.data[plan_id].after;
- time_t before = ops->plan.data[plan_id].before;
+ size_t points_to_add_to_before;
+ if(p + 1 < qm->plan.used) {
+ // there is another plan after to this
- if(overwrite_after > after && overwrite_after < before)
- after = overwrite_after;
+ size_t tier1 = qm->plan.array[p+1].tier;
+ time_t update_every1 = qm->tiers[tier1].db_update_every_s;
- ops->tier = ops->plan.data[plan_id].tier;
- ops->tier_ptr = &ops->qm->tiers[ops->tier];
- ops->tier_ptr->eng->api.query_ops.init(ops->tier_ptr->db_metric_handle, &ops->handle, after, before);
- ops->next_metric = ops->tier_ptr->eng->api.query_ops.next_metric;
- ops->is_finished = ops->tier_ptr->eng->api.query_ops.is_finished;
- ops->finalize = ops->tier_ptr->eng->api.query_ops.finalize;
+ points_to_add_to_before = query_planer_expand_duration_in_points(update_every, update_every1);
+ }
+ else
+ points_to_add_to_before = POINTS_TO_EXPAND_QUERY;
+
+ time_t after = qm->plan.array[p].after - (time_t)(update_every * points_to_add_to_after);
+ time_t before = qm->plan.array[p].before + (time_t)(update_every * points_to_add_to_before);
+
+ qm->plan.array[p].expanded_after = after;
+ qm->plan.array[p].expanded_before = before;
+
+ struct query_metric_tier *tier_ptr = &qm->tiers[tier];
+ tier_ptr->eng->api.query_ops.init(
+ tier_ptr->db_metric_handle,
+ &qm->plan.array[p].handle,
+ after, before,
+ ops->r->internal.qt->request.priority);
+
+ qm->plan.array[p].next_metric = tier_ptr->eng->api.query_ops.next_metric;
+ qm->plan.array[p].is_finished = tier_ptr->eng->api.query_ops.is_finished;
+ qm->plan.array[p].finalize = tier_ptr->eng->api.query_ops.finalize;
+ qm->plan.array[p].initialized = true;
+ qm->plan.array[p].finalized = false;
+ }
+}
+
+static void query_planer_finalize_plan(QUERY_ENGINE_OPS *ops, size_t plan_id) {
+ QUERY_METRIC *qm = ops->qm;
+
+ if(qm->plan.array[plan_id].initialized && !qm->plan.array[plan_id].finalized) {
+ qm->plan.array[plan_id].finalize(&qm->plan.array[plan_id].handle);
+ qm->plan.array[plan_id].initialized = false;
+ qm->plan.array[plan_id].finalized = true;
+ qm->plan.array[plan_id].next_metric = NULL;
+ qm->plan.array[plan_id].is_finished = NULL;
+ qm->plan.array[plan_id].finalize = NULL;
+
+ if(ops->current_plan == plan_id) {
+ ops->next_metric = NULL;
+ ops->is_finished = NULL;
+ ops->finalize = NULL;
+ }
+ }
+}
+
+static void query_planer_finalize_remaining_plans(QUERY_ENGINE_OPS *ops) {
+ QUERY_METRIC *qm = ops->qm;
+
+ for(size_t p = 0; p < qm->plan.used ; p++)
+ query_planer_finalize_plan(ops, p);
+}
+
+static void query_planer_activate_plan(QUERY_ENGINE_OPS *ops, size_t plan_id, time_t overwrite_after __maybe_unused) {
+ QUERY_METRIC *qm = ops->qm;
+
+ internal_fatal(plan_id >= qm->plan.used, "QUERY: invalid plan_id given");
+ internal_fatal(!qm->plan.array[plan_id].initialized, "QUERY: plan has not been initialized");
+ internal_fatal(qm->plan.array[plan_id].finalized, "QUERY: plan has been finalized");
+
+ internal_fatal(qm->plan.array[plan_id].after > qm->plan.array[plan_id].before, "QUERY: flipped after/before");
+
+ ops->tier = qm->plan.array[plan_id].tier;
+ ops->tier_ptr = &qm->tiers[ops->tier];
+ ops->handle = &qm->plan.array[plan_id].handle;
+ ops->next_metric = qm->plan.array[plan_id].next_metric;
+ ops->is_finished = qm->plan.array[plan_id].is_finished;
+ ops->finalize = qm->plan.array[plan_id].finalize;
ops->current_plan = plan_id;
- ops->current_plan_expire_time = ops->plan.data[plan_id].before;
+
+ if(plan_id + 1 < qm->plan.used && qm->plan.array[plan_id + 1].after < qm->plan.array[plan_id].before)
+ ops->current_plan_expire_time = qm->plan.array[plan_id + 1].after;
+ else
+ ops->current_plan_expire_time = qm->plan.array[plan_id].before;
+
+ ops->plan_expanded_after = qm->plan.array[plan_id].expanded_after;
+ ops->plan_expanded_before = qm->plan.array[plan_id].expanded_before;
}
-static void query_planer_next_plan(QUERY_ENGINE_OPS *ops, time_t now, time_t last_point_end_time) {
- internal_error(now < ops->current_plan_expire_time && now < ops->plan.data[ops->current_plan].before,
- "QUERY: switching query plan too early!");
+static bool query_planer_next_plan(QUERY_ENGINE_OPS *ops, time_t now, time_t last_point_end_time) {
+ QUERY_METRIC *qm = ops->qm;
size_t old_plan = ops->current_plan;
@@ -969,32 +1084,26 @@ static void query_planer_next_plan(QUERY_ENGINE_OPS *ops, time_t now, time_t las
do {
ops->current_plan++;
- if (ops->current_plan >= ops->plan.entries) {
+ if (ops->current_plan >= qm->plan.used) {
ops->current_plan = old_plan;
ops->current_plan_expire_time = ops->r->internal.qt->window.before;
// let the query run with current plan
// we will not switch it
- return;
+ return false;
}
- next_plan_before_time = ops->plan.data[ops->current_plan].before;
+ next_plan_before_time = qm->plan.array[ops->current_plan].before;
} while(now >= next_plan_before_time || last_point_end_time >= next_plan_before_time);
- if(!query_metric_is_valid_tier(ops->qm, ops->plan.data[ops->current_plan].tier)) {
+ if(!query_metric_is_valid_tier(qm, qm->plan.array[ops->current_plan].tier)) {
ops->current_plan = old_plan;
ops->current_plan_expire_time = ops->r->internal.qt->window.before;
- return;
- }
-
- if(ops->finalize) {
- ops->finalize(&ops->handle);
- ops->finalize = NULL;
- ops->is_finished = NULL;
+ return false;
}
- // internal_error(true, "QUERY: switched plan to %zu (all is %zu), previous expiration was %ld, this starts at %ld, now is %ld, last_point_end_time %ld", ops->current_plan, ops->plan.entries, ops->plan.data[ops->current_plan-1].before, ops->plan.data[ops->current_plan].after, now, last_point_end_time);
-
+ query_planer_finalize_plan(ops, old_plan);
query_planer_activate_plan(ops, ops->current_plan, MIN(now, last_point_end_time));
+ return true;
}
static int compare_query_plan_entries_on_start_time(const void *a, const void *b) {
@@ -1004,59 +1113,66 @@ static int compare_query_plan_entries_on_start_time(const void *a, const void *b
}
static bool query_plan(QUERY_ENGINE_OPS *ops, time_t after_wanted, time_t before_wanted, size_t points_wanted) {
- //BUFFER *wb = buffer_create(1000);
- //buffer_sprintf(wb, "QUERY PLAN for chart '%s' dimension '%s', from %ld to %ld:", rd->rrdset->name, rd->name, after_wanted, before_wanted);
+ QUERY_METRIC *qm = ops->qm;
// put our selected tier as the first plan
size_t selected_tier;
if(ops->r->internal.query_options & RRDR_OPTION_SELECTED_TIER
&& ops->r->internal.qt->window.tier < storage_tiers
- && query_metric_is_valid_tier(ops->qm, ops->r->internal.qt->window.tier)) {
+ && query_metric_is_valid_tier(qm, ops->r->internal.qt->window.tier)) {
selected_tier = ops->r->internal.qt->window.tier;
}
else {
- selected_tier = query_metric_best_tier_for_timeframe(ops->qm, after_wanted, before_wanted, points_wanted);
+ selected_tier = query_metric_best_tier_for_timeframe(qm, after_wanted, before_wanted, points_wanted);
if(ops->r->internal.query_options & RRDR_OPTION_SELECTED_TIER)
ops->r->internal.query_options &= ~RRDR_OPTION_SELECTED_TIER;
+
+ if(!query_metric_is_valid_tier(qm, selected_tier))
+ return false;
+
+ if(qm->tiers[selected_tier].db_first_time_s > before_wanted ||
+ qm->tiers[selected_tier].db_last_time_s < after_wanted)
+ return false;
}
- ops->plan.entries = 1;
- ops->plan.data[0].tier = selected_tier;
- ops->plan.data[0].after = ops->qm->tiers[selected_tier].db_first_time_t;
- ops->plan.data[0].before = ops->qm->tiers[selected_tier].db_last_time_t;
+ qm->plan.used = 1;
+ qm->plan.array[0].tier = selected_tier;
+ qm->plan.array[0].after = (qm->tiers[selected_tier].db_first_time_s < after_wanted) ? after_wanted : qm->tiers[selected_tier].db_first_time_s;
+ qm->plan.array[0].before = (qm->tiers[selected_tier].db_last_time_s > before_wanted) ? before_wanted : qm->tiers[selected_tier].db_last_time_s;
if(!(ops->r->internal.query_options & RRDR_OPTION_SELECTED_TIER)) {
// the selected tier
- time_t selected_tier_first_time_t = ops->plan.data[0].after;
- time_t selected_tier_last_time_t = ops->plan.data[0].before;
-
- //buffer_sprintf(wb, ": SELECTED tier %zu, from %ld to %ld", selected_tier, ops->plan.data[0].after, ops->plan.data[0].before);
+ time_t selected_tier_first_time_s = qm->plan.array[0].after;
+ time_t selected_tier_last_time_s = qm->plan.array[0].before;
// check if our selected tier can start the query
- if (selected_tier_first_time_t > after_wanted) {
+ if (selected_tier_first_time_s > after_wanted) {
// we need some help from other tiers
for (size_t tr = (int)selected_tier + 1; tr < storage_tiers; tr++) {
- if(!query_metric_is_valid_tier(ops->qm, tr))
+ if(!query_metric_is_valid_tier(qm, tr))
continue;
// find the first time of this tier
- time_t first_time_t = ops->qm->tiers[tr].db_first_time_t;
-
- //buffer_sprintf(wb, ": EVAL AFTER tier %d, %ld", tier, first_time_t);
+ time_t tier_first_time_s = qm->tiers[tr].db_first_time_s;
// can it help?
- if (first_time_t < selected_tier_first_time_t) {
+ if (tier_first_time_s < selected_tier_first_time_s) {
// it can help us add detail at the beginning of the query
QUERY_PLAN_ENTRY t = {
.tier = tr,
- .after = (first_time_t < after_wanted) ? after_wanted : first_time_t,
- .before = selected_tier_first_time_t};
- ops->plan.data[ops->plan.entries++] = t;
+ .after = (tier_first_time_s < after_wanted) ? after_wanted : tier_first_time_s,
+ .before = selected_tier_first_time_s,
+ .initialized = false,
+ .finalized = false,
+ };
+ qm->plan.array[qm->plan.used++] = t;
+
+ internal_fatal(!t.after || !t.before, "QUERY: invalid plan selected");
// prepare for the tier
- selected_tier_first_time_t = t.after;
+ selected_tier_first_time_s = t.after;
if (t.after <= after_wanted)
break;
@@ -1065,28 +1181,33 @@ static bool query_plan(QUERY_ENGINE_OPS *ops, time_t after_wanted, time_t before
}
// check if our selected tier can finish the query
- if (selected_tier_last_time_t < before_wanted) {
+ if (selected_tier_last_time_s < before_wanted) {
// we need some help from other tiers
for (int tr = (int)selected_tier - 1; tr >= 0; tr--) {
- if(!query_metric_is_valid_tier(ops->qm, tr))
+ if(!query_metric_is_valid_tier(qm, tr))
continue;
// find the last time of this tier
- time_t last_time_t = ops->qm->tiers[tr].db_last_time_t;
+ time_t tier_last_time_s = qm->tiers[tr].db_last_time_s;
- //buffer_sprintf(wb, ": EVAL BEFORE tier %d, %ld", tier, last_time_t);
+ //buffer_sprintf(wb, ": EVAL BEFORE tier %d, %ld", tier, last_time_s);
// can it help?
- if (last_time_t > selected_tier_last_time_t) {
+ if (tier_last_time_s > selected_tier_last_time_s) {
// it can help us add detail at the end of the query
QUERY_PLAN_ENTRY t = {
.tier = tr,
- .after = selected_tier_last_time_t,
- .before = (last_time_t > before_wanted) ? before_wanted : last_time_t};
- ops->plan.data[ops->plan.entries++] = t;
+ .after = selected_tier_last_time_s,
+ .before = (tier_last_time_s > before_wanted) ? before_wanted : tier_last_time_s,
+ .initialized = false,
+ .finalized = false,
+ };
+ qm->plan.array[qm->plan.used++] = t;
// prepare for the tier
- selected_tier_last_time_t = t.before;
+ selected_tier_last_time_s = t.before;
+
+ internal_fatal(!t.after || !t.before, "QUERY: invalid plan selected");
if (t.before >= before_wanted)
break;
@@ -1096,26 +1217,21 @@ static bool query_plan(QUERY_ENGINE_OPS *ops, time_t after_wanted, time_t before
}
// sort the query plan
- if(ops->plan.entries > 1)
- qsort(&ops->plan.data, ops->plan.entries, sizeof(QUERY_PLAN_ENTRY), compare_query_plan_entries_on_start_time);
+ if(qm->plan.used > 1)
+ qsort(&qm->plan.array, qm->plan.used, sizeof(QUERY_PLAN_ENTRY), compare_query_plan_entries_on_start_time);
- // make sure it has the whole timeframe we need
- if(ops->plan.data[0].after < after_wanted)
- ops->plan.data[0].after = after_wanted;
-
- if(ops->plan.data[ops->plan.entries - 1].before > before_wanted)
- ops->plan.data[ops->plan.entries - 1].before = before_wanted;
-
- //buffer_sprintf(wb, ": FINAL STEPS %zu", ops->plan.entries);
-
- //for(size_t i = 0; i < ops->plan.entries ;i++)
- // buffer_sprintf(wb, ": STEP %zu = use tier %zu from %ld to %ld", i+1, ops->plan.data[i].tier, ops->plan.data[i].after, ops->plan.data[i].before);
-
- //internal_error(true, "%s", buffer_tostring(wb));
-
- if(!query_metric_is_valid_tier(ops->qm, ops->plan.data[0].tier))
+ if(!query_metric_is_valid_tier(qm, qm->plan.array[0].tier))
return false;
+#ifdef NETDATA_INTERNAL_CHECKS
+ for(size_t p = 0; p < qm->plan.used ;p++) {
+ internal_fatal(qm->plan.array[p].after > qm->plan.array[p].before, "QUERY: flipped after/before");
+ internal_fatal(qm->plan.array[p].after < after_wanted, "QUERY: too small plan first time");
+ internal_fatal(qm->plan.array[p].before > before_wanted, "QUERY: too big plan last time");
+ }
+#endif
+
+ query_planer_initialize_plans(ops);
query_planer_activate_plan(ops, 0, 0);
return true;
@@ -1146,24 +1262,45 @@ static bool query_plan(QUERY_ENGINE_OPS *ops, time_t after_wanted, time_t before
#define query_add_point_to_group(r, point, ops) do { \
if(likely(netdata_double_isnumber((point).value))) { \
if(likely(fpclassify((point).value) != FP_ZERO)) \
- (ops).group_points_non_zero++; \
+ (ops)->group_points_non_zero++; \
\
if(unlikely((point).flags & SN_FLAG_RESET)) \
- (ops).group_value_flags |= RRDR_VALUE_RESET; \
+ (ops)->group_value_flags |= RRDR_VALUE_RESET; \
\
- (ops).grouping_add(r, (point).value); \
+ (ops)->grouping_add(r, (point).value); \
} \
\
- (ops).group_points_added++; \
- (ops).group_anomaly_rate += (point).anomaly; \
+ (ops)->group_points_added++; \
+ (ops)->group_anomaly_rate += (point).anomaly; \
} while(0)
-static inline void rrd2rrdr_do_dimension(RRDR *r, size_t dim_id_in_rrdr) {
+static QUERY_ENGINE_OPS *rrd2rrdr_query_prep(RRDR *r, size_t dim_id_in_rrdr) {
QUERY_TARGET *qt = r->internal.qt;
- QUERY_METRIC *qm = &qt->query.array[dim_id_in_rrdr];
+
+ QUERY_ENGINE_OPS *ops = onewayalloc_mallocz(r->internal.owa, sizeof(QUERY_ENGINE_OPS));
+ *ops = (QUERY_ENGINE_OPS) {
+ .r = r,
+ .qm = &qt->query.array[dim_id_in_rrdr],
+ .grouping_add = r->internal.grouping_add,
+ .grouping_flush = r->internal.grouping_flush,
+ .tier_query_fetch = r->internal.tier_query_fetch,
+ .view_update_every = r->update_every,
+ .query_granularity = (time_t)(r->update_every / r->group),
+ .group_value_flags = RRDR_VALUE_NOTHING,
+ };
+
+ if(!query_plan(ops, qt->window.after, qt->window.before, qt->window.points))
+ return NULL;
+
+ return ops;
+}
+
+static void rrd2rrdr_query_execute(RRDR *r, size_t dim_id_in_rrdr, QUERY_ENGINE_OPS *ops) {
+ QUERY_TARGET *qt = r->internal.qt;
+ QUERY_METRIC *qm = &qt->query.array[dim_id_in_rrdr]; (void)qm;
size_t points_wanted = qt->window.points;
time_t after_wanted = qt->window.after;
- time_t before_wanted = qt->window.before;
+ time_t before_wanted = qt->window.before; (void)before_wanted;
// bool debug_this = false;
// if(strcmp("user", string2str(rd->id)) == 0 && strcmp("system.cpu", string2str(rd->rrdset->id)) == 0)
@@ -1174,39 +1311,30 @@ static inline void rrd2rrdr_do_dimension(RRDR *r, size_t dim_id_in_rrdr) {
size_t points_added = 0;
- QUERY_ENGINE_OPS ops = {
- .r = r,
- .qm = qm,
- .grouping_add = r->internal.grouping_add,
- .grouping_flush = r->internal.grouping_flush,
- .tier_query_fetch = r->internal.tier_query_fetch,
- .view_update_every = r->update_every,
- .query_granularity = (time_t)(r->update_every / r->group),
- .group_value_flags = RRDR_VALUE_NOTHING
- };
-
long rrdr_line = -1;
bool use_anomaly_bit_as_value = (r->internal.query_options & RRDR_OPTION_ANOMALY_BIT) ? true : false;
- if(!query_plan(&ops, after_wanted, before_wanted, points_wanted))
- return;
-
NETDATA_DOUBLE min = r->min, max = r->max;
QUERY_POINT last2_point = QUERY_POINT_EMPTY;
QUERY_POINT last1_point = QUERY_POINT_EMPTY;
QUERY_POINT new_point = QUERY_POINT_EMPTY;
- time_t now_start_time = after_wanted - ops.query_granularity;
- time_t now_end_time = after_wanted + ops.view_update_every - ops.query_granularity;
+ // ONE POINT READ-AHEAD
+ // when we switch plans, we read-ahead a point from the next plan
+ // to join them smoothly at the exact time the next plan begins
+ STORAGE_POINT next1_point = STORAGE_POINT_UNSET;
+
+ time_t now_start_time = after_wanted - ops->query_granularity;
+ time_t now_end_time = after_wanted + ops->view_update_every - ops->query_granularity;
size_t db_points_read_since_plan_switch = 0; (void)db_points_read_since_plan_switch;
// The main loop, based on the query granularity we need
- for( ; points_added < points_wanted ; now_start_time = now_end_time, now_end_time += ops.view_update_every) {
+ for( ; points_added < points_wanted ; now_start_time = now_end_time, now_end_time += ops->view_update_every) {
if(unlikely(query_plan_should_switch_plan(ops, now_end_time))) {
- query_planer_next_plan(&ops, now_end_time, new_point.end_time);
+ query_planer_next_plan(ops, now_end_time, new_point.end_time);
db_points_read_since_plan_switch = 0;
}
@@ -1219,7 +1347,7 @@ static inline void rrd2rrdr_do_dimension(RRDR *r, size_t dim_id_in_rrdr) {
last1_point = new_point;
}
- if(unlikely(ops.is_finished(&ops.handle))) {
+ if(unlikely(ops->is_finished(ops->handle))) {
if(count_same_end_time != 0) {
last2_point = last1_point;
last1_point = new_point;
@@ -1235,29 +1363,62 @@ static inline void rrd2rrdr_do_dimension(RRDR *r, size_t dim_id_in_rrdr) {
// fetch the new point
{
- db_points_read_since_plan_switch++;
- STORAGE_POINT sp = ops.next_metric(&ops.handle);
+ STORAGE_POINT sp;
+ if(likely(storage_point_is_unset(next1_point))) {
+ db_points_read_since_plan_switch++;
+ sp = ops->next_metric(ops->handle);
+ }
+ else {
+ // ONE POINT READ-AHEAD
+ sp = next1_point;
+ storage_point_unset(next1_point);
+ db_points_read_since_plan_switch = 1;
+ }
+
+ // ONE POINT READ-AHEAD
+ if(unlikely(query_plan_should_switch_plan(ops, sp.end_time_s) &&
+ query_planer_next_plan(ops, now_end_time, new_point.end_time))) {
+
+ // The end time of the current point, crosses our plans (tiers)
+ // so, we switched plan (tier)
+ //
+ // There are 2 cases now:
+ //
+ // A. the entire point of the previous plan is to the future of point from the next plan
+ // B. part of the point of the previous plan overlaps with the point from the next plan
+
+ STORAGE_POINT sp2 = ops->next_metric(ops->handle);
+
+ if(sp.start_time_s > sp2.start_time_s)
+ // the point from the previous plan is useless
+ sp = sp2;
+ else
+ // let the query run from the previous plan
+ // but setting this will also cut off the interpolation
+ // of the point from the previous plan
+ next1_point = sp2;
+ }
- ops.db_points_read_per_tier[ops.tier]++;
- ops.db_total_points_read++;
+ ops->db_points_read_per_tier[ops->tier]++;
+ ops->db_total_points_read++;
- new_point.start_time = sp.start_time;
- new_point.end_time = sp.end_time;
+ new_point.start_time = sp.start_time_s;
+ new_point.end_time = sp.end_time_s;
new_point.anomaly = sp.count ? (NETDATA_DOUBLE)sp.anomaly_count * 100.0 / (NETDATA_DOUBLE)sp.count : 0.0;
- query_point_set_id(new_point, ops.db_total_points_read);
+ query_point_set_id(new_point, ops->db_total_points_read);
// if(debug_this)
// info("QUERY: got point %zu, from time %ld to %ld // now from %ld to %ld // query from %ld to %ld",
// new_point.id, new_point.start_time, new_point.end_time, now_start_time, now_end_time, after_wanted, before_wanted);
//
- // set the right value to the point we got
- if(likely(!storage_point_is_unset(sp) && !storage_point_is_empty(sp))) {
+ // get the right value from the point we got
+ if(likely(!storage_point_is_unset(sp) && !storage_point_is_gap(sp))) {
if(unlikely(use_anomaly_bit_as_value))
new_point.value = new_point.anomaly;
else {
- switch (ops.tier_query_fetch) {
+ switch (ops->tier_query_fetch) {
default:
case TIER_QUERY_FETCH_AVERAGE:
new_point.value = sp.sum / sp.count;
@@ -1284,19 +1445,30 @@ static inline void rrd2rrdr_do_dimension(RRDR *r, size_t dim_id_in_rrdr) {
}
// check if the db is giving us zero duration points
- if(unlikely(new_point.start_time == new_point.end_time)) {
- internal_error(true, "QUERY: '%s', dimension '%s' next_metric() returned point %zu start time %ld, end time %ld, that are both equal",
- qt->id, string2str(qm->dimension.id), new_point.id, new_point.start_time, new_point.end_time);
+ if(unlikely(db_points_read_since_plan_switch > 1 &&
+ new_point.start_time == new_point.end_time)) {
- new_point.start_time = new_point.end_time - ops.tier_ptr->db_update_every;
+ internal_error(true, "QUERY: '%s', dimension '%s' next_metric() returned "
+ "point %zu from %ld to %ld, that are both equal",
+ qt->id, string2str(qm->dimension.id),
+ new_point.id, new_point.start_time, new_point.end_time);
+
+ new_point.start_time = new_point.end_time - ops->tier_ptr->db_update_every_s;
}
// check if the db is advancing the query
- if(unlikely(new_point.end_time <= last1_point.end_time)) {
- internal_error(db_points_read_since_plan_switch > 1,
- "QUERY: '%s', dimension '%s' next_metric() returned point %zu from %ld to %ld, before the last point %zu from %ld to %ld, now is %ld to %ld",
- qt->id, string2str(qm->dimension.id), new_point.id, new_point.start_time, new_point.end_time,
- last1_point.id, last1_point.start_time, last1_point.end_time, now_start_time, now_end_time);
+ if(unlikely(db_points_read_since_plan_switch > 1 &&
+ new_point.end_time <= last1_point.end_time)) {
+
+ internal_error(true,
+ "QUERY: '%s', dimension '%s' next_metric() returned "
+ "point %zu from %ld to %ld, before the "
+ "last point %zu from %ld to %ld, "
+ "now is %ld to %ld",
+ qt->id, string2str(qm->dimension.id),
+ new_point.id, new_point.start_time, new_point.end_time,
+ last1_point.id, last1_point.start_time, last1_point.end_time,
+ now_start_time, now_end_time);
count_same_end_time++;
continue;
@@ -1321,12 +1493,16 @@ static inline void rrd2rrdr_do_dimension(RRDR *r, size_t dim_id_in_rrdr) {
// at exactly the time we will want
// we only log if this is not point 1
- internal_error(new_point.end_time < after_wanted && new_point.id > 1,
- "QUERY: '%s', dimension '%s' next_metric() returned point %zu from %ld time %ld, which is entirely before our current timeframe %ld to %ld (and before the entire query, after %ld, before %ld)",
+ internal_error(new_point.end_time < ops->plan_expanded_after &&
+ db_points_read_since_plan_switch > 1,
+ "QUERY: '%s', dimension '%s' next_metric() "
+ "returned point %zu from %ld time %ld, "
+ "which is entirely before our current timeframe %ld to %ld "
+ "(and before the entire query, after %ld, before %ld)",
qt->id, string2str(qm->dimension.id),
new_point.id, new_point.start_time, new_point.end_time,
now_start_time, now_end_time,
- after_wanted, before_wanted);
+ ops->plan_expanded_after, ops->plan_expanded_before);
}
}
@@ -1339,20 +1515,31 @@ static inline void rrd2rrdr_do_dimension(RRDR *r, size_t dim_id_in_rrdr) {
if(unlikely(count_same_end_time)) {
internal_error(true,
- "QUERY: '%s', dimension '%s', the database does not advance the query, it returned an end time less or equal to the end time of the last point we got %ld, %zu times",
- qt->id, string2str(qm->dimension.id), last1_point.end_time, count_same_end_time);
+ "QUERY: '%s', dimension '%s', the database does not advance the query,"
+ " it returned an end time less or equal to the end time of the last "
+ "point we got %ld, %zu times",
+ qt->id, string2str(qm->dimension.id),
+ last1_point.end_time, count_same_end_time);
if(unlikely(new_point.end_time <= last1_point.end_time))
new_point.end_time = now_end_time;
}
+ time_t stop_time = new_point.end_time;
+ if(unlikely(!storage_point_is_unset(next1_point))) {
+ // ONE POINT READ-AHEAD
+ // the point crosses the start time of the
+ // read ahead storage point we have read
+ stop_time = next1_point.start_time_s;
+ }
+
// the inner loop
// we have 3 points in memory: last2, last1, new
// we select the one to use based on their timestamps
size_t iterations = 0;
- for ( ; now_end_time <= new_point.end_time && points_added < points_wanted ;
- now_end_time += ops.view_update_every, iterations++) {
+ for ( ; now_end_time <= stop_time && points_added < points_wanted ;
+ now_end_time += ops->view_update_every, iterations++) {
// now_start_time is wrong in this loop
// but, we don't need it
@@ -1411,20 +1598,20 @@ static inline void rrd2rrdr_do_dimension(RRDR *r, size_t dim_id_in_rrdr) {
RRDR_VALUE_FLAGS *rrdr_value_options_ptr = &r->o[rrdr_o_v_index];
// update the dimension options
- if(likely(ops.group_points_non_zero))
+ if(likely(ops->group_points_non_zero))
r->od[dim_id_in_rrdr] |= RRDR_DIMENSION_NONZERO;
// store the specific point options
- *rrdr_value_options_ptr = ops.group_value_flags;
+ *rrdr_value_options_ptr = ops->group_value_flags;
// store the group value
- NETDATA_DOUBLE group_value = ops.grouping_flush(r, rrdr_value_options_ptr);
+ NETDATA_DOUBLE group_value = ops->grouping_flush(r, rrdr_value_options_ptr);
r->v[rrdr_o_v_index] = group_value;
// we only store uint8_t anomaly rates,
// so let's get double precision by storing
// anomaly rates in the range 0 - 200
- r->ar[rrdr_o_v_index] = ops.group_anomaly_rate / (NETDATA_DOUBLE)ops.group_points_added;
+ r->ar[rrdr_o_v_index] = ops->group_anomaly_rate / (NETDATA_DOUBLE)ops->group_points_added;
if(likely(points_added || dim_id_in_rrdr)) {
// find the min/max across all dimensions
@@ -1440,72 +1627,71 @@ static inline void rrd2rrdr_do_dimension(RRDR *r, size_t dim_id_in_rrdr) {
}
points_added++;
- ops.group_points_added = 0;
- ops.group_value_flags = RRDR_VALUE_NOTHING;
- ops.group_points_non_zero = 0;
- ops.group_anomaly_rate = 0;
+ ops->group_points_added = 0;
+ ops->group_value_flags = RRDR_VALUE_NOTHING;
+ ops->group_points_non_zero = 0;
+ ops->group_anomaly_rate = 0;
}
// the loop above increased "now" by query_granularity,
// but the main loop will increase it too,
// so, let's undo the last iteration of this loop
if(iterations)
- now_end_time -= ops.view_update_every;
+ now_end_time -= ops->view_update_every;
}
- ops.finalize(&ops.handle);
+ query_planer_finalize_remaining_plans(ops);
r->internal.result_points_generated += points_added;
- r->internal.db_points_read += ops.db_total_points_read;
+ r->internal.db_points_read += ops->db_total_points_read;
for(size_t tr = 0; tr < storage_tiers ; tr++)
- r->internal.tier_points_read[tr] += ops.db_points_read_per_tier[tr];
+ r->internal.tier_points_read[tr] += ops->db_points_read_per_tier[tr];
r->min = min;
r->max = max;
r->before = max_date;
- r->after = min_date - ops.view_update_every + ops.query_granularity;
+ r->after = min_date - ops->view_update_every + ops->query_granularity;
rrdr_done(r, rrdr_line);
internal_error(points_added != points_wanted,
"QUERY: '%s', dimension '%s', requested %zu points, but RRDR added %zu (%zu db points read).",
qt->id, string2str(qm->dimension.id),
- (size_t)points_wanted, (size_t)points_added, ops.db_total_points_read);
+ (size_t)points_wanted, (size_t)points_added, ops->db_total_points_read);
}
// ----------------------------------------------------------------------------
// fill the gap of a tier
void store_metric_at_tier(RRDDIM *rd, size_t tier, struct rrddim_tier *t, STORAGE_POINT sp, usec_t now_ut);
-void store_metric_collection_completed(void);
-void rrdr_fill_tier_gap_from_smaller_tiers(RRDDIM *rd, size_t tier, time_t now) {
+void rrdr_fill_tier_gap_from_smaller_tiers(RRDDIM *rd, size_t tier, time_t now_s) {
if(unlikely(tier >= storage_tiers)) return;
if(storage_tiers_backfill[tier] == RRD_BACKFILL_NONE) return;
- struct rrddim_tier *t = rd->tiers[tier];
+ struct rrddim_tier *t = &rd->tiers[tier];
if(unlikely(!t)) return;
- time_t latest_time_t = t->query_ops->latest_time(t->db_metric_handle);
+ time_t latest_time_s = t->query_ops->latest_time_s(t->db_metric_handle);
time_t granularity = (time_t)t->tier_grouping * (time_t)rd->update_every;
- time_t time_diff = now - latest_time_t;
+ time_t time_diff = now_s - latest_time_s;
// if the user wants only NEW backfilling, and we don't have any data
- if(storage_tiers_backfill[tier] == RRD_BACKFILL_NEW && latest_time_t <= 0) return;
+ if(storage_tiers_backfill[tier] == RRD_BACKFILL_NEW && latest_time_s <= 0) return;
// there is really nothing we can do
- if(now <= latest_time_t || time_diff < granularity) return;
+ if(now_s <= latest_time_s || time_diff < granularity) return;
struct storage_engine_query_handle handle;
// for each lower tier
for(int read_tier = (int)tier - 1; read_tier >= 0 ; read_tier--){
- time_t smaller_tier_first_time = rd->tiers[read_tier]->query_ops->oldest_time(rd->tiers[read_tier]->db_metric_handle);
- time_t smaller_tier_last_time = rd->tiers[read_tier]->query_ops->latest_time(rd->tiers[read_tier]->db_metric_handle);
- if(smaller_tier_last_time <= latest_time_t) continue; // it is as bad as we are
+ time_t smaller_tier_first_time = rd->tiers[read_tier].query_ops->oldest_time_s(rd->tiers[read_tier].db_metric_handle);
+ time_t smaller_tier_last_time = rd->tiers[read_tier].query_ops->latest_time_s(rd->tiers[read_tier].db_metric_handle);
+ if(smaller_tier_last_time <= latest_time_s) continue; // it is as bad as we are
- long after_wanted = (latest_time_t < smaller_tier_first_time) ? smaller_tier_first_time : latest_time_t;
+ long after_wanted = (latest_time_s < smaller_tier_first_time) ? smaller_tier_first_time : latest_time_s;
long before_wanted = smaller_tier_last_time;
- struct rrddim_tier *tmp = rd->tiers[read_tier];
- tmp->query_ops->init(tmp->db_metric_handle, &handle, after_wanted, before_wanted);
+ struct rrddim_tier *tmp = &rd->tiers[read_tier];
+ tmp->query_ops->init(tmp->db_metric_handle, &handle, after_wanted, before_wanted, STORAGE_PRIORITY_HIGH);
size_t points_read = 0;
@@ -1514,9 +1700,9 @@ void rrdr_fill_tier_gap_from_smaller_tiers(RRDDIM *rd, size_t tier, time_t now)
STORAGE_POINT sp = tmp->query_ops->next_metric(&handle);
points_read++;
- if(sp.end_time > latest_time_t) {
- latest_time_t = sp.end_time;
- store_metric_at_tier(rd, tier, t, sp, sp.end_time * USEC_PER_SEC);
+ if(sp.end_time_s > latest_time_s) {
+ latest_time_s = sp.end_time_s;
+ store_metric_at_tier(rd, tier, t, sp, sp.end_time_s * USEC_PER_SEC);
}
}
@@ -1551,12 +1737,12 @@ static void rrd2rrdr_log_request_response_metadata(RRDR *r
, const char *msg
) {
- time_t first_entry_t = r->internal.qt->db.first_time_t;
- time_t last_entry_t = r->internal.qt->db.last_time_t;
+ time_t first_entry_s = r->internal.qt->db.first_time_s;
+ time_t last_entry_s = r->internal.qt->db.last_time_s;
internal_error(
- true,
- "rrd2rrdr() on %s update every %ld with %s grouping %s (group: %zu, resampling_time: %ld, resampling_group: %zu), "
+ true,
+ "rrd2rrdr() on %s update every %ld with %s grouping %s (group: %zu, resampling_time: %ld, resampling_group: %zu), "
"after (got: %ld, want: %ld, req: %ld, db: %ld), "
"before (got: %ld, want: %ld, req: %ld, db: %ld), "
"duration (got: %ld, want: %ld, req: %ld, db: %ld), "
@@ -1576,19 +1762,19 @@ static void rrd2rrdr_log_request_response_metadata(RRDR *r
, r->after
, after_wanted
, after_requested
- , first_entry_t
+ , first_entry_s
// before
, r->before
, before_wanted
, before_requested
- , last_entry_t
+ , last_entry_s
// duration
, (long)(r->before - r->after + r->internal.qt->window.query_granularity)
, (long)(before_wanted - after_wanted + r->internal.qt->window.query_granularity)
, (long)before_requested - after_requested
- , (long)((last_entry_t - first_entry_t) + r->internal.qt->window.query_granularity)
+ , (long)((last_entry_s - first_entry_s) + r->internal.qt->window.query_granularity)
// points
, r->rows
@@ -1708,7 +1894,7 @@ bool query_target_calculate_window(QUERY_TARGET *qt) {
time_t resampling_time_requested = qt->request.resampling_time;
RRDR_OPTIONS options = qt->request.options;
size_t tier = qt->request.tier;
- time_t update_every = qt->db.minimum_latest_update_every;
+ time_t update_every = qt->db.minimum_latest_update_every_s;
// RULES
// points_requested = 0
@@ -1763,30 +1949,30 @@ bool query_target_calculate_window(QUERY_TARGET *qt) {
if (after_wanted == 0 || before_wanted == 0) {
relative_period_requested = true;
- time_t first_entry_t = qt->db.first_time_t;
- time_t last_entry_t = qt->db.last_time_t;
+ time_t first_entry_s = qt->db.first_time_s;
+ time_t last_entry_s = qt->db.last_time_s;
- if (first_entry_t == 0 || last_entry_t == 0) {
- internal_error(true, "QUERY: no data detected on query '%s' (db first_entry_t = %ld, last_entry_t = %ld", qt->id, first_entry_t, last_entry_t);
+ if (first_entry_s == 0 || last_entry_s == 0) {
+ internal_error(true, "QUERY: no data detected on query '%s' (db first_entry_t = %ld, last_entry_t = %ld", qt->id, first_entry_s, last_entry_s);
query_debug_log_free();
return false;
}
- query_debug_log(":first_entry_t %ld, last_entry_t %ld", first_entry_t, last_entry_t);
+ query_debug_log(":first_entry_t %ld, last_entry_t %ld", first_entry_s, last_entry_s);
if (after_wanted == 0) {
- after_wanted = first_entry_t;
+ after_wanted = first_entry_s;
query_debug_log(":zero after_wanted %ld", after_wanted);
}
if (before_wanted == 0) {
- before_wanted = last_entry_t;
+ before_wanted = last_entry_s;
before_is_aligned_to_db_end = true;
query_debug_log(":zero before_wanted %ld", before_wanted);
}
if (points_wanted == 0) {
- points_wanted = (last_entry_t - first_entry_t) / update_every;
+ points_wanted = (last_entry_s - first_entry_s) / update_every;
query_debug_log(":zero points_wanted %zu", points_wanted);
}
}
@@ -1804,7 +1990,7 @@ bool query_target_calculate_window(QUERY_TARGET *qt) {
update_every = rrdset_find_natural_update_every_for_timeframe(
qt, after_wanted, before_wanted, points_wanted, options, tier);
- if (update_every <= 0) update_every = qt->db.minimum_latest_update_every;
+ if (update_every <= 0) update_every = qt->db.minimum_latest_update_every_s;
query_debug_log(":natural update every %ld", update_every);
}
@@ -1975,7 +2161,8 @@ RRDR *rrd2rrdr_legacy(
ONEWAYALLOC *owa,
RRDSET *st, size_t points, time_t after, time_t before,
RRDR_GROUPING group_method, time_t resampling_time, RRDR_OPTIONS options, const char *dimensions,
- const char *group_options, time_t timeout, size_t tier, QUERY_SOURCE query_source) {
+ const char *group_options, time_t timeout, size_t tier, QUERY_SOURCE query_source,
+ STORAGE_PRIORITY priority) {
QUERY_TARGET_REQUEST qtr = {
.st = st,
@@ -1990,6 +2177,7 @@ RRDR *rrd2rrdr_legacy(
.timeout = timeout,
.tier = tier,
.query_source = query_source,
+ .priority = priority,
};
return rrd2rrdr(owa, query_target_create(&qtr));
@@ -2056,16 +2244,48 @@ RRDR *rrd2rrdr(ONEWAYALLOC *owa, QUERY_TARGET *qt) {
if (qt->request.timeout)
now_realtime_timeval(&query_start_time);
+ size_t last_db_points_read = 0;
+ size_t last_result_points_generated = 0;
+
+ QUERY_ENGINE_OPS **ops = onewayalloc_callocz(r->internal.owa, qt->query.used, sizeof(QUERY_ENGINE_OPS *));
+
+ size_t capacity = libuv_worker_threads * 2;
+ size_t max_queries_to_prepare = (qt->query.used > (capacity - 1)) ? (capacity - 1) : qt->query.used;
+ size_t queries_prepared = 0;
+ while(queries_prepared < max_queries_to_prepare) {
+ // preload another query
+ ops[queries_prepared] = rrd2rrdr_query_prep(r, queries_prepared);
+ queries_prepared++;
+ }
+
for(size_t c = 0, max = qt->query.used; c < max ; c++) {
+
+ if(queries_prepared < max) {
+ // preload another query
+ ops[queries_prepared] = rrd2rrdr_query_prep(r, queries_prepared);
+ queries_prepared++;
+ }
+
// set the query target dimension options to rrdr
r->od[c] = qt->query.array[c].dimension.options;
- r->od[c] |= RRDR_DIMENSION_SELECTED;
-
// reset the grouping for the new dimension
r->internal.grouping_reset(r);
- rrd2rrdr_do_dimension(r, c);
+ if(ops[c]) {
+ r->od[c] |= RRDR_DIMENSION_SELECTED;
+ rrd2rrdr_query_execute(r, c, ops[c]);
+ }
+
+ global_statistics_rrdr_query_completed(
+ 1,
+ r->internal.db_points_read - last_db_points_read,
+ r->internal.result_points_generated - last_result_points_generated,
+ qt->request.query_source);
+
+ last_db_points_read = r->internal.db_points_read;
+ last_result_points_generated = r->internal.result_points_generated;
+
if (qt->request.timeout)
now_realtime_timeval(&query_current_time);
@@ -2106,6 +2326,12 @@ RRDR *rrd2rrdr(ONEWAYALLOC *owa, QUERY_TARGET *qt) {
log_access("QUERY CANCELED RUNTIME EXCEEDED %0.2f ms (LIMIT %lld ms)",
(NETDATA_DOUBLE)dt_usec(&query_start_time, &query_current_time) / 1000.0, (long long)qt->request.timeout);
r->result_options |= RRDR_RESULT_OPTION_CANCEL;
+
+ for(size_t i = c + 1; i < queries_prepared ; i++) {
+ if(ops[i])
+ query_planer_finalize_remaining_plans(ops[i]);
+ }
+
break;
}
}
@@ -2169,7 +2395,5 @@ RRDR *rrd2rrdr(ONEWAYALLOC *owa, QUERY_TARGET *qt) {
}
}
- global_statistics_rrdr_query_completed(dimensions_used, r->internal.db_points_read,
- r->internal.result_points_generated, qt->request.query_source);
return r;
}
diff --git a/web/api/queries/rrdr.h b/web/api/queries/rrdr.h
index 6151cddc..e31a9809 100644
--- a/web/api/queries/rrdr.h
+++ b/web/api/queries/rrdr.h
@@ -40,7 +40,8 @@ typedef enum rrdr_options {
RRDR_OPTION_RETURN_RAW = 0x00100000, // Return raw data for aggregating across multiple nodes
RRDR_OPTION_RETURN_JWAR = 0x00200000, // Return anomaly rates in jsonwrap
RRDR_OPTION_SELECTED_TIER = 0x00400000, // Use the selected tier for the query
- RRDR_OPTION_ALL_DIMENSIONS = 0x00800000, // Return the full dimensions list
+ RRDR_OPTION_ALL_DIMENSIONS = 0x00800000, // Return the full dimensions list
+ RRDR_OPTION_SHOW_PLAN = 0x01000000, // Return the query plan in jsonwrap
// internal ones - not to be exposed to the API
RRDR_OPTION_INTERNAL_AR = 0x10000000, // internal use only, to let the formatters we want to render the anomaly rate
@@ -138,7 +139,8 @@ RRDR *rrd2rrdr_legacy(
ONEWAYALLOC *owa,
RRDSET *st, size_t points, time_t after, time_t before,
RRDR_GROUPING group_method, time_t resampling_time, RRDR_OPTIONS options, const char *dimensions,
- const char *group_options, time_t timeout, size_t tier, QUERY_SOURCE query_source);
+ const char *group_options, time_t timeout, size_t tier, QUERY_SOURCE query_source,
+ STORAGE_PRIORITY priority);
RRDR *rrd2rrdr(ONEWAYALLOC *owa, struct query_target *qt);
bool query_target_calculate_window(struct query_target *qt);
diff --git a/web/api/queries/weights.c b/web/api/queries/weights.c
index a9555a66..dc98aeed 100644
--- a/web/api/queries/weights.c
+++ b/web/api/queries/weights.c
@@ -520,6 +520,7 @@ NETDATA_DOUBLE *rrd2rrdr_ks2(
.group_options = group_options,
.tier = tier,
.query_source = QUERY_SOURCE_API_WEIGHTS,
+ .priority = STORAGE_PRIORITY_NORMAL,
};
RRDR *r = rrd2rrdr(owa, query_target_create(&qtr));
@@ -638,7 +639,9 @@ static void rrdset_metric_correlations_volume(
options |= RRDR_OPTION_MATCH_IDS | RRDR_OPTION_ABSOLUTE | RRDR_OPTION_NATURAL_POINTS;
- QUERY_VALUE baseline_average = rrdmetric2value(host, rca, ria, rma, baseline_after, baseline_before, options, group_method, group_options, tier, 0, QUERY_SOURCE_API_WEIGHTS);
+ QUERY_VALUE baseline_average = rrdmetric2value(host, rca, ria, rma, baseline_after, baseline_before,
+ options, group_method, group_options, tier, 0,
+ QUERY_SOURCE_API_WEIGHTS, STORAGE_PRIORITY_NORMAL);
merge_query_value_to_stats(&baseline_average, stats);
if(!netdata_double_isnumber(baseline_average.value)) {
@@ -646,7 +649,9 @@ static void rrdset_metric_correlations_volume(
baseline_average.value = 0.0;
}
- QUERY_VALUE highlight_average = rrdmetric2value(host, rca, ria, rma, after, before, options, group_method, group_options, tier, 0, QUERY_SOURCE_API_WEIGHTS);
+ QUERY_VALUE highlight_average = rrdmetric2value(host, rca, ria, rma, after, before,
+ options, group_method, group_options, tier, 0,
+ QUERY_SOURCE_API_WEIGHTS, STORAGE_PRIORITY_NORMAL);
merge_query_value_to_stats(&highlight_average, stats);
if(!netdata_double_isnumber(highlight_average.value))
@@ -659,7 +664,9 @@ static void rrdset_metric_correlations_volume(
char highlight_countif_options[50 + 1];
snprintfz(highlight_countif_options, 50, "%s" NETDATA_DOUBLE_FORMAT, highlight_average.value < baseline_average.value ? "<" : ">", baseline_average.value);
- QUERY_VALUE highlight_countif = rrdmetric2value(host, rca, ria, rma, after, before, options, RRDR_GROUPING_COUNTIF, highlight_countif_options, tier, 0, QUERY_SOURCE_API_WEIGHTS);
+ QUERY_VALUE highlight_countif = rrdmetric2value(host, rca, ria, rma, after, before,
+ options, RRDR_GROUPING_COUNTIF, highlight_countif_options, tier, 0,
+ QUERY_SOURCE_API_WEIGHTS, STORAGE_PRIORITY_NORMAL);
merge_query_value_to_stats(&highlight_countif, stats);
if(!netdata_double_isnumber(highlight_countif.value)) {
@@ -700,7 +707,10 @@ static void rrdset_weights_anomaly_rate(
options |= RRDR_OPTION_MATCH_IDS | RRDR_OPTION_ANOMALY_BIT | RRDR_OPTION_NATURAL_POINTS;
- QUERY_VALUE qv = rrdmetric2value(host, rca, ria, rma, after, before, options, group_method, group_options, tier, 0, QUERY_SOURCE_API_WEIGHTS);
+ QUERY_VALUE qv = rrdmetric2value(host, rca, ria, rma, after, before,
+ options, group_method, group_options, tier, 0,
+ QUERY_SOURCE_API_WEIGHTS, STORAGE_PRIORITY_NORMAL);
+
merge_query_value_to_stats(&qv, stats);
if(netdata_double_isnumber(qv.value))
diff --git a/web/api/web_api_v1.c b/web/api/web_api_v1.c
index 93f501f9..1b38a33b 100644
--- a/web/api/web_api_v1.c
+++ b/web/api/web_api_v1.c
@@ -41,6 +41,7 @@ static struct {
, {"natural-points" , 0 , RRDR_OPTION_NATURAL_POINTS}
, {"virtual-points" , 0 , RRDR_OPTION_VIRTUAL_POINTS}
, {"all-dimensions" , 0 , RRDR_OPTION_ALL_DIMENSIONS}
+ , {"plan" , 0 , RRDR_OPTION_SHOW_PLAN}
, {NULL , 0 , 0}
};
@@ -311,7 +312,7 @@ inline int web_client_api_request_v1_alarm_count(RRDHOST *host, struct web_clien
else if (!strcmp("CLEAR", value)) status = RRDCALC_STATUS_CLEAR;
}
else if(!strcmp(name, "context") || !strcmp(name, "ctx")) {
- if(!contexts) contexts = buffer_create(255);
+ if(!contexts) contexts = buffer_create(255, &netdata_buffers_statistics.buffers_api);
buffer_strcat(contexts, "|");
buffer_strcat(contexts, value);
}
@@ -388,7 +389,7 @@ inline int web_client_api_request_single_chart(RRDHOST *host, struct web_client
}
w->response.data->contenttype = CT_APPLICATION_JSON;
- st->last_accessed_time = now_realtime_sec();
+ st->last_accessed_time_s = now_realtime_sec();
callback(st, w->response.data);
return HTTP_RESP_OK;
@@ -459,7 +460,7 @@ static int web_client_api_request_v1_context(RRDHOST *host, struct web_client *w
else if(!strcmp(name, "chart_label_key")) chart_label_key = value;
else if(!strcmp(name, "chart_labels_filter")) chart_labels_filter = value;
else if(!strcmp(name, "dimension") || !strcmp(name, "dim") || !strcmp(name, "dimensions") || !strcmp(name, "dims")) {
- if(!dimensions) dimensions = buffer_create(100);
+ if(!dimensions) dimensions = buffer_create(100, &netdata_buffers_statistics.buffers_api);
buffer_strcat(dimensions, "|");
buffer_strcat(dimensions, value);
}
@@ -520,7 +521,7 @@ static int web_client_api_request_v1_contexts(RRDHOST *host, struct web_client *
else if(!strcmp(name, "chart_label_key")) chart_label_key = value;
else if(!strcmp(name, "chart_labels_filter")) chart_labels_filter = value;
else if(!strcmp(name, "dimension") || !strcmp(name, "dim") || !strcmp(name, "dimensions") || !strcmp(name, "dims")) {
- if(!dimensions) dimensions = buffer_create(100);
+ if(!dimensions) dimensions = buffer_create(100, &netdata_buffers_statistics.buffers_api);
buffer_strcat(dimensions, "|");
buffer_strcat(dimensions, value);
}
@@ -560,18 +561,6 @@ inline int web_client_api_request_v1_charts(RRDHOST *host, struct web_client *w,
return HTTP_RESP_OK;
}
-inline int web_client_api_request_v1_archivedcharts(RRDHOST *host __maybe_unused, struct web_client *w, char *url) {
- (void)url;
-
- buffer_flush(w->response.data);
- w->response.data->contenttype = CT_APPLICATION_JSON;
-#ifdef ENABLE_DBENGINE
- if (host->rrd_memory_mode == RRD_MEMORY_MODE_DBENGINE)
- sql_rrdset2json(host, w->response.data);
-#endif
- return HTTP_RESP_OK;
-}
-
inline int web_client_api_request_v1_chart(RRDHOST *host, struct web_client *w, char *url) {
return web_client_api_request_single_chart(host, w, url, rrd_stats_api_v1_chart);
}
@@ -637,7 +626,7 @@ inline int web_client_api_request_v1_data(RRDHOST *host, struct web_client *w, c
else if(!strcmp(name, "chart_labels_filter")) chart_labels_filter = value;
else if(!strcmp(name, "chart")) chart = value;
else if(!strcmp(name, "dimension") || !strcmp(name, "dim") || !strcmp(name, "dimensions") || !strcmp(name, "dims")) {
- if(!dimensions) dimensions = buffer_create(100);
+ if(!dimensions) dimensions = buffer_create(100, &netdata_buffers_statistics.buffers_api);
buffer_strcat(dimensions, "|");
buffer_strcat(dimensions, value);
}
@@ -752,6 +741,7 @@ inline int web_client_api_request_v1_data(RRDHOST *host, struct web_client *w, c
.chart_label_key = chart_label_key,
.charts_labels_filter = chart_labels_filter,
.query_source = QUERY_SOURCE_API_DATA,
+ .priority = STORAGE_PRIORITY_NORMAL,
};
qt = query_target_create(&qtr);
@@ -1068,15 +1058,13 @@ static inline void web_client_api_request_v1_info_mirrored_hosts(BUFFER *wb) {
if (count > 0)
buffer_strcat(wb, ",\n");
- netdata_mutex_lock(&host->receiver_lock);
buffer_sprintf(
wb, "\t\t{ \"guid\": \"%s\", \"hostname\": \"%s\", \"reachable\": %s, \"hops\": %d"
, host->machine_guid
, rrdhost_hostname(host)
- , (host->receiver || host == localhost) ? "true" : "false"
+ , (host == localhost || !rrdhost_flag_check(host, RRDHOST_FLAG_ORPHAN)) ? "true" : "false"
, host->system_info ? host->system_info->hops : (host == localhost) ? 0 : 1
);
- netdata_mutex_unlock(&host->receiver_lock);
rrdhost_aclk_state_lock(host);
if (host->aclk_state.claimed_id)
@@ -1510,7 +1498,7 @@ int web_client_api_request_v1_functions(RRDHOST *host, struct web_client *w, cha
}
#ifndef ENABLE_DBENGINE
-int web_client_api_request_v1_dbengine_stats(RRDHOST *host, struct web_client *w, char *url) {
+int web_client_api_request_v1_dbengine_stats(RRDHOST *host __maybe_unused, struct web_client *w __maybe_unused, char *url __maybe_unused) {
return HTTP_RESP_NOT_FOUND;
}
#else
@@ -1519,12 +1507,6 @@ static void web_client_api_v1_dbengine_stats_for_tier(BUFFER *wb, size_t tier) {
buffer_sprintf(wb,
"\n\t\t\"default_granularity_secs\":%zu"
- ",\n\t\t\"sizeof_metric\":%zu"
- ",\n\t\t\"sizeof_metric_in_index\":%zu"
- ",\n\t\t\"sizeof_page\":%zu"
- ",\n\t\t\"sizeof_page_in_index\":%zu"
- ",\n\t\t\"sizeof_extent\":%zu"
- ",\n\t\t\"sizeof_page_in_extent\":%zu"
",\n\t\t\"sizeof_datafile\":%zu"
",\n\t\t\"sizeof_page_in_cache\":%zu"
",\n\t\t\"sizeof_point_data\":%zu"
@@ -1540,8 +1522,8 @@ static void web_client_api_v1_dbengine_stats_for_tier(BUFFER *wb, size_t tier) {
",\n\t\t\"pages_uncompressed_bytes\":%zu"
",\n\t\t\"pages_duration_secs\":%lld"
",\n\t\t\"single_point_pages\":%zu"
- ",\n\t\t\"first_t\":%llu"
- ",\n\t\t\"last_t\":%llu"
+ ",\n\t\t\"first_t\":%ld"
+ ",\n\t\t\"last_t\":%ld"
",\n\t\t\"database_retention_secs\":%lld"
",\n\t\t\"average_compression_savings\":%0.2f"
",\n\t\t\"average_point_duration_secs\":%0.2f"
@@ -1550,16 +1532,9 @@ static void web_client_api_v1_dbengine_stats_for_tier(BUFFER *wb, size_t tier) {
",\n\t\t\"average_page_size_bytes\":%0.2f"
",\n\t\t\"estimated_concurrently_collected_metrics\":%zu"
",\n\t\t\"currently_collected_metrics\":%zu"
- ",\n\t\t\"max_concurrently_collected_metrics\":%zu"
",\n\t\t\"disk_space\":%zu"
",\n\t\t\"max_disk_space\":%zu"
, stats.default_granularity_secs
- , stats.sizeof_metric
- , stats.sizeof_metric_in_index
- , stats.sizeof_page
- , stats.sizeof_page_in_index
- , stats.sizeof_extent
- , stats.sizeof_page_in_extent
, stats.sizeof_datafile
, stats.sizeof_page_in_cache
, stats.sizeof_point_data
@@ -1575,8 +1550,8 @@ static void web_client_api_v1_dbengine_stats_for_tier(BUFFER *wb, size_t tier) {
, stats.pages_uncompressed_bytes
, (long long)stats.pages_duration_secs
, stats.single_point_pages
- , stats.first_t
- , stats.last_t
+ , stats.first_time_s
+ , stats.last_time_s
, (long long)stats.database_retention_secs
, stats.average_compression_savings
, stats.average_point_duration_secs
@@ -1585,7 +1560,6 @@ static void web_client_api_v1_dbengine_stats_for_tier(BUFFER *wb, size_t tier) {
, stats.average_page_size_bytes
, stats.estimated_concurrently_collected_metrics
, stats.currently_collected_metrics
- , stats.max_concurrently_collected_metrics
, stats.disk_space
, stats.max_disk_space
);
@@ -1634,7 +1608,6 @@ static struct api_command {
{ "charts", 0, WEB_CLIENT_ACL_DASHBOARD | WEB_CLIENT_ACL_ACLK, web_client_api_request_v1_charts },
{ "context", 0, WEB_CLIENT_ACL_DASHBOARD | WEB_CLIENT_ACL_ACLK, web_client_api_request_v1_context },
{ "contexts", 0, WEB_CLIENT_ACL_DASHBOARD | WEB_CLIENT_ACL_ACLK, web_client_api_request_v1_contexts },
- { "archivedcharts", 0, WEB_CLIENT_ACL_DASHBOARD | WEB_CLIENT_ACL_ACLK, web_client_api_request_v1_archivedcharts },
// registry checks the ACL by itself, so we allow everything
{ "registry", 0, WEB_CLIENT_ACL_NOCHECK, web_client_api_request_v1_registry },
diff --git a/web/api/web_api_v1.h b/web/api/web_api_v1.h
index e6682c99..9dd6a1c2 100644
--- a/web/api/web_api_v1.h
+++ b/web/api/web_api_v1.h
@@ -9,7 +9,6 @@
#include "web/api/health/health_cmdapi.h"
#include "web/api/queries/weights.h"
-#define MAX_CHART_LABELS_FILTER (32)
RRDR_OPTIONS web_client_api_request_v1_data_options(char *o);
void web_client_api_request_v1_data_options_to_buffer(BUFFER *wb, RRDR_OPTIONS options);
void web_client_api_request_v1_data_options_to_string(char *buf, size_t size, RRDR_OPTIONS options);
@@ -24,7 +23,6 @@ int web_client_api_request_single_chart(RRDHOST *host, struct web_client *w, cha
int web_client_api_request_v1_alarm_variables(RRDHOST *host, struct web_client *w, char *url);
int web_client_api_request_v1_alarm_count(RRDHOST *host, struct web_client *w, char *url);
int web_client_api_request_v1_charts(RRDHOST *host, struct web_client *w, char *url);
-int web_client_api_request_v1_archivedcharts(RRDHOST *host, struct web_client *w, char *url);
int web_client_api_request_v1_chart(RRDHOST *host, struct web_client *w, char *url);
int web_client_api_request_v1_data(RRDHOST *host, struct web_client *w, char *url);
int web_client_api_request_v1_registry(RRDHOST *host, struct web_client *w, char *url);
diff --git a/web/gui/README.md b/web/gui/README.md
index 69db6bec..fbd7da4d 100644
--- a/web/gui/README.md
+++ b/web/gui/README.md
@@ -13,16 +13,16 @@ before:
action](https://user-images.githubusercontent.com/1153921/101513938-fae28380-3939-11eb-9434-8ad86a39be62.gif)
Learn more about how dashboards work and how they're populated using the `dashboards.js` file in our [web dashboards
-overview](/web/README.md).
+overview](https://github.com/netdata/netdata/blob/master/web/README.md).
By default, Netdata starts a web server for its dashboard at port `19999`. Open up your web browser of choice and
navigate to `http://NODE:19999`, replacing `NODE` with the IP address or hostname of your Agent. If you're unsure, try
`http://localhost:19999` first.
-Netdata uses an [internal, static-threaded web server](/web/server/README.md) to host the HTML, CSS, and JavaScript
+Netdata uses an [internal, static-threaded web server](https://github.com/netdata/netdata/blob/master/web/server/README.md) to host the HTML, CSS, and JavaScript
files that make up the local Agent dashboard. You don't have to configure anything to access it, although you can adjust
-[your settings](/web/server/README.md#other-netdataconf-web-section-options) in the `netdata.conf` file, or run Netdata
-behind an [Nginx proxy](https://learn.netdata.cloud/docs/agent/running-behind-nginx), and so on.
+[your settings](https://github.com/netdata/netdata/blob/master/web/server/README.md#other-netdataconf-web-section-options) in the `netdata.conf` file, or run Netdata
+behind an [Nginx proxy](https://github.com/netdata/netdata/blob/master/docs/Running-behind-nginx.md), and so on.
## Navigating the local dashboard
@@ -40,8 +40,8 @@ dashboard](https://user-images.githubusercontent.com/1153921/101509403-f7e59400-
Netdata is broken up into multiple **sections**, such as **System Overview**,
**CPU**, **Disk**, and more. Inside each section you'll find a number of charts,
-broken down into [contexts](/web/README.md#contexts) and
-[families](/web/README.md#families).
+broken down into [contexts](https://github.com/netdata/netdata/blob/master/web/README.md#contexts) and
+[families](https://github.com/netdata/netdata/blob/master/web/README.md#families).
An example of the **Memory** section on a Linux desktop system.
@@ -69,7 +69,7 @@ Use the calendar to select multiple days. Click on a date to begin the timeframe
Click **Apply** to re-render all visualizations with new metrics data, or **Clear** to restore the default timeframe.
-[Increase the metrics retention policy](/docs/store/change-metrics-storage.md) for your node to see more historical
+[Increase the metrics retention policy](https://github.com/netdata/netdata/blob/master/docs/store/change-metrics-storage.md) for your node to see more historical
timeframes.
### Metrics menus
@@ -80,7 +80,7 @@ section, and menus link to the section they're associated with.
![A screenshot of metrics menus](https://user-images.githubusercontent.com/1153921/80834638-f08f2880-8ba5-11ea-99ae-f610b2885fd6.png)
Most metrics menu items will contain several **submenu** entries, which represent any
-[families](/web/README.md#families) from that section. Netdata automatically
+[families](https://github.com/netdata/netdata/blob/master/web/README.md#families) from that section. Netdata automatically
generates these submenu entries.
Here's a **Disks** menu with several submenu entries for each disk drive and
@@ -100,7 +100,7 @@ a War Room's name to jump to the Netdata Cloud web interface.
menus](https://user-images.githubusercontent.com/1153921/80837210-3f8b8c80-8bab-11ea-9c75-128c2d823ef8.png)
If you want to know more about how Cloud populates this menu, and the Agent-Cloud integration at a high level, see our
-document on [using the Agent with Netdata Cloud](/docs/agent-cloud.md).
+document on [using the Agent with Netdata Cloud](https://github.com/netdata/netdata/blob/master/docs/agent-cloud.md).
## Customizing the local dashboard
@@ -163,5 +163,5 @@ file](https://user-images.githubusercontent.com/1153921/62798924-570e6c80-ba94-1
## Custom dashboards
-For information on creating custom dashboards from scratch, see the [custom dashboards](/web/gui/custom/README.md) or
-[Atlassian Confluence dashboards](/web/gui/confluence/README.md) guides.
+For information on creating custom dashboards from scratch, see the [custom dashboards](https://github.com/netdata/netdata/blob/master/web/gui/custom/README.md) or
+[Atlassian Confluence dashboards](https://github.com/netdata/netdata/blob/master/web/gui/confluence/README.md) guides.
diff --git a/web/gui/confluence/README.md b/web/gui/confluence/README.md
index 64dacdf3..9e7b8025 100644
--- a/web/gui/confluence/README.md
+++ b/web/gui/confluence/README.md
@@ -85,7 +85,7 @@ This badge is now auto-refreshing. It will update itself based on the update fre
> Keep in mind you can add badges with custom Netdata queries too. Netdata automatically creates badges for all the
> alarms, but every chart, every dimension on every chart, can be used for a badge. And Netdata badges are quite
-> powerful! Check [Creating Badges](/web/api/badges/README.md) for more information on badges.
+> powerful! Check [Creating Badges](https://github.com/netdata/netdata/blob/master/web/api/badges/README.md) for more information on badges.
So, let's create a table and add this badge for both our web servers:
diff --git a/web/gui/custom/README.md b/web/gui/custom/README.md
index cdd5d426..0751f208 100644
--- a/web/gui/custom/README.md
+++ b/web/gui/custom/README.md
@@ -1,7 +1,11 @@
<!--
title: "Custom dashboards"
description: "Build custom dashboards with key metrics from one or more nodes running the Netdata Agent and host them anywhere."
-custom_edit_url: https://github.com/netdata/netdata/edit/master/web/gui/custom/README.md
+custom_edit_url: "https://github.com/netdata/netdata/edit/master/web/gui/custom/README.md"
+sidebar_label: "Custom dashboards"
+learn_status: "Published"
+learn_topic_type: "Tasks"
+learn_rel_path: "Operations"
-->
# Custom dashboards
@@ -28,7 +32,7 @@ monitoring two servers on the same page:
![image](https://cloud.githubusercontent.com/assets/2662304/14252187/d8d5f78e-fa8e-11e5-990d-99821d38c874.png)
---
+
## Web directory
@@ -72,7 +76,6 @@ header:
</html>
```
----
## dashboard.js
@@ -163,7 +166,7 @@ that do not specify a Netdata server, add this before loading `dashboard.js`:
<script type="text/javascript">var netdataServer = "http://your.netdata.server:19999";</script>
```
----
+
## Adding charts
@@ -242,7 +245,7 @@ Each chart can get data from a different Netdata server. You can specify the Net
></div>
```
-If you have ephemeral monitoring setup ([More info here](/streaming/README.md#monitoring-ephemeral-nodes)) and have no
+If you have ephemeral monitoring setup ([More info here](https://github.com/netdata/netdata/blob/master/streaming/README.md#monitoring-ephemeral-nodes)) and have no
direct access to the nodes dashboards, you can use the following:
```html
@@ -366,7 +369,7 @@ select specific dimensions using this:
```
Netdata supports coma (`,`) or pipe (`|`) separated [simple
-patterns](/libnetdata/simple_pattern/README.md) for dimensions. By default it
+patterns](https://github.com/netdata/netdata/blob/master/libnetdata/simple_pattern/README.md) for dimensions. By default it
searches for both dimension IDs and dimension NAMEs. You can control the target
of the match with: `data-append-options="match-ids"` or
`data-append-options="match-names"`. Spaces in `data-dimensions=""` are matched
@@ -434,7 +437,7 @@ it, using this:
### API options
-You can append Netdata **[REST API v1](/web/api/README.md)** data options, using this:
+You can append Netdata **[REST API v1](https://github.com/netdata/netdata/blob/master/web/api/README.md)** data options, using this:
```html
<div data-netdata="unique.id"
diff --git a/web/gui/dashboard_info.js b/web/gui/dashboard_info.js
index 11cfda54..d34a4df1 100644
--- a/web/gui/dashboard_info.js
+++ b/web/gui/dashboard_info.js
@@ -297,12 +297,6 @@ netdataDashboard.menu = {
info: 'Docker containers state and disk usage.'
},
- 'fping': {
- title: 'fping',
- icon: '<i class="fas fa-exchange-alt"></i>',
- info: 'Network latency statistics, via <b>fping</b>. <b>fping</b> is a program to send ICMP echo probes to network hosts, similar to <code>ping</code>, but much better performing when pinging multiple hosts. fping versions after 3.15 can be directly used as netdata plugins.'
- },
-
'ping': {
title: 'Ping',
icon: '<i class="fas fa-exchange-alt"></i>',
@@ -585,6 +579,36 @@ netdataDashboard.menu = {
info: undefined
},
+ 'iis': {
+ title: 'IIS',
+ icon: '<i class="fas fa-eye"></i>',
+ info: undefined
+ },
+
+ 'mssql': {
+ title: 'MS SQL Server',
+ icon: '<i class="fas fa-database"></i>',
+ info: undefined
+ },
+
+ 'ad': {
+ title: 'Active Directory',
+ icon: '<i class="fab fa-windows"></i>',
+ info: undefined
+ },
+
+ 'adcs': {
+ title: 'AD Certification Service',
+ icon: '<i class="fab fa-windows"></i>',
+ info: undefined
+ },
+
+ 'adfs': {
+ title: 'AD Federation Service',
+ icon: '<i class="fab fa-windows"></i>',
+ info: undefined
+ },
+
'perf': {
title: 'Perf Counters',
icon: '<i class="fas fa-tachometer-alt"></i>',
@@ -738,6 +762,12 @@ netdataDashboard.menu = {
title: 'Cassandra',
icon: '<i class="fas fa-database"></i>',
info: 'Performance metrics for Cassandra, the open source distributed NoSQL database management system'
+ },
+
+ 'consul': {
+ title: 'Consul',
+ icon: '<i class="fas fa-circle-notch"></i>',
+ info: 'Consul performance and health metrics. For details, see <a href="https://developer.hashicorp.com/consul/docs/agent/telemetry#key-metrics" target="_blank">Key Metrics</a>.'
}
};
@@ -4225,6 +4255,114 @@ netdataDashboard.context = {
},
// ------------------------------------------------------------------------
+ // Consul
+ 'consul.node_health_check_status': {
+ info: 'The current status of the <a href="https://developer.hashicorp.com/consul/tutorials/developer-discovery/service-registration-health-checks#monitor-a-node" target="_blank">node health check</a>. A node health check monitors the health of the entire node. If the node health check fails, Consul marks the node as unhealthy.'
+ },
+ 'consul.service_health_check_status': {
+ info: 'The current status of the <a href="https://developer.hashicorp.com/consul/tutorials/developer-discovery/service-registration-health-checks#monitor-a-service" target="_blank">service health check</a>. A service check only affects the health of the service it is associated with. If the service health check fails, the DNS interface stops returning that service.'
+ },
+ 'consul.client_rpc_requests_rate': {
+ info: 'The number of RPC requests to a Consul server.'
+ },
+ 'consul.client_rpc_requests_exceeded_rate': {
+ info: 'The number of rate-limited RPC requests to a Consul server. An Increase of this metric either indicates the load is getting high enough to limit the rate or a <a href="https://developer.hashicorp.com/consul/docs/agent/config/config-files#limits" target="_blank">incorrectly configured</a> Consul agent.'
+ },
+ 'consul.client_rpc_requests_failed_rate': {
+ info: 'The number of failed RPC requests to a Consul server.'
+ },
+ 'consul.memory_allocated': {
+ info: 'The amount of memory allocated by the Consul process.'
+ },
+ 'consul.memory_sys': {
+ info: 'The amount of memory obtained from the OS.'
+ },
+ 'consul.gc_pause_time': {
+ info: 'The amount of time spent in garbage collection (GC) pauses. GC pause is a "stop-the-world" event, meaning that all runtime threads are blocked until GC completes. If memory usage is high, the Go runtime may GC so frequently that it starts to slow down Consul.'
+ },
+ 'consul.kvs_apply_time': {
+ info: 'The time it takes to complete an update to the KV store.'
+ },
+ 'consul.kvs_apply_operations_rate': {
+ info: 'The number of KV store updates.'
+ },
+ 'consul.txn_apply_time': {
+ info: 'The time spent applying a transaction operation.'
+ },
+ 'consul.txn_apply_operations_rate': {
+ info: 'The number of applied transaction operations.'
+ },
+ 'consul.raft_commit_time': {
+ info: 'The time it takes to commit a new entry to the Raft log on the leader.'
+ },
+ 'consul.raft_commits_rate': {
+ info: 'The number of applied Raft transactions.'
+ },
+ 'consul.autopilot_health_status': {
+ info: 'The overall health of the local server cluster. The status is healthy if <b>all servers</b> are considered healthy by Autopilot.'
+ },
+ 'consul.autopilot_server_health_status': {
+ info: 'Whether the server is healthy according to the current <a href="https://developer.hashicorp.com/consul/tutorials/datacenter-operations/autopilot-datacenter-operations#server-health-checking", target="_blank">Autopilot configuration</a>.'
+ },
+ 'consul.autopilot_server_stable_time': {
+ info: 'The time this server has been in its current state.'
+ },
+ 'consul.autopilot_server_serf_status': {
+ info: 'The SerfHealth check status for the server.'
+ },
+ 'consul.autopilot_server_voter_status': {
+ info: 'Whether the server is a voting member of the Raft cluster.'
+ },
+ 'consul.autopilot_failure_tolerance': {
+ info: 'The number of voting servers that the cluster can lose while continuing to function.'
+ },
+ 'consul.network_lan_rtt': {
+ info: '<a href="https://developer.hashicorp.com/consul/docs/architecture/coordinates#working-with-coordinates" target="_blank">Estimated</a> network round-trip time between this node and other nodes of the cluster.'
+ },
+ 'consul.raft_leader_last_contact_time': {
+ info: 'The time since the leader was last able to contact the follower nodes when checking its leader lease.'
+ },
+ 'consul.raft_follower_last_contact_leader_time': {
+ info: 'The time elapsed since this server last contacted the leader.'
+ },
+ 'consul.raft_leader_elections_rate': {
+ info: 'The number of leadership elections. Increments whenever a Consul server starts an election.'
+ },
+ 'consul.raft_leadership_transitions_rate': {
+ info: 'The number of leadership elections. Increments whenever a Consul server becomes a leader.'
+ },
+ 'consul.server_leadership_status': {
+ info: 'The Consul server leadership status.'
+ },
+ 'consul.raft_thread_main_saturation_perc': {
+ info: 'An approximate measurement of the proportion of time the main Raft goroutine is busy and unavailable to accept new work.'
+ },
+ 'consul.raft_thread_fsm_saturation_perc': {
+ info: 'An approximate measurement of the proportion of time the Raft FSM goroutine is busy and unavailable to accept new work.'
+ },
+ 'consul.raft_fsm_last_restore_duration': {
+ info: 'The time taken to restore the FSM from a snapshot on an agent restart or from the leader calling <i>installSnapshot</i>.'
+ },
+ 'consul.raft_leader_oldest_log_age': {
+ info: 'The time elapsed since the oldest journal was written to the leader\'s journal storage. This can be important for the health of replication when the write rate is high and the snapshot is large, because followers may not be able to recover from a restart if recovery takes longer than the minimum for the current leader.'
+ },
+ 'consul.raft_rpc_install_snapshot_time': {
+ info: 'The time it takes to process the <i>installSnapshot</i> RPC call.'
+ },
+ 'consul.raft_boltdb_freelist_bytes': {
+ info: 'The number of bytes necessary to encode the freelist metadata. When <a href="https://developer.hashicorp.com/consul/docs/agent/config/config-files#NoFreelistSync" target="_blank">raft_boltdb.NoFreelistSync</a> is set to <i>false</i> these metadata bytes must also be written to disk for each committed log.'
+ },
+ 'consul.raft_boltdb_logs_per_batch_rate': {
+ info: 'The number of logs written per batch to the database.'
+ },
+ 'consul.raft_boltdb_store_logs_time': {
+ info: 'The amount of time spent writing logs to the database.'
+ },
+ 'consul.license_expiration_time': {
+ info: 'The amount of time remaining before Consul Enterprise license expires. When the license expires, some Consul Enterprise features will stop working.'
+ },
+
+ // ------------------------------------------------------------------------
// WMI (Process)
'wmi.processes_cpu_time': {
@@ -4281,6 +4419,93 @@ netdataDashboard.context = {
},
// ------------------------------------------------------------------------
+ // WMI (IIS)
+
+ 'iis.website_isapi_extension_requests_count': {
+ info: 'The number of <a href="https://learn.microsoft.com/en-us/previous-versions/iis/6.0-sdk/ms525282(v=vs.90)" target="_blank">ISAPI extension</a> requests that are processed concurrently by the web service.'
+ },
+ 'iis.website_errors_rate': {
+ info: '<p>The number of requests that cannot be satisfied by the server.</p><p><b>DocumentLocked</b> - the requested document was locked. Usually reported as HTTP error 423. <b>DocumentNotFound</b> - the requested document was not found. Usually reported as HTTP error 404.</p>'
+ },
+
+ // ------------------------------------------------------------------------
+ // WMI (Service)
+
+ 'wmi.service_status': {
+ info: 'The current <a href="https://learn.microsoft.com/en-us/windows/win32/services/service-status-transitions" target="_blank">status</a> of the service.'
+ },
+
+ // ------------------------------------------------------------------------
+ // WMI (MSSQL)
+
+ 'mssql.instance_accessmethods_page_splits': {
+ info : 'Page split happens when the page does not have more space. This chart shows the number of page splits per second that occur as the result of overflowing index pages.'
+ },
+
+ 'mssql.instance_cache_hit_ratio': {
+ info : 'Indicates the percentage of pages found in the buffer cache without having to read from disk. The ratio is the total number of cache hits divided by the total number of cache lookups over the last few thousand page accesses. After a long period of time, the ratio moves very little. Because reading from the cache is much less expensive than reading from disk, you want this ratio to be high.'
+ },
+
+ 'mssql.instance_bufman_checkpoint_pages': {
+ info : 'Indicates the number of pages flushed to disk per second by a checkpoint or other operation that require all dirty pages to be flushed.'
+ },
+
+ 'mssql.instance_bufman_page_life_expectancy': {
+ info : 'Indicates the number of seconds a page will stay in the buffer pool without references.'
+ },
+
+ 'mssql.instance_memmgr_external_benefit_of_memory': {
+ info : 'It is used by the engine to balance memory usage between cache and is useful to support when troubleshooting cases with unexpected cache growth. The value is presented as an integer based on an internal calculation.'
+ },
+
+ 'mssql.instance_sql_errors': {
+ info: 'Errors in Microsoft SQL Server.</p><p><b>Db_offline</b> - Tracks severe errors that cause SQL Server to take the current database offline. <b>Info</b> - Information related to error messages that provide information to users but do not cause errors. <b>Kill_connection</b> - Tracks severe errors that cause SQL Server to kill the current connection. <b>User</b> - User errors.</p>'
+ },
+
+ 'mssql.instance_sqlstats_auto_parameterization_attempts': {
+ info: 'Auto-parameterization occurs when an instance of SQL Server tries to parameterize a Transact-SQL request by replacing some literals with parameters so that reuse of the resulting cached execution plan across multiple similar-looking requests is possible. Note that auto-parameterizations are also known as simple parameterizations in newer versions of SQL Server. This counter does not include forced parameterizations.'
+ },
+
+ 'mssql.instance_sqlstats_batch_requests': {
+ info: 'This statistic is affected by all constraints (such as I/O, number of users, cache size, complexity of requests, and so on). High batch requests mean good throughput.'
+ },
+
+ 'mssql.instance_sqlstats_safe_auto_parameterization_attempts': {
+ info: 'Note that auto-parameterizations are also known as simple parameterizations in later versions of SQL Server.'
+ },
+
+ 'mssql.instance_sqlstats_sql_compilations': {
+ info: 'Indicates the number of times the compile code path is entered. Includes compiles caused by statement-level recompilations in SQL Server. After SQL Server user activity is stable, this value reaches a steady state.'
+ },
+
+ // ------------------------------------------------------------------------
+ // WMI (AD)
+
+ 'ad.dra_replication_intersite_compressed_traffic': {
+ info: 'The compressed size, in bytes, of inbound and outbound compressed replication data (size after compression, from DSAs in other sites).'
+ },
+
+ 'ad.dra_replication_intrasite_compressed_traffic': {
+ info: 'The number of bytes replicated that were not compressed (that is., from DSAs in the same site).'
+ },
+
+ 'ad.dra_replication_properties_updated': {
+ info: 'The number of properties that are updated due to incoming property winning the reconciliation logic that determines the final value to be replicated.'
+ },
+
+ 'ad.dra_replication_objects_filtered': {
+ info: 'The number of objects received from inbound replication partners that contained no updates that needed to be applied.'
+ },
+
+ 'ad.dra_replication_pending_syncs': {
+ info: 'The number of directory synchronizations that are queued for this server but not yet processed.'
+ },
+
+ 'ad.dra_replication_sync_requests': {
+ info: 'The number of directory synchronizations that are queued for this server but not yet processed.'
+ },
+
+ // ------------------------------------------------------------------------
// APACHE
'apache.connections': {
@@ -4419,6 +4644,150 @@ netdataDashboard.context = {
},
// ------------------------------------------------------------------------
+ // NGINX Plus
+ 'nginxplus.client_connections_rate': {
+ info: 'Accepted and dropped (not handled) connections. A connection is considered <b>dropped</b> if the worker process is unable to get a connection for the request by establishing a new connection or reusing an open one.'
+ },
+ 'nginxplus.client_connections_count': {
+ info: 'The current number of client connections. A connection is considered <b>idle</b> if there are currently no active requests.'
+ },
+ 'nginxplus.ssl_handshakes_rate': {
+ info: 'Successful and failed SSL handshakes.'
+ },
+ 'nginxplus.ssl_session_reuses_rate': {
+ info: 'The number of session reuses during SSL handshake.'
+ },
+ 'nginxplus.ssl_handshakes_failures_rate': {
+ info: '<p>SSL handshake failures.</p><p><b>NoCommonProtocol</b> - failed because of no common protocol. <b>NoCommonCipher</b> - failed because of no shared cipher. <b>Timeout</b> - failed because of a timeout. <b>PeerRejectedCert</b> - failed because a client rejected the certificate.</p>'
+ },
+ 'nginxplus.ssl_verification_errors_rate': {
+ info: '<p>SSL verification errors.</p><p><b>NoCert</b> - a client did not provide the required certificate. <b>ExpiredCert</b> - an expired or not yet valid certificate was presented by a client. <b>RevokedCert</b> - a revoked certificate was presented by a client. <b>HostnameMismatch</b> - server\'s certificate does not match the hostname. <b>Other</b> - other SSL certificate verification errors.</p>'
+ },
+ 'nginxplus.http_requests_rate': {
+ info: 'The number of HTTP requests received from clients.'
+ },
+ 'nginxplus.http_requests_count': {
+ info: 'The current number of client requests.'
+ },
+ 'nginxplus.uptime': {
+ info: 'The time elapsed since the NGINX process was started.'
+ },
+ 'nginxplus.http_server_zone_requests_rate': {
+ info: 'The number of requests to the HTTP Server Zone.'
+ },
+ 'nginxplus.http_server_zone_responses_per_code_class_rate': {
+ info: 'The number of responses from the HTTP Server Zone. Responses grouped by HTTP status code class.'
+ },
+ 'nginxplus.http_server_zone_traffic_rate': {
+ info: 'The amount of data transferred to and from the HTTP Server Zone.'
+ },
+ 'nginxplus.http_server_zone_requests_processing_count': {
+ info: 'The number of client requests that are currently being processed by the HTTP Server Zone.'
+ },
+ 'nginxplus.http_server_zone_requests_discarded_rate': {
+ info: 'The number of requests to the HTTP Server Zone completed without sending a response.'
+ },
+ 'nginxplus.http_location_zone_requests_rate': {
+ info: 'The number of requests to the HTTP Location Zone.'
+ },
+ 'nginxplus.http_location_zone_responses_per_code_class_rate': {
+ info: 'The number of responses from the HTTP Location Zone. Responses grouped by HTTP status code class.'
+ },
+ 'nginxplus.http_location_zone_traffic_rate': {
+ info: 'The amount of data transferred to and from the HTTP Location Zone.'
+ },
+ 'nginxplus.http_location_zone_requests_discarded_rate': {
+ info: 'The number of requests to the HTTP Location Zone completed without sending a response.'
+ },
+ 'nginxplus.http_upstream_peers_count': {
+ info: 'The number of HTTP Upstream servers.'
+ },
+ 'nginxplus.http_upstream_zombies_count': {
+ info: 'The current number of HTTP Upstream servers removed from the group but still processing active client requests.'
+ },
+ 'nginxplus.http_upstream_keepalive_count': {
+ info: 'The current number of idle keepalive connections to the HTTP Upstream.'
+ },
+ 'nginxplus.http_upstream_server_requests_rate': {
+ info: 'The number of client requests forwarded to the HTTP Upstream Server.'
+ },
+ 'nginxplus.http_upstream_server_responses_per_code_class_rate': {
+ info: 'The number of responses received from the HTTP Upstream Server. Responses grouped by HTTP status code class.'
+ },
+ 'nginxplus.http_upstream_server_response_time': {
+ info: 'The average time to get a complete response from the HTTP Upstream Server.'
+ },
+ 'nginxplus.http_upstream_server_response_header_time': {
+ info: 'The average time to get a response header from the HTTP Upstream Server.'
+ },
+ 'nginxplus.http_upstream_server_traffic_rate': {
+ info: 'The amount of traffic transferred to and from the HTTP Upstream Server.'
+ },
+ 'nginxplus.http_upstream_server_state': {
+ info: 'The current state of the HTTP Upstream Server. Status active if set to 1.'
+ },
+ 'nginxplus.http_upstream_server_connections_count': {
+ info: 'The current number of active connections to the HTTP Upstream Server.'
+ },
+ 'nginxplus.http_upstream_server_downtime': {
+ info: 'The time the HTTP Upstream Server has spent in the <b>unavail</b>, <b>checking</b>, and <b>unhealthy</b> states.'
+ },
+ 'nginxplus.http_cache_state': {
+ info: 'HTTP cache current state. <b>Cold</b> means that the cache loader process is still loading data from disk into the cache.'
+ },
+ 'nginxplus.http_cache_iops': {
+ info: '<p>HTTP cache IOPS.</p><p><b>Served</b> - valid, expired, and revalidated responses read from the cache. <b>Written</b> - miss, expired, and bypassed responses written to the cache. <b>Bypassed</b> - miss, expired, and bypass responses.</p>'
+ },
+ 'nginxplus.http_cache_io': {
+ info: '<p>HTTP cache IO.</p><p><b>Served</b> - valid, expired, and revalidated responses read from the cache. <b>Written</b> - miss, expired, and bypassed responses written to the cache. <b>Bypassed</b> - miss, expired, and bypass responses.</p>'
+ },
+ 'nginxplus.http_cache_size': {
+ info: 'The current size of the cache.'
+ },
+ 'nginxplus.stream_server_zone_connections_rate': {
+ info: 'The number of accepted connections to the Stream Server Zone.'
+ },
+ 'nginxplus.stream_server_zone_sessions_per_code_class_rate': {
+ info: 'The number of completed sessions for the Stream Server Zone. Sessions grouped by status code class.'
+ },
+ 'nginxplus.stream_server_zone_traffic_rate': {
+ info: 'The amount of data transferred to and from the Stream Server Zone.'
+ },
+ 'nginxplus.stream_server_zone_connections_processing_count': {
+ info: 'The number of client connections to the Stream Server Zone that are currently being processed.'
+ },
+ 'nginxplus.stream_server_zone_connections_discarded_rate': {
+ info: 'The number of connections to the Stream Server Zone completed without creating a session.'
+ },
+ 'nginxplus.stream_upstream_peers_count': {
+ info: 'The number of Stream Upstream servers.'
+ },
+ 'nginxplus.stream_upstream_zombies_count': {
+ info: 'The current number of HTTP Upstream servers removed from the group but still processing active client connections.'
+ },
+ 'nginxplus.stream_upstream_server_connections_rate': {
+ info: 'The number of connections forwarded to the Stream Upstream Server.'
+ },
+ 'nginxplus.stream_upstream_server_traffic_rate': {
+ info: 'The amount of traffic transferred to and from the Stream Upstream Server.'
+ },
+ 'nginxplus.stream_upstream_server_state': {
+ info: 'The current state of the Stream Upstream Server. Status active if set to 1.'
+ },
+ 'nginxplus.stream_upstream_server_downtime': {
+ info: 'The time the Stream Upstream Server has spent in the <b>unavail</b>, <b>checking</b>, and <b>unhealthy</b> states.'
+ },
+ 'nginxplus.stream_upstream_server_connections_count': {
+ info: 'The current number of connections to the Stream Upstream Server.'
+ },
+ 'nginxplus.resolver_zone_requests_rate': {
+ info: '<p>Resolver zone DNS requests.</p><p><b>Name</b> - requests to resolve names to addresses. <b>Srv</b> - requests to resolve SRV records. <b>Addr</b> - requests to resolve addresses to names.</p>'
+ },
+ 'nginxplus.resolver_zone_responses_rate': {
+ info: '<p>Resolver zone DNS responses.</p><p><b>NoError</b> - successful responses. <b>FormErr</b> - format error responses. <b>ServFail</b> - server failure responses. <b>NXDomain</b> - host not found responses. <b>NotImp</b> - unimplemented responses. <b>Refused</b> - operation refused responses. <b>TimedOut</b> - timed out requests. <b>Unknown</b> - requests completed with an unknown error.</p>'
+ },
+
+ // ------------------------------------------------------------------------
// HTTP check
'httpcheck.responsetime': {
@@ -4488,19 +4857,6 @@ netdataDashboard.context = {
},
// ------------------------------------------------------------------------
- // fping
-
- 'fping.quality': {
- colors: NETDATA.colors[10],
- height: 0.5
- },
-
- 'fping.packets': {
- height: 0.5
- },
-
-
- // ------------------------------------------------------------------------
// containers
'cgroup.cpu_limit': {
diff --git a/web/gui/main.js b/web/gui/main.js
index a2a18670..20f455fd 100644
--- a/web/gui/main.js
+++ b/web/gui/main.js
@@ -4932,8 +4932,7 @@ function handleSignInMessage(e) {
netdataRegistryCallback(registryAgents);
if (e.data.redirectURI && !window.location.href.includes(e.data.redirectURI)) {
- // lgtm false-positive - redirectURI does not come from user input, but from iframe callback
- window.location.replace(e.data.redirectURI); // lgtm[js/client-side-unvalidated-url-redirection]
+ window.location.replace(e.data.redirectURI);
}
}
diff --git a/web/server/README.md b/web/server/README.md
index 6485b84b..407df6c0 100644
--- a/web/server/README.md
+++ b/web/server/README.md
@@ -1,7 +1,11 @@
<!--
title: "Web server"
description: "The Netdata Agent's local static-threaded web server serves dashboards and real-time visualizations with security and DDoS protection."
-custom_edit_url: https://github.com/netdata/netdata/edit/master/web/server/README.md
+custom_edit_url: "https://github.com/netdata/netdata/edit/master/web/server/README.md"
+sidebar_label: "Web server"
+learn_status: "Published"
+learn_topic_type: "References"
+learn_rel_path: "References/Configuration"
-->
# Web server
@@ -47,7 +51,7 @@ Using the above, Netdata will bind to:
- IPv4 127.0.0.1 at port 19999 (port was used from `default port`). Only the UI (dashboard) and the read API will be accessible on this port. Both HTTP and HTTPS requests will be accepted.
- IPv4 10.1.1.1 at port 19998. The management API and `netdata.conf` will be accessible on this port.
- All the IPs `hostname` resolves to (both IPv4 and IPv6 depending on the resolved IPs) at port 19997. Only badges will be accessible on this port.
-- All IPv6 IPs at port 19996. Only metric streaming requests from other Netdata agents will be accepted on this port. Only encrypted streams will be allowed (i.e. child nodes also need to be [configured for TLS](/streaming/README.md).
+- All IPv6 IPs at port 19996. Only metric streaming requests from other Netdata agents will be accepted on this port. Only encrypted streams will be allowed (i.e. child nodes also need to be [configured for TLS](https://github.com/netdata/netdata/blob/master/streaming/README.md).
- All the IPs `localhost` resolves to (both IPv4 and IPv6 depending the resolved IPs) at port 19996. This port will only accept registry API requests.
- All IPv4 and IPv6 IPs at port `http` as set in `/etc/services`. Only the UI (dashboard) and the read API will be accessible on this port.
- Unix domain socket `/run/netdata/netdata.sock`. All requests are serviceable on this socket. Note that in some OSs like Fedora, every service sees a different `/tmp`, so don't create a Unix socket under `/tmp`. `/run` or `/var/run` is suggested.
@@ -136,7 +140,7 @@ Example:
bind to = *=dashboard|registry|badges|management|streaming|netdata.conf^SSL=force
```
-For information how to configure the child to use TLS, check [securing the communication](/streaming/README.md#securing-streaming-communications) in the streaming documentation. There you will find additional details on the expected behavior for client and server nodes, when their respective TLS options are enabled.
+For information how to configure the child to use TLS, check [securing the communication](https://github.com/netdata/netdata/blob/master/streaming/README.md#securing-streaming-communications) in the streaming documentation. There you will find additional details on the expected behavior for client and server nodes, when their respective TLS options are enabled.
When we define the use of SSL in a Netdata agent for different ports, Netdata will apply the behavior specified on each port. For example, using the configuration line below:
@@ -192,7 +196,7 @@ Netdata supports access lists in `netdata.conf`:
- `allow netdata.conf from` checks the IP to allow `http://netdata.host:19999/netdata.conf`.
The IPs listed are all the private IPv4 addresses, including link local IPv6 addresses. Keep in mind that connections to Netdata API ports are filtered by `allow connections from`. So, IPs allowed by `allow netdata.conf from` should also be allowed by `allow connections from`.
-- `allow management from` checks the IPs to allow API management calls. Management via the API is currently supported for [health](/web/api/health/README.md#health-management-api)
+- `allow management from` checks the IPs to allow API management calls. Management via the API is currently supported for [health](https://github.com/netdata/netdata/blob/master/web/api/health/README.md#health-management-api)
In order to check the FQDN of the connection without opening the Netdata agent to DNS-spoofing, a reverse-dns record
must be setup for the connecting host. At connection time the reverse-dns of the peer IP address is resolved, and
@@ -218,13 +222,13 @@ present that may match DNS FQDNs.
|setting|default|info|
|:-----:|:-----:|:---|
-|ses max window|`15`|See [single exponential smoothing](/web/api/queries/des/README.md)|
-|des max window|`15`|See [double exponential smoothing](/web/api/queries/des/README.md)|
+|ses max window|`15`|See [single exponential smoothing](https://github.com/netdata/netdata/blob/master/web/api/queries/des/README.md)|
+|des max window|`15`|See [double exponential smoothing](https://github.com/netdata/netdata/blob/master/web/api/queries/des/README.md)|
|listen backlog|`4096`|The port backlog. Check `man 2 listen`.|
|disconnect idle clients after seconds|`60`|The time in seconds to disconnect web clients after being totally idle.|
|timeout for first request|`60`|How long to wait for a client to send a request before closing the socket. Prevents slow request attacks.|
-|accept a streaming request every seconds|`0`|Can be used to set a limit on how often a parent node will accept streaming requests from child nodes in a [streaming and replication setup](/streaming/README.md)|
-|respect do not track policy|`no`|If set to `yes`, Netdata will respect the user's browser preferences for [Do Not Track](https://www.eff.org/issues/do-not-track) (DNT) and storing cookies. If DNT is _enabled_ in the browser, and this option is set to `yes`, users will not be able to sign in to Netdata Cloud via their local Agent dashboard, and their node will not connect to any [registry](/registry/README.md). For certain browsers, users must disable DNT and change this option to `yes` for full functionality.|
+|accept a streaming request every seconds|`0`|Can be used to set a limit on how often a parent node will accept streaming requests from child nodes in a [streaming and replication setup](https://github.com/netdata/netdata/blob/master/streaming/README.md)|
+|respect do not track policy|`no`|If set to `yes`, Netdata will respect the user's browser preferences for [Do Not Track](https://www.eff.org/issues/do-not-track) (DNT) and storing cookies. If DNT is _enabled_ in the browser, and this option is set to `yes`, users will not be able to sign in to Netdata Cloud via their local Agent dashboard, and their node will not connect to any [registry](https://github.com/netdata/netdata/blob/master/registry/README.md). For certain browsers, users must disable DNT and change this option to `yes` for full functionality.|
|x-frame-options response header||[Avoid clickjacking attacks, by ensuring that the content is not embedded into other sites](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Frame-Options).|
|enable gzip compression|`yes`|When set to `yes`, Netdata web responses will be GZIP compressed, if the web client accepts such responses.|
|gzip compression strategy|`default`|Valid strategies are `default`, `filtered`, `huffman only`, `rle` and `fixed`|
diff --git a/web/server/static/static-threaded.c b/web/server/static/static-threaded.c
index 26e9a47b..aca7d7ec 100644
--- a/web/server/static/static-threaded.c
+++ b/web/server/static/static-threaded.c
@@ -307,7 +307,7 @@ static int web_server_rcv_callback(POLLINFO *pi, short int *events) {
web_client_send(w);
}
- if(unlikely(w->mode == WEB_CLIENT_MODE_FILECOPY)) {
+ else if(unlikely(w->mode == WEB_CLIENT_MODE_FILECOPY)) {
if(w->pollinfo_filecopy_slot == 0) {
debug(D_WEB_CLIENT, "%llu: FILECOPY DETECTED ON FD %d", w->id, pi->fd);
@@ -408,6 +408,10 @@ static void socket_listen_main_static_threaded_worker_cleanup(void *ptr) {
worker_unregister();
}
+static bool web_server_should_stop(void) {
+ return !service_running(SERVICE_WEB_SERVER);
+}
+
void *socket_listen_main_static_threaded_worker(void *ptr) {
worker_private = (struct web_server_static_threaded_worker *)ptr;
worker_private->running = 1;
@@ -430,6 +434,7 @@ void *socket_listen_main_static_threaded_worker(void *ptr) {
, web_server_rcv_callback
, web_server_snd_callback
, NULL
+ , web_server_should_stop
, web_allow_connections_from
, web_allow_connections_dns
, NULL
@@ -452,35 +457,35 @@ static void socket_listen_main_static_threaded_cleanup(void *ptr) {
struct netdata_static_thread *static_thread = (struct netdata_static_thread *)ptr;
static_thread->enabled = NETDATA_MAIN_THREAD_EXITING;
- int i, found = 0;
- usec_t max = 2 * USEC_PER_SEC, step = 50000;
-
- // we start from 1, - 0 is self
- for(i = 1; i < static_threaded_workers_count; i++) {
- if(static_workers_private_data[i].running) {
- found++;
- info("stopping worker %d", i + 1);
- netdata_thread_cancel(static_workers_private_data[i].thread);
- }
- else
- info("found stopped worker %d", i + 1);
- }
-
- while(found && max > 0) {
- max -= step;
- info("Waiting %d static web threads to finish...", found);
- sleep_usec(step);
- found = 0;
-
- // we start from 1, - 0 is self
- for(i = 1; i < static_threaded_workers_count; i++) {
- if (static_workers_private_data[i].running)
- found++;
- }
- }
-
- if(found)
- error("%d static web threads are taking too long to finish. Giving up.", found);
+// int i, found = 0;
+// usec_t max = 2 * USEC_PER_SEC, step = 50000;
+//
+// // we start from 1, - 0 is self
+// for(i = 1; i < static_threaded_workers_count; i++) {
+// if(static_workers_private_data[i].running) {
+// found++;
+// info("stopping worker %d", i + 1);
+// netdata_thread_cancel(static_workers_private_data[i].thread);
+// }
+// else
+// info("found stopped worker %d", i + 1);
+// }
+//
+// while(found && max > 0) {
+// max -= step;
+// info("Waiting %d static web threads to finish...", found);
+// sleep_usec(step);
+// found = 0;
+//
+// // we start from 1, - 0 is self
+// for(i = 1; i < static_threaded_workers_count; i++) {
+// if (static_workers_private_data[i].running)
+// found++;
+// }
+// }
+//
+// if(found)
+// error("%d static web threads are taking too long to finish. Giving up.", found);
info("closing all web server sockets...");
listen_sockets_close(&api_sockets);
@@ -502,7 +507,7 @@ void *socket_listen_main_static_threaded(void *ptr) {
// 6 threads is the optimal value
// since 6 are the parallel connections browsers will do
// so, if the machine has more CPUs, avoid using resources unnecessarily
- int def_thread_count = (processors > 6) ? 6 : processors;
+ int def_thread_count = MIN(get_netdata_cpus(), 6);
if (!strcmp(config_get(CONFIG_SECTION_WEB, "mode", ""),"single-threaded")) {
info("Running web server with one thread, because mode is single-threaded");
@@ -534,7 +539,7 @@ void *socket_listen_main_static_threaded(void *ptr) {
static_workers_private_data[i].max_sockets = max_sockets / static_threaded_workers_count;
char tag[50 + 1];
- snprintfz(tag, 50, "WEB_SERVER[static%d]", i+1);
+ snprintfz(tag, 50, "WEB[%d]", i+1);
info("starting worker %d", i+1);
netdata_thread_create(&static_workers_private_data[i].thread, tag, NETDATA_THREAD_OPTION_DEFAULT,
diff --git a/web/server/web_client.c b/web/server/web_client.c
index b3c5ada7..c14b86f3 100644
--- a/web/server/web_client.c
+++ b/web/server/web_client.c
@@ -129,10 +129,10 @@ void web_client_request_done(struct web_client *w) {
, mode
, sent
, size
- , -((size > 0) ? ((size - sent) / (double) size * 100.0) : 0.0)
- , dt_usec(&w->tv_ready, &w->tv_in) / 1000.0
- , dt_usec(&tv, &w->tv_ready) / 1000.0
- , dt_usec(&tv, &w->tv_in) / 1000.0
+ , -((size > 0) ? ((double)(size - sent) / (double) size * 100.0) : 0.0)
+ , (double)dt_usec(&w->tv_ready, &w->tv_in) / 1000.0
+ , (double)dt_usec(&tv, &w->tv_ready) / 1000.0
+ , (double)dt_usec(&tv, &w->tv_in) / 1000.0
, w->response.code
, strip_control_characters(w->last_url)
);
@@ -302,7 +302,7 @@ int mysendfile(struct web_client *w, char *filename) {
}
}
- // if the filename contains a .. refuse to serve it
+ // if the filename contains a double dot refuse to serve it
if(strstr(filename, "..") != 0) {
debug(D_WEB_CLIENT_ACCESS, "%llu: File '%s' is not acceptable.", w->id, filename);
w->response.data->contenttype = CT_TEXT_HTML;
@@ -831,9 +831,8 @@ static inline char *web_client_valid_method(struct web_client *w, char *s) {
* @param s is the first address of the string.
* @param ptr is the address of the separator.
*/
-static void web_client_set_path_query(struct web_client *w, char *s, char *ptr) {
+static void web_client_set_path_query(struct web_client *w, const char *s, char *ptr) {
w->url_path_length = (size_t)(ptr -s);
-
w->url_search_path = ptr;
}
@@ -1250,12 +1249,15 @@ static inline void web_client_send_http_header(struct web_client *w) {
if(bytes > 0)
w->stats_sent_bytes += bytes;
- error("HTTP headers failed to be sent (I sent %zu bytes but the system sent %zd bytes). Closing web client."
- , buffer_strlen(w->response.header_output)
- , bytes);
+ if (bytes < 0) {
- WEB_CLIENT_IS_DEAD(w);
- return;
+ error("HTTP headers failed to be sent (I sent %zu bytes but the system sent %zd bytes). Closing web client."
+ , buffer_strlen(w->response.header_output)
+ , bytes);
+
+ WEB_CLIENT_IS_DEAD(w);
+ return;
+ }
}
else
w->stats_sent_bytes += bytes;
@@ -1314,6 +1316,9 @@ static inline int web_client_switch_host(RRDHOST *host, struct web_client *w, ch
}
static inline int web_client_process_url(RRDHOST *host, struct web_client *w, char *url) {
+ if(unlikely(!service_running(ABILITY_WEB_REQUESTS)))
+ return web_client_permission_denied(w);
+
static uint32_t
hash_api = 0,
hash_netdata_conf = 0,
@@ -1423,7 +1428,7 @@ static inline int web_client_process_url(RRDHOST *host, struct web_client *w, ch
// replace the zero bytes with spaces
buffer_char_replace(w->response.data, '\0', ' ');
- // just leave the buffer as is
+ // just leave the buffer as-is
// it will be copied back to the client
return HTTP_RESP_OK;
@@ -1540,7 +1545,7 @@ void web_client_process_request(struct web_client *w) {
break;
}
- // keep track of the time we done processing
+ // keep track of the processing time
now_realtime_timeval(&w->tv_ready);
w->response.sent = 0;
@@ -1612,7 +1617,6 @@ ssize_t web_client_send_chunk_header(struct web_client *w, size_t len)
else if(bytes == 0) {
debug(D_WEB_CLIENT, "%llu: Did not send chunk header to the client.", w->id);
- WEB_CLIENT_IS_DEAD(w);
}
else {
debug(D_WEB_CLIENT, "%llu: Failed to send chunk header to client.", w->id);
@@ -1635,7 +1639,6 @@ ssize_t web_client_send_chunk_close(struct web_client *w)
else if(bytes == 0) {
debug(D_WEB_CLIENT, "%llu: Did not send chunk suffix to the client.", w->id);
- WEB_CLIENT_IS_DEAD(w);
}
else {
debug(D_WEB_CLIENT, "%llu: Failed to send chunk suffix to client.", w->id);
@@ -1658,7 +1661,6 @@ ssize_t web_client_send_chunk_finalize(struct web_client *w)
else if(bytes == 0) {
debug(D_WEB_CLIENT, "%llu: Did not send chunk finalize suffix to the client.", w->id);
- WEB_CLIENT_IS_DEAD(w);
}
else {
debug(D_WEB_CLIENT, "%llu: Failed to send chunk finalize suffix to client.", w->id);
@@ -1775,7 +1777,6 @@ ssize_t web_client_send_deflate(struct web_client *w)
debug(D_WEB_CLIENT, "%llu: Did not send any bytes to the client (zhave = %zu, zsent = %zu, need to send = %zu).",
w->id, w->response.zhave, w->response.zsent, w->response.zhave - w->response.zsent);
- WEB_CLIENT_IS_DEAD(w);
}
else {
debug(D_WEB_CLIENT, "%llu: Failed to send data to client.", w->id);
@@ -1828,7 +1829,6 @@ ssize_t web_client_send(struct web_client *w) {
}
else if(likely(bytes == 0)) {
debug(D_WEB_CLIENT, "%llu: Did not send any bytes to the client.", w->id);
- WEB_CLIENT_IS_DEAD(w);
}
else {
debug(D_WEB_CLIENT, "%llu: Failed to send data to client.", w->id);
@@ -1846,7 +1846,7 @@ ssize_t web_client_read_file(struct web_client *w)
if(unlikely(w->response.rlen <= w->response.data->len))
return 0;
- ssize_t left = w->response.rlen - w->response.data->len;
+ ssize_t left = (ssize_t)(w->response.rlen - w->response.data->len);
ssize_t bytes = read(w->ifd, &w->response.data->buffer[w->response.data->len], (size_t)left);
if(likely(bytes > 0)) {
size_t old = w->response.data->len;
@@ -1896,7 +1896,7 @@ ssize_t web_client_receive(struct web_client *w)
return web_client_read_file(w);
ssize_t bytes;
- ssize_t left = w->response.data->size - w->response.data->len;
+ ssize_t left = (ssize_t)(w->response.data->size - w->response.data->len);
// do we have any space for more data?
buffer_need_bytes(w->response.data, NETDATA_WEB_REQUEST_RECEIVE_SIZE);
@@ -1928,10 +1928,32 @@ ssize_t web_client_receive(struct web_client *w)
debug(D_WEB_CLIENT, "%llu: Received %zd bytes.", w->id, bytes);
debug(D_WEB_DATA, "%llu: Received data: '%s'.", w->id, &w->response.data->buffer[old]);
}
- else {
+ else if (bytes < 0) {
debug(D_WEB_CLIENT, "%llu: receive data failed.", w->id);
WEB_CLIENT_IS_DEAD(w);
- }
+ } else
+ debug(D_WEB_CLIENT, "%llu: Received %zd bytes.", w->id, bytes);
return(bytes);
}
+
+
+int web_client_socket_is_now_used_for_streaming(struct web_client *w) {
+ // prevent the web_client from closing the streaming socket
+
+ WEB_CLIENT_IS_DEAD(w);
+
+ if(web_server_mode == WEB_SERVER_MODE_STATIC_THREADED) {
+ web_client_flag_set(w, WEB_CLIENT_FLAG_DONT_CLOSE_SOCKET);
+ }
+ else {
+ if(w->ifd == w->ofd)
+ w->ifd = w->ofd = -1;
+ else
+ w->ifd = -1;
+ }
+
+ buffer_flush(w->response.data);
+
+ return HTTP_RESP_OK;
+}
diff --git a/web/server/web_client.h b/web/server/web_client.h
index 630d71a8..d0360f4f 100644
--- a/web/server/web_client.h
+++ b/web/server/web_client.h
@@ -19,13 +19,16 @@ extern int web_enable_gzip, web_gzip_level, web_gzip_strategy;
// HTTP_CODES 4XX Client Errors
#define HTTP_RESP_BAD_REQUEST 400
+#define HTTP_RESP_UNAUTHORIZED 401
#define HTTP_RESP_FORBIDDEN 403
#define HTTP_RESP_NOT_FOUND 404
+#define HTTP_RESP_CONFLICT 409
#define HTTP_RESP_PRECOND_FAIL 412
// HTTP_CODES 5XX Server Errors
#define HTTP_RESP_INTERNAL_SERVER_ERROR 500
-#define HTTP_RESP_BACKEND_FETCH_FAILED 503
+#define HTTP_RESP_BACKEND_FETCH_FAILED 503 // 503 is right
+#define HTTP_RESP_SERVICE_UNAVAILABLE 503 // 503 is right
#define HTTP_RESP_GATEWAY_TIMEOUT 504
#define HTTP_RESP_BACKEND_RESPONSE_INVALID 591
@@ -206,6 +209,8 @@ int mysendfile(struct web_client *w, char *filename);
void web_client_build_http_header(struct web_client *w);
char *strip_control_characters(char *url);
+int web_client_socket_is_now_used_for_streaming(struct web_client *w);
+
#include "daemon/common.h"
#endif
diff --git a/web/server/web_client_cache.c b/web/server/web_client_cache.c
index 1fa59358..4344209c 100644
--- a/web/server/web_client_cache.c
+++ b/web/server/web_client_cache.c
@@ -11,7 +11,16 @@
static void web_client_reuse_ssl(struct web_client *w) {
if (netdata_ssl_srv_ctx) {
if (w->ssl.conn) {
- SSL_clear(w->ssl.conn);
+ SSL_SESSION *session = SSL_get_session(w->ssl.conn);
+ SSL *old = w->ssl.conn;
+ w->ssl.conn = SSL_new(netdata_ssl_srv_ctx);
+ if (session) {
+#if OPENSSL_VERSION_NUMBER >= OPENSSL_VERSION_111
+ if (SSL_SESSION_is_resumable(session))
+#endif
+ SSL_set_session(w->ssl.conn, session);
+ }
+ SSL_free(old);
}
}
}
@@ -56,13 +65,15 @@ static void web_client_free(struct web_client *w) {
}
#endif
freez(w);
+ __atomic_sub_fetch(&netdata_buffers_statistics.buffers_web, sizeof(struct web_client), __ATOMIC_RELAXED);
}
static struct web_client *web_client_alloc(void) {
struct web_client *w = callocz(1, sizeof(struct web_client));
- w->response.data = buffer_create(NETDATA_WEB_RESPONSE_INITIAL_SIZE);
- w->response.header = buffer_create(NETDATA_WEB_RESPONSE_HEADER_SIZE);
- w->response.header_output = buffer_create(NETDATA_WEB_RESPONSE_HEADER_SIZE);
+ __atomic_add_fetch(&netdata_buffers_statistics.buffers_web, sizeof(struct web_client), __ATOMIC_RELAXED);
+ w->response.data = buffer_create(NETDATA_WEB_RESPONSE_INITIAL_SIZE, &netdata_buffers_statistics.buffers_web);
+ w->response.header = buffer_create(NETDATA_WEB_RESPONSE_HEADER_SIZE, &netdata_buffers_statistics.buffers_web);
+ w->response.header_output = buffer_create(NETDATA_WEB_RESPONSE_HEADER_SIZE, &netdata_buffers_statistics.buffers_web);
return w;
}
diff --git a/web/server/web_server.c b/web/server/web_server.c
index 4da08d43..d5645a94 100644
--- a/web/server/web_server.c
+++ b/web/server/web_server.c
@@ -37,7 +37,7 @@ LISTEN_SOCKETS api_sockets = {
};
void debug_sockets() {
- BUFFER *wb = buffer_create(256 * sizeof(char));
+ BUFFER *wb = buffer_create(256 * sizeof(char), NULL);
int i;
for(i = 0 ; i < (int)api_sockets.opened ; i++) {